《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数
《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数
- 《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数
- send & recv 函数
- Linux 平台下的 send 和 recv 函数
- MSG_OOB:发送紧急消息
- 紧急模式的工作原理
- 检查输入缓冲
- readv & writev 函数
- 基于 Windows 的实现
- 基于 Windows 的紧急消息处理程序
- 基于 Windows 的输入缓冲检查程序
- 习题
- (1)下面关于MSG_OOB可选项的说法错误的是?
- (2)利用readv&writev函数收发数据有何优点?分别从函数调用次数和I/O缓冲的角度给出说明。
- (3)通过recv函数见证输入缓冲是否存在数据时(确认后立即返回),如何设置recv函数最后一个参数中的可选项?分别说明各可选项的含义。
- (4)可在Linux平台通过注册时间处理函数接收MSG_OOB数据。那Windows中如何接受?请说明接收方法。
《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数
send & recv 函数
Linux 平台下的 send 和 recv 函数
#include <sys/socket.h>
ssize_t recv(int sockfd, const void *buf, size_t nbytes, int flags);
成功时返回发送的字节数,失败时返回 -1。
参数:
- sockfd:表示与数据传输对象的连接的套接字文件描述符
- buf:保存待传输数据的缓冲地址值
- nbytes:待传输字节数
- flags:传输数据时指定的可选项信息
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
成功时返回接收的字节数(收到 EOF 返回 0),失败时返回 -1。
参数:
- sockfd:表示与数据接收对象的连接的套接字文件描述符
- buf:保存接收数据的缓冲地址值
- nbytes:可接收最大字节数
- flags:接收数据时指定的可选项信息
send函数和recv函数的最后一个参数是收发数据时的可选项,该可选项可利用位(bit)或运算符同时传递多个信息,通过下表整理可选项的种类及含义。
可选项(Option) | 含义 | send | recv |
---|---|---|---|
MSG_OOB | 用于传输带外数据(Out-of-band data) | O | O |
MSG_PEEK | 验证输入缓冲中是否存在接受的数据 | X | O |
MSG_DONTROUTE | 数据传输过程中不参照本地路由(Routing)表,在本地(Local)网络中寻找目的地 | O | X |
MSG_DONTWAIT | 调用 I/O 函数时不阻塞,用于使用非阻塞(Non-blocking)I/O | O | O |
MSG_WAITALL | 防止函数返回,直到接收到全部请求的字节数 | X | O |
不同操作系统对上述可选项的支持也不同。因此,为了使用不同可选项,需对实际开发采用的操作系统有一定了解。
MSG_OOB:发送紧急消息
MSG_OOB是特定于TCP的一个选项,它用于在TCP连接中发送紧急或带外数据(out-of-band data),通常用于中断正常的数据流,它允许发送端发送一些紧急信息,这些信息会绕过正常的数据队列,直接发送给接收端,而接收端也可以优先处理这些带外数据。
下面示例将通过MSG_OOB可选项收发数据。
oob_send.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define BUF_SIZE 30
void error_handling(char *message);int main(int argc, char *argv[])
{int sock;struct sockaddr_in recv_adr;if (argc != 3){printf("Usage : %s <IP> <port>\n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);memset(&recv_adr, 0, sizeof(recv_adr));recv_adr.sin_family = AF_INET;recv_adr.sin_addr.s_addr = inet_addr(argv[1]);recv_adr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)error_handling("connect() error!");write(sock, "123", strlen("123"));send(sock, "4", strlen("4"), MSG_OOB);write(sock, "567", strlen("567"));send(sock, "890", strlen("890"), MSG_OOB);close(sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
从上面示例可以看出,紧急消息的传输比即将介绍的接收过程要简单,只需在调用send函数时指定MSG_OOB可选项,接收紧急消息的过程要相对复杂一点
oob_recv.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>#define BUF_SIZE 30
void error_handling(char *message);
void urg_handler(int signo);int acpt_sock;
int recv_sock;int main(int argc, char *argv[])
{struct sockaddr_in recv_adr, serv_adr;int str_len, state;socklen_t serv_adr_sz;struct sigaction act;char buf[BUF_SIZE];if (argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}act.sa_handler = urg_handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;acpt_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&recv_adr, 0, sizeof(recv_adr));recv_adr.sin_family = AF_INET;recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);recv_adr.sin_port = htons(atoi(argv[1]));if (bind(acpt_sock, (struct sockaddr *)&recv_adr, sizeof(recv_adr)) == -1)error_handling("bind() error");listen(acpt_sock, 5);serv_adr_sz = sizeof(serv_adr);recv_sock = accept(acpt_sock, (struct sockaddr *)&serv_adr, &serv_adr_sz);fcntl(recv_sock, F_SETOWN, getpid());state = sigaction(SIGURG, &act, 0);while ((str_len = recv(recv_sock, buf, sizeof(buf), 0)) != 0){if (str_len == -1)continue;buf[str_len] = 0;puts(buf);}close(recv_sock);close(acpt_sock);return 0;
}void urg_handler(int signo)
{int str_len;char buf[BUF_SIZE];str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_OOB);buf[str_len] = 0;printf("Urgent message: %s \n", buf);
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
上述示例中插入了未曾讲解的fcntl函数调用语句,关于此函数只讲解必要部分,后面还会再讲解此函数
fcntl(recv_sock, F_SETOWN, getpid());
fntcl函数用于控制文件描述符,但上述调用语句的含义为:将文件描述符recv_sock指向的套接字拥有者(F_SETOWN)改为把getpid函数返回值用作ID进程。
可能大家对“套接字拥有者”的概念有些生疏,操作系统实际上创建管理套接字,所以从严格意义上说,操作系统才是套接字的拥有者,只是此处所谓的“拥有者”是指套接字所有事物的主体,
上述描述可简要概括为:文件描述符recv_sock指向的套接字引发的SIGURG信号处理进程变为将getpid函数返回值用作ID的进程。
当然,上述描述中的“处理SIGURG信号”指的是“调用SIGURG信号处理函数”。但之前讲过,多进程可以共同拥有一个套接字描述符。例如,通过调用fork函数创建子进程并同时复制文件描述符。此时如果发生SIGURG信号,应该调用哪个进程的信号处理函数呢?可以肯定的是,不会调用所有进程的信号处理函数。因此,处理SIGURG信号时必须指定处理信号的进程,而getpid函数返回调用此函数的进程ID。上述调用语句指定当前进程为处理SIGURG信号的主体。该程序只创建一个进程,因此,理应由该进程处理SIGURG信号。
编译oob_recv.c并运行:
# gcc oob_recv.c -o oob_recv
# ./oob_recv 8500
123
Urgent message: 4
567
Urgent message: 0
89
编译oob_send.c并运行:
# gcc oob_send.c -o oob_send
# ./oob_send 127.0.0.1 8500
从运行结果可以看出,send 是客户端,recv 是服务端,客户端给服务端发送消息,服务端接收完消息之后显示出来。
注意:每次运行的效果,并不是一样的。
输出结果出乎意料:通过 MSG_OOB 可选项传递数据时只返回 1 个字节,而且也不快。
的确,通过 MSG_OOB 并不会加快传输速度,而通过信号处理函数 urg_handler 也只能读取一个字节。剩余数据只能通过未设置 MSG_OOB 可选项的普通输入函数读取。这是因为 TCP 不存在真正意义上的「外带数据」。实际上,MSG_OOB 中的 OOB 指的是 Out-of-band ,而「外带数据」的含义是:通过完全不同的通信路径传输的数据。
真正意义上的 Out-of-band 需要通过单独的通信路径高速传输数据,但是 TCP 不另外提供,只利用 TCP 的紧急模式(Urgent mode)进行传输。
紧急模式的工作原理
MSG_OOB 的真正意义在于督促数据接收对象尽快处理数据。这是紧急模式的全部内容,而 TCP 「保持传输顺序」的传输特性依然成立。TCP 的紧急消息无法保证及时到达,但是可以要求急救。下面是 MSG_OOB 可选项状态下的数据传输过程:
上图给出的是示例oob_send.c的第32行中调用如下函数后的输出缓冲状态,此处假设已传输之前的数据。
send(sock, "890", strlen("890"), MSG_OOB);
如果将缓冲最左端的位置视作偏移量为0,字符0保存于偏移量为2的位置。另外,字符0右侧偏移量为3的位置存有紧急指针。紧急指针指向紧急消息的下一个位置(偏移量加1),同时向对方主机传递消息:紧急指针指向的偏移量为3之前的部分就是紧急消息。
也就是说,实际只用一个字节表示紧急消息。这一点可以通过下图中用于传输数据的TCP数据包(段)的结构看的更清楚。
TCP数据包实际包含很多信息,图1-2只标注了与我们主题相关的内容,TCP头含有如下两种信息:
- URG=1:载有紧急消息的数据包
- URG指针:紧急指针位于偏移量为3的位置
指定MSG_OOB选项的数据包本身就是紧急数据包,并通过紧急指针表示紧急消息所在位置,但无法得知紧急消息是字符串890?还是90?亦或是单个0?但这并不重要,如前所述,除紧急指针的前面一个字节外,数据接收方将通过调用常用输入函数读取剩余部分。换言之,紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息。
检查输入缓冲
同时设置MSG_PEEK选项和MSG_DONTWAIT选项,以验证输入缓冲中是否存在接收的数据。设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除。因此,该选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读数据存在与否的函数。
peek_send.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void error_handling(char *message);int main(int argc, char *argv[])
{int sock;struct sockaddr_in send_adr;if (argc != 3){printf("Usage : %s <IP> <port>\n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);memset(&send_adr, 0, sizeof(send_adr));send_adr.sin_family = AF_INET;send_adr.sin_addr.s_addr = inet_addr(argv[1]);send_adr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr *)&send_adr, sizeof(send_adr)) == -1)error_handling("connect() error!");write(sock, "123", strlen("123"));close(sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
发起连接请求,然后发送字符串“123”。
peek_recv.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define BUF_SIZE 30
void error_handling(char *message);int main(int argc, char *argv[])
{int acpt_sock, recv_sock;struct sockaddr_in acpt_adr, recv_adr;int str_len, state;socklen_t recv_adr_sz;char buf[BUF_SIZE];if (argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}acpt_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&acpt_adr, 0, sizeof(acpt_adr));acpt_adr.sin_family = AF_INET;acpt_adr.sin_addr.s_addr = htonl(INADDR_ANY);acpt_adr.sin_port = htons(atoi(argv[1]));if (bind(acpt_sock, (struct sockaddr *)&acpt_adr, sizeof(acpt_adr)) == -1)error_handling("bind() error");listen(acpt_sock, 5);recv_adr_sz = sizeof(recv_adr);recv_sock = accept(acpt_sock, (struct sockaddr *)&recv_adr, &recv_adr_sz);while (1){str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_PEEK | MSG_DONTWAIT);if (str_len > 0)break;}buf[str_len] = 0;printf("Buffering %d bytes: %s \n", str_len, buf);str_len = recv(recv_sock, buf, sizeof(buf) - 1, 0);buf[str_len] = 0;printf("Read again: %s \n", buf);close(acpt_sock);close(recv_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
程序调用recv函数的同时传递MSG_PEEK可选项,这是为了保证即使不存在待读取数据也不会进入阻塞状态。再次调用recv函数,这次并未设置任何可选项。因此,本次读取的数据将从输入缓冲中删除。
编译peek_recv.c并运行:
# gcc peek_recv.c -o peek_recv
# ./peek_recv 8500
Buffering 3 bytes: 123
Read again: 123
编译peek_send.c并运行:
# gcc peek_send.c -o peek_send
# ./peek_send 127.0.0.1 8500
通过运行结果可以验证,仅发送一次的数据被读取两次,因为第一次调用recv函数时设置了MSG_PEEK选项,以上就是MSG_PEEK可选项的功能。
readv & writev 函数
readv和writev函数的功能可概括为:对数据进行整合传输及发送的函数。也就是说,通过writev函数可以将分散保存在多个缓冲的数据一并发送,通过readv函数可以由多个缓冲分别接收。因此,适当使用这两个函数可以减少I/O函数的调用次数。
#include <sys/uio.h>ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
成功时返回发送的字节数,失败时返回 -1。
参数:
- filedes:表示数据传输对象的套接字文件描述符,但该函数并不只限于套接字,因此,可以像read函数一样向其传递文件或标准输出描述符
- iov:iovec结构体数组的地址值,结构体iovec中包含待发送数据的位置和大小信息
- iovcnt:向第二个参数传递的数组长度
上述函数的第二个参数中出现的数组iovec结构体的声明如下:
struct iovec
{void *iov_base; // 缓冲地址size_t iov_len; // 缓冲大小
};
可以看到,结构体iovec由保存待发送数据的缓冲(char型数组)地址值和实际发送的数据长度信息构成。给出上述函数的调用示例前,先通过下图了解该函数的使用方法。
writev的第一个参数1是文件描述符,因此向控制台输出数据,ptr是存有待发送数据信息的iovec数组指针。第三个参数为2,因此,从ptr指向的地址开始,共浏览两个iovec结构体变量,发送这些指针指向的缓冲数据。
示例程序:
#include <stdio.h>
#include <sys/uio.h>int main(int argc, char *argv[])
{struct iovec vec[2];char buf1[] = "ABCDEFG";char buf2[] = "1234567";int str_len;vec[0].iov_base = buf1;vec[0].iov_len = 3;vec[1].iov_base = buf2;vec[1].iov_len = 4;str_len = writev(1, vec, 2);puts("");printf("Write bytes: %d \n", str_len);return 0;
}
编译writev.c并运行:
# gcc writev.c -o writev
# ./writev
ABC1234
Write bytes: 7
下面介绍readv函数,它与writev函数正好相反。
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
成功时返回接收的字节数,失败时返回-1。
参数:
- filedes:传递接收数据的文件(或套接字)描述符
- iov:包含数据保存位置和大小信息的iovec结构体数组的地址值
- iovcnt:第二个参数中数组的长度
示例程序:
#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100int main(int argc, char *argv[])
{struct iovec vec[2];char buf1[BUF_SIZE] = {0,};char buf2[BUF_SIZE] = {0,};int str_len;vec[0].iov_base = buf1;vec[0].iov_len = 5;vec[1].iov_base = buf2;vec[1].iov_len = BUF_SIZE;// readv函数的第一个参数为0,因此从标准输入接收数据str_len = readv(0, vec, 2);printf("Read bytes: %d \n", str_len);printf("First message: %s \n", buf1);printf("Second message: %s \n", buf2);return 0;
}
编译readv.c并运行:
# gcc readv.c -o readv
# ./readv
I like TCP/IP socket programming
Read bytes: 33
First message: I lik
Second message: e TCP/IP socket programming
由运行结果可知,通过第7行声明的vec数组保存了数据。
哪种情况适合使用readv和writev函数?实际上,能使用该函数的所有情况都适用。例如:需要传输的数据分别位于不同缓冲(数组)时,需要多次调用write函数,此时可以通过一次writev函数调用来提高效率。同样,需要将输入缓冲中的数据读入不同位置时,可以不必多次调用read函数,而是利用一次readv函数就能大大提高效率。
即使从C语言角度来看,减少函数调用次数也能相应提高性能。但其更大的意义在于减少数据包个数,假设为了提高效率而在服务端明确禁止了Nagle算法,其实writev函数在不采用Nagle算法时更有价值。
上述示例中待发送的数据分别存在三个不同的地方,此时如果使用write函数则需要三次函数调用。但若为提高速度而关闭了Nagle算法,则极有可能通过三个数据包传递数据。反之,若使用writev函数将所有数据一次性写入输出缓冲,则很有可能仅通过一个数据包传输数据。所以writev函数和readv函数非常有用。
再考虑一种情况:将不同位置的数据按照发送顺序移动(复制)到一个大数组,并通过一次write函数调用进行传输。这种方式是否与调用writev函数的效果相同?当然!但使用writev函数更为便利。
因此,如果遇到writev函数和readv函数的适用情况,请一定要优先考虑writev和readv函数。
基于 Windows 的实现
基于 Windows 的紧急消息处理程序
Windows 并不存在 Linux 那样的信号处理机制,也就是没有 sigaction 函数之类的。在 Windows 中无法完成对 MSG_OOB 可选项的事件处理,我们通过 select 函数解决这一问题。
之前讲过,select 函数的 3 种监视对象:
- 是否存在套接字接收数据?
- 无需阻塞传输数据的套接字有哪些?
- 哪些套接字发生了异常?
异常是不同寻常的程序执行流,因此,收到 Out-of-band 数据也属于异常,可以利用这一特性接收紧急消息。
oob_send_win.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET sock;SOCKADDR_IN serverAddr;if (argc != 3){printf("Usage: %s <IP> <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == INVALID_SOCKET)ErrorHanding("socket() error!");memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = inet_addr(argv[1]);serverAddr.sin_port = htons(atoi(argv[2]));if (connect(sock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)ErrorHanding("connect() error!");send(sock, "123", 3, 0);send(sock, "4", 1, MSG_OOB);send(sock, "567", 3, 0);send(sock, "890", 3, MSG_OOB);closesocket(sock);WSACleanup();return 0;
}
oob_recv_win.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 30void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET serverSock, clientSock;SOCKADDR_IN serverAddr, clientAddr;int clientAddrSize;char message[BUF_SIZE];int strLen;TIMEVAL timeout;fd_set read, readCopy, except, exceptCopy;int result;if (argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");serverSock = socket(PF_INET, SOCK_STREAM, 0);if (serverSock == INVALID_SOCKET)ErrorHanding("socket() error!");memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);serverAddr.sin_port = htons(atoi(argv[1]));if (bind(serverSock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)ErrorHanding("bind() error!");if (listen(serverSock, 5) == SOCKET_ERROR)ErrorHanding("listen() error!");clientAddrSize = sizeof(clientAddr);clientSock = accept(serverSock, (SOCKADDR *)&clientAddr, &clientAddrSize);if (clientSock == INVALID_SOCKET)ErrorHanding("accept() error!");FD_ZERO(&read);FD_ZERO(&except);FD_SET(clientSock, &read); // 注册服务器端套接字文件描述符FD_SET(clientSock, &except);while (1){readCopy = read;exceptCopy = except;timeout.tv_sec = 5, timeout.tv_usec = 0;if ((result = select(0, &readCopy, 0, &exceptCopy, &timeout)) == SOCKET_ERROR)break;if (result == 0)continue;if (FD_ISSET(clientSock, &exceptCopy)){strLen = recv(clientSock, message, BUF_SIZE - 1, MSG_OOB);message[strLen] = '\0';printf("Urgent message: %s\n", message);}if (FD_ISSET(clientSock, &readCopy)){strLen = recv(clientSock, message, BUF_SIZE - 1, 0);if (strLen == 0){closesocket(clientSock);break;}else{message[strLen] = '\0';puts(message);}}}closesocket(serverSock);WSACleanup();return 0;
}
编译:
gcc oob_recv_win.c -lwsock32 -o oobServgcc oob_send_win.c -lwsock32 -o oobClnt
运行结果(服务器端):
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 13>oobServ 9190
Urgent message: 40
12356789
基于 Windows 的输入缓冲检查程序
Windows 没有 MSG_DONTWAIT,但似乎没有影响。
peek_send_win.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET sock;SOCKADDR_IN serverAddr;if (argc != 3){printf("Usage: %s <IP> <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == INVALID_SOCKET)ErrorHanding("socket() error!");memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = inet_addr(argv[1]);serverAddr.sin_port = htons(atoi(argv[2]));if (connect(sock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)ErrorHanding("connect() error!");send(sock, "123", 3, 0);closesocket(sock);WSACleanup();return 0;
}
peek_recv_win.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 30void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET serverSock, clientSock;SOCKADDR_IN serverAddr, clientAddr;int clientAddrSize;char message[BUF_SIZE];int strLen;if (argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");serverSock = socket(PF_INET, SOCK_STREAM, 0);if (serverSock == INVALID_SOCKET)ErrorHanding("socket() error!");memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);serverAddr.sin_port = htons(atoi(argv[1]));if (bind(serverSock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)ErrorHanding("bind() error!");if (listen(serverSock, 5) == SOCKET_ERROR)ErrorHanding("listen() error!");clientAddrSize = sizeof(clientAddr);clientSock = accept(serverSock, (SOCKADDR *)&clientAddr, &clientAddrSize);while (1){strLen = recv(clientSock, message, BUF_SIZE - 1, MSG_PEEK);if (strLen > 0)break;}message[strLen] = '\0';printf("Buffering %d bytes: %s\n", strLen, message);strLen = recv(clientSock, message, BUF_SIZE - 1, 0);message[strLen] = '\0';printf("Read again: %s\n", message);closesocket(clientSock);closesocket(serverSock);WSACleanup();return 0;
}
编译:
gcc peek_recv_win.c -lwsock32 -o peekServgcc peek_send_win.c -lwsock32 -o peekClnt
运行结果(服务器端):
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 13>peekServ 9190
Buffering 3 bytes: 123
Read again: 123
习题
(1)下面关于MSG_OOB可选项的说法错误的是?
a. MSG_OOB指传输Out-of-band数据,是通过其他路径高速传输数据。
b. MSG_OOB指通过其他路径高速传输数据,因此,TCP中设置该选项的数据先到达对方主机。
c. 设置MSG_OOB使数据先到达对方主机后,以普通数据的形式和顺序读取。也就是说,只是提高了传输速度,接收方无法识别这一点。
d. MSG_OOB无法脱离TCP的默认数据传输方式。即使设置了MSG_OOB,也会保持原有传输顺序。该选项只用于要求接收方紧急处理。
答:b、c。
(2)利用readv&writev函数收发数据有何优点?分别从函数调用次数和I/O缓冲的角度给出说明。
readv和writev函数的功能可概括为:对数据进行整合传输及发送的函数。也就是说,通过writev函数可以将分散保存在多个缓冲的数据一并发送,通过readv函数可以由多个缓冲分别接收。因此,适当使用这两个函数可以减少I/O函数的调用次数。
(3)通过recv函数见证输入缓冲是否存在数据时(确认后立即返回),如何设置recv函数最后一个参数中的可选项?分别说明各可选项的含义。
同时设置MSG_PEEK选项和MSG_DONTWAIT选项,以验证输入缓冲是否存在可接收的数据。设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲数据也不会删除。因此,该选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读数据存在与否的函数。
(4)可在Linux平台通过注册时间处理函数接收MSG_OOB数据。那Windows中如何接受?请说明接收方法。
MSG_OOB数据的接收,在select函数中属于异常数据,既在Windows中可以通过异常处理来接收Out-of-band数据。
相关文章:
《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数
《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数 《TCP/IP网络编程》学习笔记 | Chapter 13:多种 I/O 函数send & recv 函数Linux 平台下的 send 和 recv 函数MSG_OOB:发送紧急消息紧急模式的工作原理检查输入缓冲 readv & w…...
详细介绍下oracle冷备(coolbackup)
冷备,也就说数据库不是运行(热的状态)的备份。有些时候我们的数据库比较小,进行同操作系统数据迁移和恢复的时候就比较好用。下面我们详细介绍下oracle数据库的冷备(我们使用最简单的拷贝数据文件方式进行冷备…...
MYSQL——多表设计以及数据库中三种关系模型
大致介绍数据库中三种关系模型 一对多(1:N) 定义: 一个实体可以与另一个实体的多个实例相关联,而后者只能与前者的一个实例相关联。 例子: 学生和课程的关系。 学生(1):每个学生…...
泷羽sec学习打卡-html基础
声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 关于云技术基础的那些事儿-捕获帅照 html基础什么是html? 常用的html标签html示例 css基础什…...
国标GB28181摄像机接入EasyGBS国标GB28181设备管理软件:GB28181-2022媒体传输协议解析
随着信息技术的飞速发展,视频监控领域正经历从传统安防向智能化、网络化安防的深刻转变。在这一转变过程中,国标GB28181设备管理软件EasyGBS成为了这场技术变革的重要一环。 GB28181-2022媒体传输协议 媒体传输命令包括实时视音频点播、历史视音频回放/…...
鸿蒙网络编程系列50-仓颉版TCP回声服务器示例
1. TCP服务端简介 TCP服务端是基于TCP协议构建的一种网络服务模式,它为HTTP(超文本传输协议)、SMTP(简单邮件传输协议)等高层协议的应用程序提供了可靠的底层支持。在TCP服务端中,服务器启动后会监听一个或…...
JMeter监听器与压测监控之 InfluxDB
1. 简介 在本文中,我们将介绍如何在 Kali Linux 上通过 Docker 安装 InfluxDB,并使用 JMeter 对其进行性能监控。InfluxDB 是一个高性能的时序数据库,而 JMeter 是一个开源的性能测试工具,可以用于对各种服务进行负载测试和性能监…...
混合上下文学习 ;In-Context Learning(ICL)
目录 In-Context Learning(ICL) 混合上下文学习 核心内容 核心创新点的原理与理论 举例说明 In-Context Learning(ICL) 是一种在大语言模型(LLM)中使用的技术,它允许模型通过提供一组输入输出示例(即“demonstrations”)来适应新任务,而无需对模型参数进行显…...
【STM32】软件I2C读写MPU6050
文章目录 软件I2C读写MPU6050接线图代码整体框架MyI2C模块MyI2C.cMyI2C.h MPU6050模块MPU6050.cMPU6050_Reg.h MPU6050.h main.c 源程序 软件I2C读写MPU6050 要实现软件I2C读写MPU6050分为两个部分: 完成软件I2C协议时序基于I2C协议读写寄存器操控MPU6050 接线图…...
HarmonyOS鸿蒙系统上File文件常用操作
HarmonyOS鸿蒙系统上,file文件常用操作记录 1.创建文件 createFile(fileName: string, content: string): string {// 获取应用文件路径let context getContext(this) as common.UIAbilityContext;let filesDirPath context.filesDir / fileName;// 新建并打开…...
如何解决Java EasyExcel 导出报内存溢出
如何解决Java EasyExcel 导出报内存溢出 EasyExcel大数据量导出常见方法 1. 分批写入 EasyExcel支持分批写入数据,可以将数据分批加载到内存中,分批写入Excel文件,避免一次性将大量数据加载到内存中。 示例代码: String fileNa…...
[产品管理-91]:产品经理的企业运营的全局思维-1
目录 前言:企业架构图 产品经理的企业运营全局思维 1、用户 - 用户价值与体验:真正的需求,真正的问题,一切的原点 2、大势 - 顺应宏观大势:政策趋势、行业趋势、技术趋势 3、市场 - 知己知彼:市场调研…...
学习笔记——stm32看门狗
目录 一、WDG简介 二、IWDG框图 2.1独立看门狗结构 2.2键寄存器 2.3超时时间 三、WWDG框图 3.1窗口看门狗结构 3.2WWDG时序图 3.3最早、最晚时间 四、IWDG和WWDG对比 五、IWDG相关库函数和应用 5.1相关库函数 5.2应用 六、WWDG相关库函数和应用 6.1相关库函数 6…...
2411rust,cargo清理缓存
原文 Cargo最近在晚间通道上取得了一个不稳定的功能(从nightly-2023-11-17开始),它可自动清理Cargo主目录中的缓存内容. 总之,请求使用晚间通道的人启用此功能,并在Cargo问题跟踪器上报告问题.要启用它,请在你的一般在~/.cargo/config.toml或%USERPROFILE%\.cargo\config.tom…...
高级java每日一道面试题-2024年11月19日-基本篇-获取一个类Class对象的方式有哪些?
如果有遗漏,评论区告诉我进行补充 面试官: 获取一个类Class对象的方式有哪些? 我回答: 在 Java 中,获取一个类的 Class 对象有多种方式。这些方式各有优缺点,适用于不同的场景。以下是常见的几种方法及其详细解释: 1. 使用 new 关键字实…...
Vue 3与TypeScript集成指南:构建类型安全的前端应用
在Vue 3中使用TypeScript,可以让你的组件更加健壮和易于维护。以下是使用TypeScript与Vue 3结合的详细步骤和知识点: 1. 环境搭建 首先,确保你安装了Node.js(推荐使用最新的LTS版本)和npm或Yarn。然后,安…...
可视化建模与UML《活动图实验报告》
你当像鸟飞往你的山。 一、实验目的: 1、熟悉活动图的基本功能和使用方法。 2、掌握使用建模工具软件绘制协作图的方法 二、实验环境: window7 | 10 | 11 EA15 三、实验内容: <1>绘制学生选课系统中添加课程(Add Course)用例的活动图…...
接雨水
接雨水 1、 题目描述2、解题思路 1、 题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 2、解题思路 本题使用了双指针,根据下图可以得出,下标 i 处能接的雨水量由左边…...
i春秋-签到题
练习平台地址 竞赛中心 题目描述 题目内容 点击GUESS后会有辨识细菌的选择题 全部完成后会有弹窗提示 输入nickname后提示获得flag F12检查 元素中没有发现信息 检查后发现flag在控制台中 flag flag{663a5c95-3050-4c3a-bb6e-bc4f2fb6c32e} 注意事项 flag不一定要在元素中找&a…...
Selenium + 数据驱动测试:从入门到实战!
引言 在软件测试中,测试数据的多样性和灵活性对测试覆盖率至关重要。而数据驱动测试(Data-Driven Testing)通过将测试逻辑与数据分离,极大地提高了测试用例的可维护性和可扩展性。本文将结合Selenium这一流行的测试工具࿰…...
考研倒计时30天丨和西电一起向前!再向前!
上岸后 就能来 西安电子科技大学 和学长学姐一起吃饭,XDU食堂都有你想要的一切 ①、海棠篇:海棠新生初入校园,还在犹豫吃什么?宿舍楼边的海棠餐厅物美价廉,满足你一日三餐的需求与期盼! 俗话说:…...
json数组写入文件每行一条数据
我们知道将json或json数组写入文件,一般是用JSON.stringify先将json或json数组转成字符串,再写入文件。JSON.stringify()的语法是这样的:JSON.stringify(value[, replacer[, space]]) JSON.stringify(value)调用则不带空格tab换行等分隔符&am…...
EasyExcel并行导出多个excel文件并压缩下载
EasyExcel并行导出多个excel文件并压缩下载 在SpringBoot应用中,采用同步方式导出Excel文件会导致服务器在生成文件期间阻塞,特别是在处理大量数据时,这种效率较低的方法会严重影响性能。为了解决这个问题,可以采用以下改进措施:首先将导出的数据进行拆分,然后利用Compl…...
hadoop3.x 新特性
hadoop3.x 新特性 FeaturesHadoop 2.xHadoop 3.xMinimum Required Java VersionJDK 6 and above.JDK 8 is the minimum runtime version of JAVA required to run Hadoop 3.x as many dependency library files have been used from JDK 8.Fault ToleranceFault Tolerance is …...
单片机智能家居火灾环境安全检测-分享
目录 前言 一、本设计主要实现哪些很“开门”功能? 二、电路设计原理图 电路图采用Altium Designer进行设计: 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 传统的火灾报警系统大多依赖于简单的烟雾探测器或温度传感器,…...
241117学习日志——[CSDIY] [ByteDance] 后端训练营 [05]
CSDIY:这是一个非科班学生的努力之路,从今天开始这个系列会长期更新,(最好做到日更),我会慢慢把自己目前对CS的努力逐一上传,帮助那些和我一样有着梦想的玩家取得胜利!!&…...
Oracle 19C 安装RAC磁盘投票失败
ORACLE 19C 安装RAC第二个节点报错,没有找到足够的 voting 文件(投票磁盘) 1、磁盘投票失败分析 1.1、02节点报错日志 CRS-4123: Starting Oracle High Availability Services-managed resources CRS-2672: Attempting to start ora.mdnsd…...
vulfocus在线靶场:骑士cms_cve_2020_35339:latest 速通手册
目录 一、启动环境,访问页面,ip:端口号/index.php?madmin,进入后台管理页面,账号密码都是adminadmin 二、进入之后,根据图片所示,地址后追加一下代码,保存修改 三、新开标签页访问:①ip:端…...
【Linux内核深度解析】TCP协议栈之tcp_recvmsg
tcp_recvmsg 是 Linux 内核中用于处理 TCP 套接字接收数据的核心函数。它的主要任务是从接收队列中读取数据并将其复制到用户空间。 函数原型 int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_le…...
android-studio-4.2下载 、启动
下载 分享一个国内的android studio网站,可以下载SDK和一些Android studio开发工具 https://www.androiddevtools.cn/ 启动 JAVA_HOME/app/zulu17.48.15-ca-jdk17.0.10-linux_x64/ /app5/android-studio-home/android-studio-ide-201.6568795-linux-4.2C1/bin/s…...
Excel——宏教程(2)
Excel——宏教程(2) 一)、处理单元格 1、直接赋值与引用 将变量、常量值直接赋给单元格、或将单元格的值直接赋给变量、常量,这是在excel中最简单的单元格赋值及引用方法。 如下例将工作表"Sheet1"A1单元格的值赋给Integer变量I,并将I1的值…...
React Native 全栈开发实战班 - 性能与调试之打包与发布
在完成 React Native 应用的开发与性能优化后,下一步就是将应用打包并发布到各大应用市场,如 Apple App Store 和 Google Play Store。本章节将详细介绍 React Native 应用的打包与发布流程,包括 Android 和 iOS 平台的打包步骤、签名配置、发…...
C# 5000 转16进制 字节(激光器串口通讯生成指定格式命令)
最近在做一个与激光器用串口进行通讯的程序文档中要求将频率参数以3个字节的方式进行发送。这里记录一下过程。以便以后再有类似问题时可以快速解决。 /// <summary>/// 设置频率/// </summary>/// <param name"sender"></param>/// <par…...
Win11下载和配置VSCode(详细讲解)
配置VSCode需要的工具: 一、MinGW-w64 二、Visual Studio Code 一、MinGW-w64下载 1、下载 MinGW官网地址: Downloads - MinGW-w64 直链下载: 下载 mingw-w64-install.exe (MinGW-w64 - 适用于 32 位和 64 位 Windows&#…...
基于Multisim的多路智力竞赛抢答器设计与仿真
(1)设计一个8路智力竞赛抢答器,主持人可控制系统的清零和抢答的开始,控制电路可实现最快抢答选手按键抢答的判别和锁定功能,并禁止后续其他选手抢答。 (2)抢答选手确定后给出一声音响的提示和选手编号的显示,抢答选手的编号显示保持到系统被清零为止。 …...
Three.js 闪电效果
闪电shader const shader new THREE.ShaderMaterial({uniforms: {iTime: this.iTime,color: { value: new THREE.Color("#D2F8FE") },},vertexShader: /* glsl */ varying vec2 vUv;varying float normalizeY;void main() {// vUv (uv * 2. - 2.) * vec2(0.3,2.);…...
高效序列化工具(1)-----Protobuf
目录 1.Protobuf Protobuf 的特点 工作原理 Protobuf 与 JSON、XML 的对比 2.protobuf语法 1.数据类型 2.消息 3.枚举 4.嵌套消息 5.重复字段 6.默认值 7.其他类型 1.oneof类型 2.any类型 8.文件组织 3.protobuf命令 1.常见命令 选项: 1. --proto_…...
湛江市社保卡申领指南:手机获取电子照片回执单号
在湛江市,社保卡的申领流程已经实现了数字化,为市民带来了极大的便利。特别是通过手机获取数码照片回执单号,这一环节更是简化了申领过程。今天,我们将详细介绍如何不去照相馆,利用手机来获取数码照片回执单号…...
HTML5实现剪刀石头布小游戏(附源码)
文章目录 1.设计来源1.1 主界面1.2 皮肤风格1.2 游戏中界面 2.效果和源码源码下载万套模板,程序开发,在线开发,在线沟通 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/143798520 HTM…...
TypeScript之常见类型
常见类型(Everyday Types) 本章我们会讲解 JavaScript 中最常见的一些类型,以及对应的描述方式。注意本章内容并不详尽,后续的章节会讲解更多命名和使用类型的方式。 类型可以出现在很多地方,不仅仅是在类型注解 (type annotations)中。我们不仅要学习类型本身,也要学习…...
MacOS java多版本安装与管理-sdkman
安装sdkman curl -s "https://get.sdkman.io" | bashsource "$HOME/.sdkman/bin/sdkman-init.sh"sdk version正常出现sdkman版本号就安装成功了 # 安装java # 安装java8 sdk install java 8.0.412.fx-zulu建议和上述一样安装 fx-zulu 的jdk,…...
NLP论文速读(多伦多大学)|利用人类偏好校准来调整机器翻译的元指标
论文速读|MetaMetrics-MT: Tuning Meta-Metrics for Machine Translation via Human Preference Calibration 论文信息: 简介: 本文的背景是机器翻译(MT)任务的评估。在机器翻译领域,由于不同场景和语言对的需求差异&a…...
20241121 android中树结构列表(使用recyclerView实现)
1、adapter-item的布局 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"wrap_content&…...
达索系统亮相第三十一届中国汽车工程学会年会暨展览会
伴随着改革开放以及中国入世WTO,三十多年来,中国汽车产销已经成为世界最大的单一市场而独占鳌头。近十年来,另辟蹊径的中国汽车产业人在新能源汽车赛道上引领了一波又一波令全球惊艳的创新成就,成为最为靓丽的新出口三大件的头牌。…...
Python网络爬虫实践案例:爬取猫眼电影Top100
以下是一个Python网络爬虫的实践案例,该案例将演示如何使用Python爬取猫眼电影Top100的电影名称、主演和上映时间等信息,并将这些信息保存到TXT文件中。此案例使用了requests库来发送HTTP请求,使用re库进行正则表达式匹配,并包含详…...
ROSSERIAL与Arduino IDE交叉开发(UBUNTU环境,包含ESP32、arduino nano)
ROSSERIAL与Arduino IDE交叉开发 一、简介二、安装1、Ubuntu下的Arduino IDE安装 **针对ESP32报错问题原因溯源和修改**三、运行结点 一、简介 这个教程展示在ubuntu环境下如何利用Arduino IDE配合rosserial开发机器人部件。通过Arduino IDErosserial实现arduino/esp32开发板通…...
爬虫开发工具与环境搭建——使用Postman和浏览器开发者工具
第三节:使用Postman和浏览器开发者工具 在网络爬虫开发过程中,我们经常需要对HTTP请求进行测试、分析和调试。Postman和浏览器开发者工具(特别是Network面板和Console面板)是两种最常用的工具,能够帮助开发者有效地捕…...
ceph 18.2.4二次开发,docker镜像制作
编译环境要求 #需要ubuntu 22.04版本 参考https://docs.ceph.com/en/reef/start/os-recommendations/ #磁盘空间最好大于200GB #内存如果小于100GB 会有OOM的情况发生,需要重跑 目前遇到内存占用最高为92GB替换阿里云ubuntu 22.04源 将下面内容写入/etc/apt/sources.list 文件…...
游戏引擎学习第19天
介绍 这段内容描述了开发者在进行游戏开发时,对于音频同步和平台层的理解和调整的过程。以下是更详细的复述: 开发者表达了他希望今天继续进行的工作内容。他提到,昨天他讲解了一些关于音频的内容,今天他想稍微深入讲解一下他正…...
简单实现vue2响应式原理
vue2 在实现响应式时,是根据 object.defineProperty() 这个实现的,vue3 是通过 Proxy 对象实现,但是实现思路是差不多的,响应式其实就是让 函数和数据产生关联,在我们对数据进行修改的时候,可以执行相关的副…...