Linux-计算机网络-TCP协议通信流程

Posted 13KB

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-计算机网络-TCP协议通信流程相关的知识,希望对你有一定的参考价值。

1.TCP协议通信流程图

 

1.1TCP协议的通讯流程可以分为以下步骤:

  1. 应用层:应用程序通过系统调用API(如socket)创建一个TCP套接字(socket),并设置好相关的选项。

  2. 传输层:当应用程序调用connect函数建立TCP连接时,TCP协议开始进行三次握手协议:

    a. 客户端向服务端发送SYN数据包(SYN_SENT状态);

    b. 服务端收到SYN数据包后,向客户端发送SYN+ACK数据包(SYN_RCVD状态);

    c. 客户端收到SYN+ACK数据包后,向服务端发送ACK数据包(ESTABLISHED状态)。

  3. 网络层:TCP数据包被封装在IP数据包中,经过网络层进行路由选择和传输。

  4. 数据链路层:TCP/IP数据包被封装在以太网帧中,通过数据链路层进行物理传输。

  5. 传输层:当数据到达目的地后,TCP协议会进行四次挥手协议

    a. 客户端向服务端发送FIN数据包(FIN_WAIT_1状态);

    b. 服务端收到FIN数据包后,向客户端发送ACK数据包(CLOSE_WAIT状态);

    c. 当服务端完成数据发送后,向客户端发送FIN数据包(LAST_ACK状态);

    d. 客户端收到FIN数据包后,向服务端发送ACK数据包,此时连接关闭(CLOSED状态)。

  • TCP在建立连接的时候,采用的是三次握手,在断开连接的时候,采用的是四次挥手 。

这里的重点是三次握手和四次挥手,接下来我们重点讲解一下。

 1.2三次握手过程

 👿Linux三次握手是指TCP连接建立的过程中,客户端和服务端之间通过交换三个数据包来确认彼此的身份和建立连接的过程。

  1. 第一次握手:客户端发送SYN(同步)数据包给服务端,请求建立连接。(SYN数据包包含客户端的初始序列号(seq number),用于同步客户端和服务端的数据传输。)

  2. 第二次握手:服务端收到SYN数据包后,发送一个SYN-ACK(同步-确认)数据包给客户端。(SYN-ACK数据包包含服务端的初始序列号和对客户端SYN数据包的确认。)这个阶段,服务端已经准备好了接收来自客户端的数据。

  3. 第三次握手:户端收到SYN-ACK数据包后,发送一个ACK(确认)数据包给服务端。(ACK数据包包含客户端对服务端SYN-ACK数据包的确认,以及客户端的初始序列号。)此时,客户端和服务端都已经准备好了通信,可以开始传输数据。此时,客户端和服务端之间的TCP连接已经建立完成

就比如你跟你同学说周末到他家里玩(SYN)希望他能够同意。你同学知道了你要到他家玩(收到SYN)表示你那天啥时候来都行(SYN-ACK)让你知道他同意了。

当你知道了同学同意你去他家后,你就要为周末去他家做准备(发送ACK)。

🌈服务器的初始化 

当服务器完成套接字的创建绑定和监听后,可以调用accept函数阻塞等待客户端发起连接请求。

服务端初始化的过程:

  1. 创建套接字:使用socket()函数创建一个套接字,该套接字可以用于接受客户端连接请求。

  2. 绑定端口:使用bind()函数将套接字绑定到一个具体的端口上,以便客户端可以通过这个端口来连接服务器。

  3. 监听连接:使用listen()函数将套接字设置为监听状态,以便接受客户端的连接请求。

  4. 接受连接:使用accept()函数等待客户端的连接请求,当客户端发起连接请求时,accept()函数会返回一个新的套接字描述符,用于和客户端进行通信。

  5. 处理请求:使用新的套接字描述符和客户端进行通信,处理客户端发送过来的请求,并发送响应给客户端。

  6. 关闭连接:当客户端请求处理完成后,关闭套接字描述符,释放相应的资源。

  • 需要注意的是,在服务端处理请求的过程中,可能会有多个客户端同时发起连接请求,因此需要使用多线程或多进程的方式来处理这些请求。同时,服务端也需要进行异常处理,防止因为网络问题或其他原因导致服务器异常退出。

接下来就是客户端与服务器的交流了。

C/S建立连接过程 

客户端与服务器建立连接的过程如下:

  1. 客户端创建一个套接字:使用socket()函数创建一个套接字,用于与服务器进行通信。

  2. 设置连接参数:使用connect()函数将套接字连接到服务器的地址和端口上。

  3. 发起连接请求:使用connect()函数发起连接请求,向服务器发送一个SYN数据包,表示客户端请求连接。

  4. 等待服务器响应:客户端会等待服务器的响应,如果服务器接受了连接请求,就会发送一个SYN+ACK数据包回来,表示服务器愿意建立连接。

  5. 确认服务器响应:客户端接收到服务器的SYN+ACK数据包后,会发送一个ACK数据包,表示客户端确认连接请求已经成功建立。

  6. 建立连接:当服务器接收到客户端发送的ACK数据包后,连接就正式建立了,客户端和服务器可以开始进行通信。

这个建立连接的过程,通常称为三次握手。

  • 需要注意的是,连接并不是立马建立成功的,由于TCP属于传输层协议,因此在建立连接时双方的操作系统会自主进行三次协商,最后连接才会建立成功。

 

数据传输的过程 

 

  •  建立连接后,TCP协议提供全双工的通信服务,所谓全双工的意思是,在同一条连接中,同一时刻,通信双方可以同时写数据,相对的概念叫做半双工,同一条连接在同一时刻,只能由一方来写数据。
  • 服务器从accept返回后立刻调用read,读socket就像读管道一样,如果没有数据到达就阻塞等待。
  • 这时客户端调用write发送请求给服务器,服务器收到后从read返回,对客户端的请求进行处理,在此期间客户端调用read阻塞等待服务器端应答。
  • 服务器调用write将处理的结果发回给客户端,再次调用read阻塞等待下一条请求。
  • 客户端收到后从read返回,发送下一条请求,如此循环下去。

示例:

  1. 客户端使用send()函数将数据发送给服务器。发送的数据可以是任何格式的数据,例如字符串、二进制数据等。

  2. 服务器使用recv()函数接收客户端发送的数据。在接收数据之前,服务器需要先使用accept()函数接受客户端的连接请求,获取到一个新的套接字描述符。服务器从这个新的套接字描述符中读取客户端发送的数据。

  3. 服务器使用send()函数将响应数据发送给客户端。响应数据可以是任何格式的数据,例如字符串、二进制数据等。

  4. 客户端使用recv()函数接收服务器发送的响应数据。

  5. 客户端和服务器之间进行多次数据的传输,直到完成数据的传输任务。

 四次挥手的过程

四次挥手的过程就是两台主机断开通信连接的过程。

TCP连接的四次挥手过程如下:

  1. 客户端发送一个FIN报文,表示客户端已经没有数据要发送给服务器了,并要求服务器关闭连接。客户端进入FIN_WAIT_1状态

  2. 服务器接收到客户端发送的FIN报文后,向客户端发送一个ACK报文作为响应。此时,服务器进入CLOSE_WAIT状态,等待应用程序关闭连接

  3. 如果服务器有尚未发送的数据,会在数据发送完毕后再发送一个FIN报文给客户端,表示服务器已经没有数据要发送了。服务器进入LAST_ACK状态

  4. 客户端接收到服务器发送的FIN报文后,向服务器发送一个ACK报文作为响应。此时,客户端进入TIME_WAIT状态,等待一段时间后才关闭连接。服务器接收到客户端发送的ACK报文后,进入CLOSED状态,连接关闭。

端口连接

  • 当双方通信结束之后,需要通过四次挥手的方案使双方断开连接,当客户端调用close关闭连接后,服务器最终也会关闭对应的连接。而其中一次close就对应两次挥手,因此一对close最终对应的就是四次挥手。
  • 其中最后一次挥手是为了确保数据传输的完整性和可靠性,避免数据重复和丢失等问题。在断开连接之前,需要确保所有的数据都已经被传输完毕,并且各个状态和缓存已经被正确处理和释放,避免可能的资源泄露和数据损失。

需要注意的是,客户端进入TIME_WAIT状态的目的是为了确保服务器接收到客户端发送的ACK报文,避免可能的数据重传和延迟。在客户端等待的这段时间内,客户端不会再向服务器发送任何数据,同时等待的时间也是由TCP协议栈决定的,通常为2倍的最大段生存时间

👿四次挥手的好处 

  1. TCP四次挥手的主要作用是安全地关闭连接并释放资源。在TCP连接中,一旦完成了数据的传输,就需要关闭连接以释放占用的资源,避免资源浪费和不必要的等待。
  2. 四次挥手的过程可以保证数据传输的完整性和可靠性,避免出现数据丢失和错误。同时,四次挥手的过程也可以保护连接的安全性,避免数据被未经授权的用户访问和篡改。
  3. 另外,四次挥手过程中,服务器可以先关闭连接并等待客户端的确认,避免出现客户端长时间占用连接资源的情况。这样可以有效地避免连接的滥用和资源的浪费。
  4. 总之,TCP四次挥手过程是保证连接可靠性、数据完整性和安全性的重要步骤,是网络通信中必不可少的一部分。

 

Linux网络编程——TCP和UDP通信

  • TCP协议流程图、TCP建立即时聊天
  • TCP即时聊天升级:服务器在客户端断开后不断开,客户端可以多次重连服务器进行即时聊天
  • UDP协议流程图、UDP建立即时连接
  • 如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)需调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket,但也可以直接用setsockopt和reuse。
  • SO_RCVLOWAT设置接收缓冲区下限

1、TCP协议的流程图

服务端:socket---bind---listen---while(1){---accept---recv---send---close---}---close
客户端:socket----------------------------------connect---send---recv-----------------close

技术图片

TCP建立即时聊天

tcp_client.c

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd=socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd,-1,"socket");
    struct sockaddr_in ser;
    bzero(&ser,sizeof(ser));
    ser.sin_family=AF_INET;
    ser.sin_port=htons(atoi(argv[2]));
    ser.sin_addr.s_addr=inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    int ret;
    ret=connect(socketFd,(struct sockaddr*)&ser,sizeof(ser));
    ERROR_CHECK(ret, -1, "connect");
    printf("connect success\\n");
    char buf[128]={0};
    fd_set rdset;
    while(1){
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(socketFd, &rdset);
        ret = select(socketFd + 1, &rdset, NULL, NULL, NULL);
        if(FD_ISSET(socketFd, &rdset)){
            bzero(buf, sizeof(buf));
            ret = recv(socketFd, buf, sizeof(buf), 0);
            ERROR_CHECK(ret, -1, "recv");
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            printf("%s\\n", buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            memset(buf, 0, sizeof(buf));
            ret = read(STDIN_FILENO, buf, sizeof(buf));
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            ret = send(socketFd, buf ,strlen(buf) - 1, 0);
            ERROR_CHECK(ret, -1, "send");
        }
    }
    close(socketFd);
}

tcp_server.c

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
    ERROR_CHECK(new_fd, -1, "accept");
    printf("client ip=%s, port=%d\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    fd_set rdset;
    char buf[128] = {0};
    while(1){
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(new_fd, &rdset);
        ret = select(new_fd + 1, &rdset, NULL, NULL, NULL);
        if(FD_ISSET(new_fd, &rdset)){
            bzero(buf, sizeof(buf));
            ret = recv(new_fd, buf, sizeof(buf), 0);
            ERROR_CHECK(ret, -1, "recv");
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            printf("%s\\n", buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            memset(buf, 0, sizeof(buf));
            ret = read(STDIN_FILENO, buf, sizeof(buf));
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            ret = send(new_fd, buf, strlen(buf) - 1, 0);
            ERROR_CHECK(ret, -1, "send");
        }
    }
    close(new_fd);
    close(socketFd);
    return 0;
}

2、TCP即时聊天升级:服务器在客户端断开后不断开,客户端可以多次重连服务器进行即时聊天

tcp_server.c

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    char buf[128] = {0};
    fd_set rdset; 
    fd_set needMonitorSet; //需要监听的描述符集合
    FD_ZERO(&needMonitorSet);  
    FD_SET(STDIN_FILENO, &needMonitorSet);
    FD_SET(socketFd, &needMonitorSet);
    while(1){
        memcpy(&rdset, &needMonitorSet, sizeof(fd_set));
        ret = select(11, &rdset, NULL, NULL, NULL); //设最大监控描述符为10
        if(FD_ISSET(socketFd, &rdset)){ //如果监听到客户端则accept接受远程计算机的连接请求
            new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen); //accept函数接受一个连接时,会返回一个新的socket标识符,以后数据的数据传输和读取就要通过这个新的socket编号来处理,原来的socket继续监听其他客户机的连接请求。
            ERROR_CHECK(new_fd, -1, "accept");
            printf("client ip=%s, port=%d\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
            FD_SET(new_fd, &needMonitorSet);    
        }
        if(FD_ISSET(new_fd, &rdset)){
            bzero(buf, sizeof(buf));
            ret = recv(new_fd, buf, sizeof(buf), 0);
            ERROR_CHECK(ret, -1, "recv");
            if(ret == 0){
                printf("byebye!\\n");
                FD_CLR(new_fd, &needMonitorSet);  //从needMonitorSet中删除new_fd
                close(new_fd);
                continue;
            }
            printf("%s\\n", buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            memset(buf, 0, sizeof(buf));
            ret = read(STDIN_FILENO, buf, sizeof(buf));
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            ret = send(new_fd, buf, strlen(buf) - 1, 0);
            ERROR_CHECK(ret, -1, "send");
        }
    }
    close(socketFd);
    return 0;
}

tcp_client.c和1同

3、使用UDP协议的流程图

服务端:socket---bind---recvfrom---sendto---close
客户端:socket----------sendto---recvfrom---close

技术图片

  • sendto()函数原型:
    int sendto(int sockfd, const void msg,int len,unsigned int flags,const struct sockaddr to, int tolen);

    该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

  • recvfrom()函数原型:

    **int recvfrom(int sockfd,void buf,int len,unsigned int flags,struct sockaddr from,int *fromlen);**
    from是一个struct sockaddr类型的变量,该变量保存连接机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。

    UDP传输一个数据报示例:

    udp_server.c

#include <func.h>

int main(int argc, char **argv){
    ARGS_CHECK(argc, 3);
    int socketFd;
    socketFd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser,sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    char buf[128] = {0};
    struct sockaddr_in client;
    int addrlen = sizeof(client);
    ret = recvfrom(socketFd, buf, 5, 0, (struct sockaddr*)&client, &addrlen);
    ERROR_CHECK(ret, -1, "recvfrom");
    printf("client ip = %s, port = %d\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    printf("udp server gets %s\\n", buf);
    //bzero(buf, sizeof(buf));
    //ret = recvfrom(socketFd, buf, 5, 0, (struct sockaddr*)&client, &addrlen);
    //ERROR_CHECK(ret, -1, "recvfrom");
    //printf("udp server gets %s\\n", buf);
    ret = sendto(socketFd, "world", 5, 0, (struct sockaddr*)&client, sizeof(client));
    ERROR_CHECK(ret, -1, "sendto");
    close(socketFd);
    return 0;
}

udp_client.c

#include <func.h>

int main(int argc, char *argv[]){
    ARGS_CHECK(argc, 3);
    int socketFd;
    socketFd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);
    int ret;
    ret = sendto(socketFd, "helloworld", 10, 0, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "sendto");
    char buf[128] = {0};
    ret = recvfrom(socketFd, buf, sizeof(buf), 0, NULL, NULL);
    ERROR_CHECK(ret, -1, "recvfrom");
    printf("udp client gets %s\\n", buf);
    close(socketFd);
    return 0;
}

4、使用UDP协议建立即时连接

udp_server.c

#include <func.h>

int main(int argc, char **argv){
    ARGS_CHECK(argc, 3);
    int socketFd;
    socketFd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser,sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    char buf[128] = {0};
    struct sockaddr_in client;
    int addrlen = sizeof(client);
    printf("client ip = %s, port = %d\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    fd_set rdset;
    while(1){
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(socketFd, &rdset);
        ret = select(socketFd + 1, &rdset, NULL, NULL, NULL);
        if(FD_ISSET(socketFd, &rdset)){
            bzero(buf, sizeof(buf));
            ret = recvfrom(socketFd, buf, sizeof(buf), 0, (struct sockaddr*)&client, &addrlen);
            ERROR_CHECK(ret, -1, "recvfrom");
            printf("%s\\n", buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            bzero(buf, sizeof(buf));
            ret = read(STDIN_FILENO, buf, sizeof(buf));
            if(0 == ret){
                printf("byeybye\\n");
                break;
            }
            ret = sendto(socketFd, buf, strlen(buf) - 1, 0, (struct sockaddr*)&client, sizeof(client));
            ERROR_CHECK(ret, -1, "sendto");
        }
    }
    close(socketFd);
    return 0;
}

udp_client.c

#include <func.h>

int main(int argc, char *argv[]){
    ARGS_CHECK(argc, 3);
    int socketFd;
    socketFd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);
    int ret;
    char buf[128] = {0};
    fd_set rdset;
    while(1){
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(socketFd, &rdset);
        ret = select(socketFd + 1, &rdset, NULL, NULL, NULL);
        if(FD_ISSET(socketFd, &rdset)){
            bzero(buf, sizeof(buf));
            ret = recvfrom(socketFd, buf, sizeof(buf), 0, NULL, NULL);
            ERROR_CHECK(ret, -1, "recvfrom");
            printf("%s\\n", buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            bzero(buf, sizeof(buf));
            ret = read(STDIN_FILENO, buf, sizeof(buf));
            if(0 == ret){
                printf("byeybye\\n");
                break;
            }
            ret = sendto(socketFd, buf, strlen(buf) - 1, 0, (struct sockaddr*)&ser, sizeof(ser));
            ERROR_CHECK(ret, -1, "sendto");
        }
    }
    close(socketFd);
    return 0;
}

5、如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)需调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket,但也可以直接用setsockopt和reuse。

int reuse=1;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&reuse,sizeof(int));

tcp_server.c

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    int ret;
    int reuse = 1;
    ret = setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    ERROR_CHECK(ret, -1, "setsockopt");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
    ERROR_CHECK(new_fd, -1, "accept");
    printf("client ip=%s, port=%d\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    fd_set rdset;
    char buf[128] = {0};
    while(1){
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(new_fd, &rdset);
        ret = select(new_fd + 1, &rdset, NULL, NULL, NULL);
        if(FD_ISSET(new_fd, &rdset)){
            bzero(buf, sizeof(buf));
            ret = recv(new_fd, buf, sizeof(buf), 0);
            ERROR_CHECK(ret, -1, "recv");
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            printf("%s\\n", buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            memset(buf, 0, sizeof(buf));
            ret = read(STDIN_FILENO, buf, sizeof(buf));
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            ret = send(new_fd, buf, strlen(buf) - 1, 0);
            ERROR_CHECK(ret, -1, "send");
        }
    }
    close(new_fd);
    close(socketFd);
    return 0;
}

tcp_client.c和1同

6、SO_RCVLOWAT:接收缓冲区下限

tcp_server_rcvlowat.c

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    int ret;
    int reuse = 1;
    ret = setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    ERROR_CHECK(ret, -1, "setsockopt");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
    ERROR_CHECK(new_fd, -1, "accept");
    printf("client ip=%s, port=%d\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    fd_set rdset;
    char buf[128] = {0};
    int rcvLowAt = 10;
    ret=setsockopt(new_fd,SOL_SOCKET,SO_RCVLOWAT,&rcvLowAt,sizeof(int));

    ERROR_CHECK(ret, -1, "setsockopt");
    while(1){
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(new_fd, &rdset);
        ret = select(new_fd + 1, &rdset, NULL, NULL, NULL);
        if(FD_ISSET(new_fd, &rdset)){
            bzero(buf, sizeof(buf));
            ret = recv(new_fd, buf, sizeof(buf), 0);
            ERROR_CHECK(ret, -1, "recv");
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            printf("%s\\n", buf);
        }
        if(FD_ISSET(STDIN_FILENO, &rdset)){
            memset(buf, 0, sizeof(buf));
            ret = read(STDIN_FILENO, buf, sizeof(buf));
            if(ret == 0){
                printf("byebye!\\n");
                break;
            }
            ret = send(new_fd, buf, strlen(buf) - 1, 0);
            ERROR_CHECK(ret, -1, "send");
        }
    }
    close(new_fd);
    close(socketFd);
    return 0;
}

tcp_client.c和1同

以上是关于Linux-计算机网络-TCP协议通信流程的主要内容,如果未能解决你的问题,请参考以下文章

TCP通信流程解析

TCP通信流程解析

Linux网络基础

网络通信流程

OSI七层模型,讲解tcp/ip五层涉及的网络协议,网络通信实现,结合协议来看网络通信流程

[架构之路-44]:目标系统 - 系统软件 - Linux下的网络通信-4-Linux内核网络协议栈网络配置命令网络服务启动