设为首页 收藏本站
查看: 1351|回复: 0

[经验分享] Linux下搭建一个简单的TCP通信

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2016-7-27 09:38:40 | 显示全部楼层 |阅读模式
                      基础知识
socket && socket pair
socket是 IP号 + 端口号。
因此,在一个网络中,每个socket都是一个唯一的进程。

而TCP协议中,建立连接的两个进程分别用2个socket表示,这两个socket组成一对"socket pair"。

可以看出 这个 "socket pair" 即是一个唯一的连接,而他们之间通信的本质,也正是进程间通信。

大端小端:

大端 小端是内存中存放数据的两种方式,其中:
大端是 高位数据存放在低地址,低位数据存放在高地址
小端反之。
而网络数据传输采用的是大端,因此在发送数据和接收数据的时候都要进行判断。如果不统一,则需要进行转换。
至于在实际编写代码的时候,下面是现成的轮子:
1
2
3
4
uint32_t htonl(uint32_t hostlong);//将一个无符号短整形数从网络字节顺序转换为主机字节顺序
uint32_t htons(uint32_t hostshort);//将一个无符号长整形数从网络字节顺序转换为主机字节顺序
uint32_t ntohl(uint32_t netlong);//将主机的无符号短整形数转换成网络字节顺序
uint32_t ntohs(uint32_t netshort);//将主机的无符号长整形数转换成网络字节顺序








调用网络库的函数:
1
2
3
4
5
socket()  //打开网络端口
bind()    //将sockfd和地址绑定,并用于网络通信
listen()  //声明sockfd处于监听的状态
connect() //建立连接(也就是三次握手的步骤,当然是由客户端发出)(connect到的地址就是server中 bind()的地址)
accept()  //接受连接 (不属于三次握手,而是三次握手之后的通信)









—————————————————————
代码实现
工作流程:
Server端一直挂起,等待客户端的连接,一旦建立连接,开始通信


Server端

三种模式:
单执行流的Server端每次只能和一个Client端连接
多进程的Server能同时和多个Client连接,但开销较大
多线程的Server用多个线程的方式同时和多个Client端连接,我的Server端就采用的是这种模式




Server建立连接并进行通信主要分为4个步骤:
1、创建 socket
2、将socket 和 Server的IP、端口号进行绑定
3、设置为监听状态
4、每建立一个链接,就开启一个新的线程,在线程内部与对应的Client端进行通信
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

//server

static void usage(const char *proc )
{
    printf("Usage: %s [ip] [port]\n", proc);
}

void* thread_run(void *arg)
{
    printf("create a new thread\n");
    int fd = (int)arg;
    char buf[1024];
    while(1)
    {
        memset(buf, '\0', sizeof(buf));
        ssize_t _s = read(fd, buf, sizeof(buf) - 1 );
        if( _s > 0 )
        {
            buf[_s] = '\0';
            printf("client :# %s\n", buf);

            memset(buf, '\0', sizeof(buf));
            printf("please enter: ");
            fflush(stdout);
            _s = read(0, buf, sizeof(buf) - 1);
            if(_s > 0)
            {
                buf[_s - 1] = '\0';
                write(fd, buf, strlen(buf) );
            }
        }
        else if(_s == 0)
        {
            printf("client close...\n");
            break;
        }
        else
        {
            printf("read error...\n");
            break;
        }
    }
}

int main(int argc, char *argv[] )
{
    if(argc != 3 )
    {
        usage(argv[0]);
        exit(1);
    }


    //1.CreateSocket
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock < 0)
    {
        perror("socket");
        return 2;
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[2]));  //argv[2]
    local.sin_addr.s_addr = inet_addr(argv[1]);

    //2.Bind
    if( bind(listen_sock, (struct sockaddr*)&local, sizeof(local) ) < 0)
    {
        perror("bind");
        return 3;
    }

    //3.Always Listen
    listen(listen_sock, 5);

    //4.Accept
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    while(1)
    {
        int fd = accept(listen_sock, (struct sockaddr*)&peer, &len);
        if(fd < 0)
        {
            perror("accept");
            return 4;
        }

        printf("get a new link... socket -> %s : %d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));

        pthread_t id;
        pthread_create(&id, NULL, thread_run, (void*)fd);
        pthread_detach(id);

    }
    return 0;
}







Client端:



Client端建立连接并进行通信主要分为4个步骤:
1、创建Socket
2、连接
3、通信


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

//client

static void usage(const char *proc )
{
    printf("Usage: %s [ip] [port]\n", proc);
}

int main(int argc, char *argv[])
{
    if(argc != 3 )
    {
        usage(argv[0]);
        exit(1);
    }
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        return 2;
    }
    struct sockaddr_in remote;
    remote.sin_family = AF_INET;
    remote.sin_port = htons(atoi(argv[2]));
    remote.sin_addr.s_addr = inet_addr(argv[1]);

    if( connect(sock, (struct sockaddr*)&remote, sizeof(remote) ) < 0)
    {
        perror("connect");
        return 3;
    }

    char buf[1024];
    while(1)
    {
        printf("Please Enter: ");
        fflush(stdout);
        ssize_t _s = read(0, buf, sizeof(buf) - 1);

        if(_s > 0)
        {
            buf[_s - 1] = '\0';
            write(sock, buf, strlen(buf));
            _s = read(sock, buf, sizeof(buf));
            if( _s > 0)
            {
                buf[_s] = '\0';
                printf("%s\n", buf);
            }
        }
    }
    return 0;
}






—————————————————————
总结&&一些值得注意的事情
一、汇总Server 和 Client的步骤:


Server端:
1、创建 socket
2、将socket 和 Server的IP、端口号进行绑定
3、设置为监听状态
4、等待,直到收到Client端的连接请求每建立一个链接,就开启一个新的线程,在线程内部与对应的Client端进行通信

Client端:
1、创建Socket
2、连接
3、通信


二、为什么不用void*
socket编程采用sockaddr*强制类型转换的方式对其家族的类型进行传参
当然相比,void*的通用性更强一些,理论上用void*设计更好,
但socket网络库要早于ANSI规范的提出,所以那时候并没有void*.......



三、Client端的socket没必要进行绑定

client端的端口号并不需要像server端那样固定以方便连接,因此不需要绑定,
而由于没有调用bind,client的端口号是由内核自动分配的

四、”Address already in use“

如下图:
wKioL1eXa1PCdGayAABCO7JbhRE506.jpg
建立连接,然后关闭server端,再打开,会提示这样,这是因为:
server程序终止了,但TCP协议层的连接并未完全断开,当前IP被占用了。
TCP协议规定,主动关闭连接的一方要处于 TIME_WAIT 状态,等待两个MSL的时间才回到CLOSE的状态。
MSL的具体时间由操作系统决定,通常 server主动关闭2分钟后 就可以再次启动了
当然,如果等不及的话,也可以用setsockopt,:

使用setsockopt,使得socket可以被重用
具体的做法为,在socket调用和bind调用之间加上一段对socket的设置:

1
2
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));



这段代码的设置,表示允许创建端口号相同但IP地址不同的多个socket描述符



五、完整的实现


https://github.com/HonestFox/NetWorks/tree/TCP
                   


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-250033-1-1.html 上篇帖子: varnish配置详解及实例 下篇帖子: postgresql的源码安装及配置使用 Linux 通信
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表