TCP协议:可靠传输与连接机制

1.TCP协议的特点

不同网络的两台主机上的应用进程如果想要进行通信,就需要通过物理层、数据链路层、网络层这三层进行数据包转发,再通过传输层把数据包发送给主机中的指定进程,所以网络模型中的传输层至关重要。

传输层中最为常见的两个协议分别是传输控制协议TCP(Transmission Control Protocol)和用户数据报协议UDP(User Datagram Protocol),想要掌握这两种协议,则需要阅读协议的标准文件,TCP标准内容如下:

通过阅读RFC 793规范可以知道,TCP协议是面向连接的、可以实现端对端通信的可靠的协议。

TCP协议可以实现让处于不同网络的但是互联的主机中的进程与进程之间进行可靠的进程间通信,所以TCP是基于一对一通信的协议。

注意:由于TCP协议是面向连接的,所以只能支持一对一服务,不提供广播服务和组播服务。

2.TCP协议可靠性

可以看到,RFC规范中指出TCP协议可靠性的定义与实现,TCP协议必须恢复从下层协议传输过来可能出现损坏、出现丢失、出现重复、到达无序的数据。

TCP协议传输数据时会为每个字节分配一个序列号,通过这个序列号就可以判断数据是否重复到达以及数据是否丢失,还可以通过序列号对数据进行排序。

另外,接收端每次接收到数据之后必须发送确认应答(ACK),如果在超时时间内没有发送应答信号(ACK),则TCP协议会认为数据没有送达,则会重新发送数据。

TCP协议判断数据是否损坏的方式是给每个数据段都添加校验和,接收端收到数据段进行校验,如果校验失败则丢弃已经损坏的数据段,也并不会进行确认应答,所以TCP协议会再次传输数据段。

3.TCP的连接机制

通过规范可以知道双方主机的进程在通信之前必须建立TCP连接,并且每个连接都由一对套接字进行指定(也就是说通信双方都创建TCP套接字)。

另外,因为通信可能是建立在不可靠的网络中以及网络层不可靠的传输机制,所以TCP协议中采用了一种基于时钟的序列号握手机制实现双方的有效连接,避免出现错误的连接初始化。

4.TCP报首的格式

想要掌握TCP连接的握手机制,以及了解序列号和应答标志等字段,则需要阅读TCP协议头:

4.1源端口号

源端口号指的是发送端的进程端口号,是一个16bit的无符号短整型数,需要注意端口范围。

4.2目标端口

目标端口指的是接收端的进程端口号,是一个16bit的无符号短整型数,需要注意端口范围。

4.3序列号

序列号指的是数据段中的第一个字节的序列号,当然,有一种情况除外:就是数据段中存在SYN标志位。

如果数据段中存在SYN标志位,则序列号为初始序列号(ISN),那么数据段中的第一个字节的序列号等于 ISN + 1。

序列号其实是建立TCP连接时由计算机生成的随机数作为序列号初始值,这个序列号初始值由SYN(SYN指的是希望建立连接)包发送给接收端,序列号主要是为了解决网络包乱序的问题。

4.4确认应答号

可以看到,确认应答号是一个32bit的整数,如果设置了ACK控制位,则这个字段就包含字段发送方所期望下一次接收的数据段的序列号的值,其实就是指接收到的数据的字节数量。

一旦双方建立连接,这个确认应答号就会发送,注意:哪一方收到数据就是哪一方发送应答。

4.5头部长度

指的是TCP头部的长度,单位是字(一个字等于32bit),所以TCP头部长度是一个32bit的整数,用于表示数据的开始位置。

可以看到,TCP协议头的长度至少是20字节,options字段是可选的,如果使用options选项字段,则头部长度会更长。

4.6控制位

可以看到TCP协议头中存在6bit控制位,每个控制位的含义各不相同,其中较为常用的控制位是ACK(确认应答)、SYN(建立连接)、FIN(断开连接)。

ACK:该位是确认应答位,如果设置为1则表示支持确认应答,TCP规定该位必须设置为1。

SYN:该位是希望建立连接,另外可以在字段中会对序列号进行初始化,初始化为ISN的值。

FIN:该位设置为1表示不再发送数据,就是希望结束连接,通信结束时双方主机交换即可。

4.6校验和

TCP协议可靠的一个原因是当网络层传递的数据出现异常的时候可以有对应的处理方案,比如序列号就是用于解决数据出现丢包、重复、无序的问题,而校验和就是用于解决数据出现损坏的问题。

TCP传输数据段时会包含校验和,接收端收到数据段会进行校验,当校验失败时会丢弃损坏的数据段。

4.7窗口号

窗口号是一个16bit的整数,指的是接收端还愿意接收的数据的字节个数,是TCP协议用于实现流量控制设计的,用于表示接收端剩余空间的值。

5.TCP的通信流程

TCP协议用于实现端对端的可靠通信,如果采用C/S模式可以把双方分为主动连接端(client)和被动连接端(server)。

注意:Linux系统提供了和TCP通信相关的函数接口,这些接口和UDP协议的接口有部分不同,可以阅读man手册的第7章和TCP通信相关的内容,终端输入 man 7 tcp ,如下所示:

可以看到,TCP协议在两个套接字之间提供可靠的、面向字节流的全双工连接,并且TCP可以保证数据按顺序到达以及数据出现丢失时会重新传输丢失的数据,只是发送端(主动连接)和接收端(被动连接)的接口调用不同,如下:

5.1发送端(客户端)

一般把发送端也称为主动连接端,实际情况中作为客户端,客户端首先要调用socket()函数创建TCP套接字,然后调用connect()函数来连接对方的TCP套接字,如果连接成功,则调用send()/write()函数发送数据即可。

5.1.1socket()函数

可以看到,SOCK_STREAM类型的套接字提供有序的、可靠的基于字节流的全双工连接,其实就是指TCP协议,所以需要先调用socket()函数创建TCP套接字,并且类型选择SOCK_STREAM。

5.1.2connect()函数

SOCK_STREAM类型的流式套接字在收发数据之前必须处于连接状态,因为TCP协议是面向连接的,只有连接成功,才可以通过send()函数和recv()函数进行数据收发。

所以发起连接的一方需要调用connect()函数进行连接,但是注意:调用connect()函数连接可能会失败,因为只有对方接收连接请求,才可以建立连接。connect()函数使用规则如下:

5.1.2.1函数参数:

第一个参数:sockfd指的是套接字的文件描述符,其实是socket()函数的返回值,是个整数。

第二个参数:addr指的是目标主机的地址,结构体指针记录了目标主机的IP地址和端口号。

第三个参数:addrlen指的是目标主机的地址结构的大小,以字节为单位,sizeof()计算即可。

5.1.2.2返回结果

connect()函数调用成功则返回0,connect()函数调用失败则返回-1,一般情况下基于连接的套接字只能成功连接一次。

5.1.2.3注意事项

如果调用connect()函数连接目标主机失败,则调用方的套接字状态是未指定的,所以应该关闭套接字,并重新创建一个新的套接字,然后再次连接即可。

5.1.3send()函数

一旦被连接端接受连接,就表示发起连接端调用connect()函数连接成功,则双方可以进行数据收发,此时可以调用send()函数发送数据,函数使用规则如下所示:

5.1.3.1函数参数

第一个参数:sockfd是发送套接字的文件描述符,其实是socket()函数的返回值,是个整数。

第二个参数:buf指的是待发送数据的缓冲区,也就是要发送的数据的存储位置,是个指针。

第三个参数:len指的是待发送数据的大小,是以字节为单位,也就是要发送的数据的长度。

第四个参数:flags指的是发送标志,当flags等于0时,send()函数的效果与write()函数相同。

5.1.3.2返回结果

函数调用成功,则返回实际发送的字节个数,函数调用失败,则返回-1,并且会返回错误码。

5.1.3.3注意事项

send()函数只能用于套接字处于连接的状态,因为已经连接的套接字记录了接收方的地址信息。

另外,如果要发送的数据的不适合套接字的发送缓冲区,则send()函数通常会阻塞,除非套接字设置为非阻塞模式(如果套接字处于非阻塞模式,则send()函数会调用失败)。

注意:发送端发送的数据会暂存在接收端的内核缓冲区中,如果接收端一直不接收数据,则会导致接收缓冲区满,这样发送端调用send()函数继续发送数据时也会阻塞。

5.2接收端(服务器)

一般把接收端称为被动连接端,实际情况中经常作为服务器,服务器首先需要创建TCP套接字,然后调用bind()函数绑定本地地址和端口号到套接字,然后调用listen()函数把TCP套接字设置为监听模式,如果处于监听模式下有客户端发送连接请求,如果打算接受连接请求,则调用accept()进行连接,连接成功后双方就可以调用recv()或者send()函数进行通信。

5.2.1listen()函数

TCP协议是面向连接的,所以双方通信之前必须建立连接(也就是客户端发起连接请求,并且服务器接受请求才算是连接成功),但是对于服务器而言并不清楚何时会收到客户端的连接请求,以及服务器也不清楚有多少个客户端发起了请求,所以就需要把服务器的TCP套接字设置为监听模式,Linux系统提供了名称叫做listen()的函数接口实现该功能,规则如下:

5.2.1.1函数参数

第一个参数:sockfd指的是要被设置为监听状态的套接字,其实就是socket()函数的返回值。

第二个参数:backlog指的是等待接受连接的客户端的最大长度,这个值最大是128,如下:

注意:这个参数指的是等待接受连接的套接字的数量,并不是可以连接的套接字的数量!!!

假设backlog = 4,这就意味着最多有4个等待连接的客户端,并不是能发起连接的客户端数量,只不过如果已经有4个客户端发起了连接,当第5个客户端发起连接之后可能会失败,因为服务器无法响应这么多请求。

5.1.1.2返回结果

listen()函数调用成功则返回0,listen()调用失败则返回-1,注意:listen()函数是不会阻塞的!!!

5.2.2accept()函数

当服务器通过listen()函数把套接字设置为监听状态并且设置好最大等待连接的客户端数量之后,服务器就可以调用accept()函数接受连接请求,函数使用规则如下所示:

5.2.2.1函数参数

第一个参数:sockfd指的是要被设置为监听状态的套接字,其实就是socket()函数的返回值。

第二个参数:addr指的是记录对方主机地址的结构指针,注意是为了记录对方的地址信息。

第三个参数:addrlen指的是对方主机的地址信息长度,以字节为单位,注意必须要初始化。

5.2.2.2返回结果

accept()函数调用成功,会返回一个新的套接字文件描述符,accept()函数调用失败则返回-1。

5.2.2.3注意事项

服务器调用accept()函数会从等待连接的队列中提取第一个客户端进行连接,也就是和第一个发起连接请求的客户端连接,如果此时等待队列中没有等待连接的客户端,则accept()函数会阻塞,直到等待队列中存在客户端请求。

注意:服务器如果和客户端连接成功,accept()函数会返回一个新的套接字描述符,这个新的套接字描述符并不处于监听状态,也就是说服务器和连接成功的客户端需要通过这个新的文件描述符进行数据收发。

5.2.3recv()函数

如果服务器和客户端成功建立连接,则双发可以进行数据收发,此时接收数据需要调用recv()函数实现,使用规则如下:

5.2.3.1函数参数

第一个参数:sockfd指的是进行读写的套接字描述符,注意:应该是accept()函数的返回值!

第二个参数:buf指的是接收到的数据要存储的缓冲区,也就是收到的数据要存在哪个位置!

第三个参数:len指的是要接收的数据的大小,以字节为单位,用户可以自行设置数据大小。

第四个参数:flags指的是接收标志,如果没有特殊需求则可以设置为0,此时和read()类似。

5.2.3.2返回结果

函数调用成功,则返回接收到的字节个数,函数调用失败则返回-1,如果对端已经关闭连接,则返回值为0。另外,如果没有收到数据,则recv()函数会默认阻塞,直到有数据到达。

设计两个程序分别作为客户端和服务器,要求服务器等待客户端连接,如果有客户端发起连接请求,则服务器接受连接,双方建立连接后,则客户端向服务器发送数据,服务器把数据输出到终端。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"  // 服务器IP地址,可修改为实际地址

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};

    // 1. 创建TCP套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket creation error");
        exit(EXIT_FAILURE);
    }

    // 2. 配置服务器地址结构
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 转换服务器IP地址为二进制格式
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
        perror("invalid address/address not supported");
        exit(EXIT_FAILURE);
    }

    // 3. 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connection failed");
        exit(EXIT_FAILURE);
    }

    printf("已成功连接到服务器 %s:%d\n", SERVER_IP, PORT);
    printf("请输入要发送的数据(输入exit结束):\n");

    // 4. 读取用户输入并发送数据
    while (1) {
        // 读取用户输入
        if (fgets(buffer, BUFFER_SIZE, stdin) == NULL) {
            break;
        }

        // 去除换行符
        buffer[strcspn(buffer, "\n")] = '\0';

        // 发送数据到服务器
        send(sock, buffer, strlen(buffer), 0);
        printf("已发送:%s\n", buffer);

        // 检查是否退出
        if (strcmp(buffer, "exit") == 0) {
            break;
        }

        memset(buffer, 0, BUFFER_SIZE);  // 清空缓冲区
    }

    // 5. 关闭套接字
    close(sock);
    printf("连接已关闭\n");
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 1. 创建TCP套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 2. 配置服务器地址结构
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用接口
    address.sin_port = htons(PORT);        // 转换端口为网络字节序

    // 3. 绑定套接字到指定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 4. 监听连接请求,最大等待队列长度为5
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    printf("服务器启动,监听端口 %d...\n", PORT);
    printf("等待客户端连接...\n");

    // 5. 接受客户端连接(阻塞等待)
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept failed");
        exit(EXIT_FAILURE);
    }

    printf("客户端已连接:%s:%d\n", 
           inet_ntoa(address.sin_addr),  // 客户端IP
           ntohs(address.sin_port));     // 客户端端口

    // 6. 接收客户端发送的数据
    ssize_t valread;
    while ((valread = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
        printf("收到客户端数据:%.*s\n", (int)valread, buffer);
        memset(buffer, 0, BUFFER_SIZE);  // 清空缓冲区
    }

    if (valread < 0) {
        perror("read failed");
    } else {
        printf("客户端已断开连接\n");
    }

    // 7. 关闭套接字
    close(new_socket);
    close(server_fd);
    return 0;
}

6.TCP的握手机制

    通过RFC标准可以知道TCP协议是面向连接的,当客户端发送连接请求,并且服务器接受请求,双方才表示成功建立连接,在TCP标准中建立连接采用了一种基于时序的序列号握手机制,如下:

    注意:TCP的三次握手是在客户端调用connect()期间和服务器调用accept()期间才会发生的。

    7.TCP的挥手机制

    RFC标准中对客户端和服务器断开连接的过程进行了总结,可以看到采用的是四次挥手机制。

    断开连接的图中提到了2MSL,MSL 是 Maximum Segment Lifetime,指的是报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

    在 Linux 系统⾥ 2MSL 默认是60秒,也就是说一个MSL也就是 30秒。Linux系统停留在TIME_WAIT 的时间为固定的60秒。

    8.TCP的有限状态机

    9.TCP的数据缓冲区

    对于TCP通信而言,是具有发送缓冲区和接收缓冲区的,发送端具有发送缓冲区,接收端具有接收缓冲区。

    9.1接收缓冲区

    接收缓冲区的最初概念指的是套接字从网络接收到的数据对应的暂存区,尤其当传输的数据是结构化数据时,设定特定大小的缓冲区就特别有用。

    接收缓冲区的大小属于Linux系统套接字文件的属性选项之一,所以想要设置接收缓冲区的大小,则需要通过设置套接字的属性选项实现,Linux系统中提供了两个函数接口来获取和设置套接字的属性选项,分别是getsockopt()和setsockopt(),使用规则如下所示:

    9.1.1函数参数

    可以看到,setsockopt()函数和getsockopt()函数有5个参数,另外,关于套接字选项的描述,需要通过man手册的第7章了解,输入 man 7 socket即可

    第一个参数:sockfd指的是创建的套接字对应的文件描述符,其实是socket()函数的返回值。

    第二个参数:level指的是选项对应的协议级别,一般建议把该参数设置为SOL_SOCKET即可。

    第三个参数:optname指的是选项的名称,比如设置接收缓冲区的宏是SO_RCVBUF,如下:

    第四个参数:optval指的是要设置的选项值,比如要设置接收缓冲区,则设置为某个有效值。

    第五个参数:optlen指的是选项值的长度,一般可以通过sizeof计算对应选项值的长度大小。

    设计一个程序,要求创建一个TCP套接字,并调用getsockopt()函数获取TCP接收缓冲区的默认值然后输出到终端。

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <unistd.h>
    
    int main() {
        int sockfd;
        int recv_buf_size;
        socklen_t optlen = sizeof(recv_buf_size);
    
        // 1. 创建TCP套接字
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("创建TCP套接字失败");
            exit(EXIT_FAILURE);
        }
        printf("TCP套接字创建成功,描述符: %d\n", sockfd);
    
        // 2. 使用getsockopt()获取TCP接收缓冲区默认值
        // 选项级别:SOL_SOCKET,选项名:SO_RCVBUF
        if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_buf_size, &optlen) < 0) {
            perror("获取接收缓冲区大小失败");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
    
        // 3. 输出结果
        printf("TCP接收缓冲区默认大小: %d 字节\n", recv_buf_size);
    
        // 4. 关闭套接字
        close(sockfd);
        return 0;
    }
        

    通过键盘输入一个整数,把这个整数通过setsockopt()设置为接收缓冲区的大小,然后再次调用getsockopt()函数获取TCP接收缓冲区的值并输出到终端,然后观察效果。

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <unistd.h>
    
    int main() {
        int sockfd;
        int buf_size;          // 用于存储缓冲区大小
        socklen_t opt_len = sizeof(buf_size);  // 选项值的长度
    
        // 1. 创建TCP套接字
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("创建套接字失败");
            exit(EXIT_FAILURE);
        }
        printf("TCP套接字创建成功(描述符:%d)\n", sockfd);
    
        // 2. 获取并输出初始接收缓冲区大小
        if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &buf_size, &opt_len) < 0) {
            perror("获取初始缓冲区大小失败");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
        printf("初始TCP接收缓冲区大小:%d 字节\n", buf_size);
    
        // 3. 从键盘输入要设置的缓冲区大小
        printf("请输入要设置的接收缓冲区大小(字节):");
        if (scanf("%d", &buf_size) != 1) {
            fprintf(stderr, "输入错误:请输入有效的整数\n");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
    
        // 4. 使用setsockopt设置接收缓冲区大小
        if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size)) < 0) {
            perror("设置缓冲区大小失败");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
        printf("已尝试设置接收缓冲区大小为:%d 字节\n", buf_size);
    
        // 5. 再次获取缓冲区大小,验证设置结果
        if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &buf_size, &opt_len) < 0) {
            perror("获取设置后的缓冲区大小失败");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
        printf("设置后实际的TCP接收缓冲区大小:%d 字节\n", buf_size);
    
        // 6. 关闭套接字
        close(sockfd);
        return 0;
    }
        

    可以发现,在Linux下TCP的接收缓冲区大小一般介于 2304-425984 之间,不同主机的检测结果可能有所不同。

    注意:如果打算设置TCP接收缓冲区大小,应该在调用listen()函数之前进行设置才会生效!!!

    提示:修改TCP的接收缓冲区的目的是可以让通信双方收发固定大小的有效的结构化数据块。

    思考:把TCP的接收缓冲区设置为某个固定值,正常情况下发送端应该发送这么多数据时接收端才能接收到,但是实际上发送端发送1个字节,接收端也可以正常接收,请问是为什么?

    回答:可以把TCP接收缓冲区理解为一个水池,原则上只有等水池的水满了(接收的数据填满了缓冲区),应用层才能读取到数据,但是为了更灵活,TCP增加了一个叫水位线的概念。

    并且规定:当接收数据量超过水位线时,就触发套接字的读就绪状态,这样应用层调用 recv()/read() 函数可以正常读取数据。

    同样,水位线可以通过setsockopt()函数进行设置,只不过选项名称发生变化,可以通过man手册的第7章阅读socket进行了解,如下:

    可以看到,Linux系统中接收缓冲区和发送缓冲区的最小字节数都被初始化为1,并且Linux系统的发送缓冲区的最小字节数不可以被修改,只有接收缓冲区在内核2.4版本允许被修改。

    9.2发送缓冲区

    TCP的发送缓冲区用来为数据的丢失重发做准备,在Linux下TCP的发送缓冲区大小一般介于 4608-425984 之间,不同主机的检测结果可能有所不同。发送缓冲区的基本作用,是当接收方发现数据丢失要求发送方重发时,发送方有备份数据可以重新发送。

    10.TCP的OOB带外数据

    思考:刚才学习到通过设置TCP的接收缓冲区的大小和TCP接收缓冲的最小字节数,可以让接收端每次读取一块完整的结构化数据,但是如果接收端设置了较大的缓冲区和较高的水位线,由于接收的数据量必须要达到水位线才能使套接字读就绪,因此在接收较少数据时,发送方发出的数据就会滞留在接收方的缓冲区中,如果此时恰好有一些比较紧急的数据,那这些数据就会被被迫滞留在缓冲区中无法被接收端读取,造成逻辑异常,应该如何解决?

    回答:可以将紧急数据设定为带外(Out of Band)数据,通过特殊的标志位,让数据到达接收方后可以不受缓冲区和水位线的限制,让接收方可以优先读取,在man手册第7章关于TCP协议的描述中有详细说明:

    注意:OOB带外数据每次只能发送一个字节,但是可以发送多次,由于recv()函数和send()函数的第4个参数才可以指定MSG_OOB标志,所以紧急带外数据只能通过这两个函数进行收发。

    注意:带外数据也是通过内核缓冲区发送出去,所以接收端接收到带外数据之后也需要从内核缓冲区中读取出来,但是由于带外数据属于紧急情况下为了防止阻塞和水位线的限制提供的一种数据发送模式,所以发送端每次发送带外数据时只能发送一个字节,并且接收端需要对SIGURG信号进行注册,但是由于信号触发之后会中断程序执行,应该确保信号响应接口只是完成接收动作,应该确保响应函数立即结束。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值