【计算机网络】TCP套接字通信

来源:互联网 发布:合作医疗报销软件 编辑:程序博客网 时间:2024/05/31 19:11

本文内容概述:
1>单进程的套接字通信
2>多进程的套接字通信
3>多线程的套接字通信


开始学习Linux基础知识的时候,我们知道Linux下一切皆文件,并且大致可以分为几类文件:普通文件、目录、连接文件、设备和设备文件、套接字、管道。而套接字就是实现网络上进程之间的通信,套接字也是文件。在TCP/IP协议中,IP地址和端口号唯一标识网络中的一个唯一进程,IP地址和端口号就是套接字。
网络中要实现通信,少不了数据的传输。所以这里就引入了网络字节序的概念。
在前边的学习中,我们接触到大端和小端的概念。小端:数据的地位在低地址,高位在高地址;大端:数据的低位在高地址,高位在低地址。网络数据流同样也有大端和小端之分。网络数据流先发出的是低地址,后发出的是高地址。TCP/IP规定,网络数据流采用大端字节序,即就是低位在高地址。我们之所以会说到大端和小端?是因为,网络通信的时候必须知道端口号,如果发送端是大端字节序,接收端是小端字节序,那么最后看到的端口号就是不正确的端口号,所以,我们必须将端口号在发送端和接收端之间转换成统一的字节序形式。
下边提供一组接口:
这里写图片描述


(一)单进程的套接字通信

服务器:
(1)调用socket,请求系统分配文件描述符。
这里写图片描述

这里写图片描述
protocol参数: 与特定的地址家族相关的协议,TCP协议一般为IPPROTO_TCP。也可以写0,那么系统会根据地址格式和套接字类别,自动选择一个适合的协议。


(2)调用bind,绑定本机的信息,包括,IP地址类型,IP地址,端口号。
这里写图片描述
所以,这里又涉及到字符串与in_addr的转换:
这里写图片描述


(3)调用listen,监听:
这里写图片描述


(4)调用accept,接收发送连接请求的socket。
这里写图片描述
(6)read读取socket中的数据。
(7)通信完成后,调用close关闭套接字。


客户机:
(1)调用socket,请求系统分配文件描述符。
(2)调用connect,连接服务器。
这里写图片描述


(3)调用read从标准输入中读取数据,放到自定义缓冲区buf中,然后将buf中的数据写到套接字中。
(4)通信完成后,调用close关闭套接字。
代码实现:

//server.c#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>int StartUp(int port,const char* ip){    int ListenSock = socket(AF_INET,SOCK_STREAM,0);    if(ListenSock < 0)    {        perror("socket");        exit(1);    }    struct sockaddr_in local;    local.sin_family = AF_INET;    local.sin_port = htons(port);    local.sin_addr.s_addr = inet_addr(ip);    if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0)    {        perror("bind");        exit(2);    }    if(listen(ListenSock,5) < 0)    {        perror("listen");        exit(3);     }    return ListenSock;}int main(int argc,const char* argv[]){    if(argc != 3)    {        printf("input error\n");        return 1;    }    int len;    int listenSock = StartUp(atoi(argv[2]),argv[1]);    struct sockaddr_in client;    while(1)    {        int sock = accept(listenSock,(struct sockaddr*)&client,&len);//获取客户机的信息        if(sock < 0)        {            perror("accept");            continue;        }        printf("get a client,ip is %s,port is %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));        char buf[1024];        while(1)        {            ssize_t s = read(sock,buf,sizeof(buf)-1);//服务器进行读数据            if(s > 0)            {                buf[s] = 0;                printf("client# %s\n",buf);            }            else            {                //数据已经读完了,客户端不发送数据了                printf("client is quit!\n");            }        }        close(sock);    }    return 0;}//client.c#include<stdio.h>#include<sys/socket.h>#include<sys/types.h>#include<unistd.h>#include<arpa/inet.h>int main(int argc,const char* argv[]){    int sock = socket(AF_INET,SOCK_STREAM,0);    if(sock < 0)    {        perror("socket");        return 1;    }    struct sockaddr_in server;    server.sin_family = AF_INET;    server.sin_port = htons(atoi(argv[2]));    server.sin_addr.s_addr = inet_addr(argv[1]);    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)    {        perror("connect");        return 2;    }    char buf[1024];    while(1)    {        printf("send# ");        fflush(stdout);        //从标准输入读数据,读到buf中,然后从buf写到管道        ssize_t s = read(0,buf,sizeof(buf)-1);        if(s < 0)        {                       perror("read");            return 3;        }               buf[s-1] = 0;        write(sock,buf,s);    }    close(sock);    return 0;}

程序运行结果(本地环回测试):
这里写图片描述
下边我们来模拟一种情形:先运行server,再运行client,client给server发数据,然后ctrl+c终止调server,立即再次启动server,会出现什么现象呢?
这里写图片描述

这是什么原因呢?服务器终止程序,服务器就是主动发起断开连接请求的一方,根据TCP的3次握手4次挥手协议,主动发起连接断开请求的一方,最后必须等待2MSL的时间确认客户端是否收到自己的确认信息。这里,我们立即运行server的时候,server还是在TIME_WAIT状态,所以bind的时候就会出现地址已经被占用。
解决办法:socket之后,bind之前,加语句
int opt = 1;
setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
就是为了处理这个问题。

网上找来一张关于tcp通信的图:
这里写图片描述
注意:我们的tcp通信,只是实现了客户端发数据,服务器接收数据。而在实际的应用中,tcp通信是一种全双工通信,即就是任意时刻,客户机可以发送数据,也可以接收数据,服务器可以接收数据,也可以发送数据,这个就需要多进程实现。下边,针对这个tcp通信,我来解决几个问题:
(1)为什么客户端不需要调用bind函数(不是不能,是不必要)?
bind函数是将本地的地址等信息绑定到套接字。而客户端不需要调用bind,这时由操作系统内核自动分配一个动态端口号,通信结束后由操作系统收回。这里的原因还是客户端给出的端口号容易出现冲突问题。

(2).端口号是怎样进行分类的?
端口包括物理端口和逻辑端口。物理端口是用于连接物理设备之间的端口,逻辑端口是逻辑上区分服务的端口。TCP/IP中的端口就是逻辑端口,范围从0~65535(TCP报文段中用16个比特位表示源端口和目的端口),最多有65536个端口,比如浏览网页的端口是80端口,用于FTP通信的端口是21端口等等。
端口号大致分为3类:
a.系统端口:从0到1023,紧密绑定与一些服务。通常这些端口的通讯明确表明了某种服务的协议,比如21端口总是用于FTP通信。
b.注册端口:从1024~49151.他们松散的绑定于一些服务,也就是说有许多服务绑定于这些端口,这些端口同样用于其他目的。
c.动态/私有端口:从49152~ 65535,不应为服务分配这些端口。


(二)多进程套接字通信

上边的代码,只可以一个客户端进行发送数据,而在实际的应用中,,都是会出现多个客户端给服务器发送数据,所以,上边的实现并不实用。所以,我们可以实现一个多进程的socket通信,以实现多个客户端给服务器发数据。
实现方法:服务器端可以创建多个子进程去处理客户端发来的信息。当每次收到一个新的客户端的连接请求的时候,我们就会fork()出一个子进程,父进程用于等待子进程,子进程用于执行 读客户端发的数据 的操作。细心的你可能会发现,我们在子进程读取信息之前还进行了一次fork(),这是为什么呢?其实,我们用子进程fork()出一个孙子进程,终止掉儿子进程,儿子进程被它的父进程回收,此时的孙子进程就是一个孤儿进程,被1号进程领养。这样做的目的就是,不要让儿子进程等待孙子进程太久而消耗太多的系统资源。
图解:
这里写图片描述
这里还涉及到父进程关闭通信套接字,子进程关闭监听套接字。这是因为,父进程是来监听的,不需要通信,子进程是读取信息的,不需要监听。

代码实现:

//server.c#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>int StartUp(int port,const char* ip){    int ListenSock = socket(AF_INET,SOCK_STREAM,0);    if(ListenSock < 0)    {        perror("socket");        exit(1);    }    int opt = 1;    setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));    struct sockaddr_in local;    local.sin_family = AF_INET;    local.sin_port = htons(port);    local.sin_addr.s_addr = inet_addr(ip);    if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0)    {        perror("bind");        exit(2);    }    if(listen(ListenSock,5) < 0)    {        perror("listen");        exit(3);    }    return ListenSock;}int main(int argc,const char* argv[]){    if(argc != 3)    {        printf("input error\n");        return 1;    }    int len;    int listenSock = StartUp(atoi(argv[2]),argv[1]);    struct sockaddr_in client;    while(1)    {        int sock = accept(listenSock,(struct sockaddr*)&client,&len);//获取客户机的信息        if(sock < 0)        {            perror("accept");            continue;        }        printf("get a client,ip is %s,port is %d\n",inet_ntoa(client.sin_addr),\                    ntohs(client.sin_port));        int id = fork();        if(id > 0)        {            close(sock);            while(waitpid(-1,NULL,WNOHANG) > 0);            continue;        }        else        {            close(listenSock);            if(fork() > 0)            {                exit(0);            }            char buf[1024];            while(1)            {                ssize_t s = read(sock,buf,sizeof(buf)-1);//服务器进行读数据                if(s > 0)                {                    buf[s] = 0;                    printf("client# %s\n",buf);                }                else                {                    //数据已经读完了,客户端不发送数据了                    printf("client is quit!\n");                    break;                }            }            close(sock);        //    exit(4);            break;        }    }    return 0;}//client.c#include<stdio.h>#include<sys/socket.h>#include<sys/types.h>#include<unistd.h>#include<arpa/inet.h>int main(int argc,const char* argv[]){    int sock = socket(AF_INET,SOCK_STREAM,0);    if(sock < 0)    {        perror("socket");        return 1;    }    struct sockaddr_in server;    server.sin_family = AF_INET;    server.sin_port = htons(atoi(argv[2]));    server.sin_addr.s_addr = inet_addr(argv[1]);    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)    {        perror("connect");        return 2;    }    char buf[1024];    while(1)    {        printf("send# ");        fflush(stdout);        //从标准输入读数据,读到buf中,然后从buf写到管道        ssize_t s = read(0,buf,sizeof(buf)-1);        if(s < 0)        {                       perror("read");            break;        }               buf[s-1] = 0;        write(sock,buf,s);    }    close(sock);    return 0;}

(三)多线程的套接字通信

在前边的系统编程的学习中,我们知道线程是进程内部的一个执行流,是在进程的地址空间中运行。而进程是程序的一次动态的执行过程。系统中的进程数过于多的话,会增加系统的负担。所以这里采用线程实现通信。
实现方法:
主线程中创建出一个新线程,新线程的执行函数是读取信息。类似于上边的多进程间的通信,我们可以将新的线程进行分离,分离之后的线程就不需要主线程去等待,而是由操作系统区回收。(这里我们不可以join新线程,如果这样做的话,主线程还是需要花费很长的时间去等待,所以,新的线程还是由系统去回收)
代码实现:

//server.c#include<stdio.h>#include<stdlib.h>#include<netinet/in.h>#include<arpa/inet.h>#include<sys/types.h>#include<sys/socket.h>int StartUp(int port,const char* ip){    int sock = socket(AF_INET,SOCK_STREAM,0);    if(sock < 0)    {        perror("socket");        exit(2);    }    int opt = 1;    setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));    struct sockaddr_in local;    local.sin_family = AF_INET;    local.sin_port = htons(port);    local.sin_addr.s_addr = inet_addr(ip);    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)    {        perror("bind");        exit(3);    }    if(listen(sock,5) < 0)    {        perror("listen");        exit(4);    }    return sock;}void* thread_hander(void* arg){    int sock = *((int*)arg);    char buf[1024];    while(1)    {        ssize_t _s = read(sock,buf,sizeof(buf)-1);        if(_s > 0)        {            buf[_s-1] = 0;            printf("client say#%s\n",buf);            if(write(sock,buf,sizeof(buf)-1)<0)            {                break;            }        }        else if(_s == 0)        {            printf("client is quit!\n");            break;        }        else        {            perror("read");            break;        }    }    close(sock);}int main(int argc,const char* argv[]){    if(argc != 3)    {        printf("input  error\n");        return 1;    }    int listenSock = StartUp(atoi(argv[2]),argv[1]);    struct sockaddr_in client;    int len = 0;    while(1)    {        int sock = accept(listenSock,(struct sockaddr*)&client,&len);        if(sock < 0)        {            perror("accept");            return 5;        }        printf("get a client!ip is %s,port is %d\n",inet_ntoa(client.sin_addr),\                ntohs(client.sin_port));        pthread_t tid;        int ret = pthread_create(&tid,NULL,thread_hander,&sock);        if(ret < 0)        {            perror("pthread_create");            return 6;        }        pthread_detach(tid);    }    return 0;}//client.c#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>int main(int argc, const char* argv[]){    if(argc != 3)    {        printf("input error\n");        return 1;    }    int sock = socket(AF_INET,SOCK_STREAM,0);    if(sock < 0)    {        perror("socket");        return 2;    }    struct sockaddr_in server;    server.sin_family = AF_INET;    server.sin_port = htons(atoi(argv[2]));    server.sin_addr.s_addr = inet_addr(argv[1]);    int ret = connect(sock,(struct sockaddr*)&server,sizeof(server));    if(connect < 0)    {        perror("connect");        return 3;    }    char buf[1024];    while(1)    {        printf("send#");        fflush(stdout);        ssize_t _s = read(0,buf,sizeof(buf)-1);        if(_s > 0)        {            buf[_s - 1] = 0;            if(write(sock,buf,sizeof(buf)-1) < 0)            {                break;            }            ssize_t s = read(sock,buf,sizeof(buf)-1);            if(s > 0)            {                buf[s] = 0;                printf("server echo#%s\n",buf);            }        }        else        {            perror("read");            return 4;        }    }    return 0;}
1 0
原创粉丝点击