write() 和 send() 都能够收发数据,有什么区别?
- send 能够应用 flags 指定可选项信息,其中 0 示意默认发送行为
- send 当 flags 为 0 时,会期待发送缓冲区数据清空之后才将数据放入发送缓冲器而后返回
- write 不能指定可选项信息,同时不会阻塞
read() 和 recv() 都能够接收数据,有什么区别?
- recv 能够应用 flags 指定可选项信息,其中 0 示意默认接管行为
- recv 当 flags 为 0 时,会期待接收缓冲区有数据之后才将数据从接收缓冲区中取出而后返回
- read 不能指定可选项信息,同时不会阻塞
数据收发选项
#include <sys/socket.h>ssize_t send(int socketfd, const void *buf, size_t nbytes, int flags);ssize_t recv(int socketfd, void *buf, size_t nbytes, int flags);
flags - 收发数据时指定可选项信息,其中 0 为默认收发行为
flags 选项信息 (局部)
可选项 | 含意 | send | recv |
MSG_OOB | 用于传输带外数据(Out Of Band Data),即:紧急数据(优先发送) | √ | √ |
MSG_PEEK | 验证接收缓冲区是否存在数据(有什么数据) | √ | |
MSG_DONTROUTE | 数据传输过程不通过路由表,在本地局域网中寻找目的地 | √ | |
MSG_DONTWAIT | 非阻塞模式,数据收发时立刻返回 | √ | √ |
MSG_WAITALL | 在接管到申请的全副数据之前,不提前返回 | √ | |
MSG_MORE | 有更多数据须要发送,批示内核期待数据 | √ | |
... | ... |
留神: 不同的操作系统对上述可选项的反对不同,理论工程开发时,须要当时对指标零碎中反对的可选项进行调研
MSG_OOB (带外数据,紧急数据)
原生定义
- 应用与一般数据不同的的通道独立传输的数据
- 带外数据优先级比一般数据高(优先传输,对端优先接管)
TCP 中的带外数据
- 因为原生设计的限度,
TCP无奈提供真正意义上的带外数据
- TCP 中仅能通过传输协定音讯头中的标记,传输紧急数据,且
长度仅1字节
TCP 带外数据实现原理
URG 指针指向紧急音讯的下一个地位,即:URG 指针指向地位的前一个字节存储了紧急音讯
接收端优先接管紧急数据,并将其存储到非凡缓冲区,之后再接管一般数据紧急数据:0x03一般数据:0x01,0x02
TCP 带外数据处理策略
- 因为 TCP 设计为流式数据,因而,无奈做到真正的带外数据
被标记的紧急数据可被提前接管,进入非凡缓冲区(仅一字节)
- 每个 TCP 包最多只有一个紧急数据
- 非凡缓冲区仅寄存最近的紧急数据(不及时接管将失落)
用上面的形式收发数据会产生什么
发送一般数据,一般形式接管:失常,数据按序达到
发送一般数据,紧急形式接管:谬误返回
发送紧急数据,一般形式接管:一般数据 recv 时阻塞
发送紧急数据,紧急形式接管:失常,收到紧急数据
编程试验:TCP 紧急数据的发送与接管
client.c
#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <unistd.h>#include <string.h>int main(){ int sock = {0}; struct sockaddr_in addr = {0}; int len = 0; char *test = "Delpin-Tang"; sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) { printf("socket error\n"); return -1; } addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(8888); if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { printf("connect error\n"); return -1; } printf("connect success\n"); len = send(sock, test, strlen(test), MSG_OOB); getchar(); close(sock); return 0;}
server.c
#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <unistd.h>#include <string.h>int main(){ int server = 0; struct sockaddr_in saddr = {0}; int client = 0; struct sockaddr_in caddr = {0}; socklen_t asize = 0; int len = 0; char buf[32] = {0}; int r = 0; server = socket(PF_INET, SOCK_STREAM, 0); if (server == -1) { printf("server socket error\n"); return -1; } saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = htonl(INADDR_ANY); saddr.sin_port = htons(8888); if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) { printf("server bind error\n"); return -1; } if (listen(server, 1) == -1) { printf("server listen error\n"); return -1; } printf("server start success\n"); while (1) { asize = sizeof(caddr); client = accept(server, (struct sockaddr*)&caddr, &asize); if (client == -1) { printf("client accept error"); return -1; } printf("client: %d\n", client); do { r = recv(client, buf, sizeof(buf), MSG_OOB); if (r > 0) { buf[r] = 0; printf("OOB: %s\n", buf); } r = recv(client, buf, sizeof(buf), 0); if (r > 0) { buf[r] = 0; printf("NORMAL: %s\n", buf); } }while (r > 0); close(client); } close(server); return 0;}
输入:
server start successclient: 4NORMAL: Delpin-Tan // 留神,一般数据先输入 (因为当 flags 为 MSG_OOB 时不阻塞,而为 0 时会阻塞,直到接管到数据)OOB: g // 留神,仅输入最初一个字符 !!
小问题:理论开发中,如何高效的接管 TCP 紧急数据?
应用 select 接管紧急数据
socket 上收到一般数据和紧急数据时都会使得 select 立刻返回
- 一般数据:socket 处于数据可读状态(可读取一般数据)
- 紧急数据:socket 解决异样状态(可读取紧急数据)
紧急数据接管示例
num = select(max + 1, &temp, 0, &except, &timeout);if (num > 0) { for (i=1; i<=max; ++i) { if (FD_ISSET(i, &except)) { if (i != server) { char buf[32] = {0}; int r = recv(i, buf, sizeof(buf), MSG_OOB); if (r > 0) { buf[r] = 0; printf("OOB: %s\n", buf); } } } if (FD_ISSET(I, &temp)) { // ... } }}
编程试验:应用 select 接管紧急数据
client.c
#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <unistd.h>#include <string.h>int main(){ int sock = {0}; struct sockaddr_in addr = {0}; int len = 0; char *test = "Delpin-Tang"; sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) { printf("socket error\n"); return -1; } addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(8888); if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { printf("connect error\n"); return -1; } printf("connect success\n"); len = send(sock, test, strlen(test), MSG_OOB); getchar(); close(sock); return 0;}
select-server.c
#include <sys/types.h>#include <sys/socket.h>#include <sys/select.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <unistd.h>#include <string.h>int server_handler(int server) { struct sockaddr_in addr = {0}; socklen_t asize = sizeof(addr); return accept(server, (struct sockaddr*)&addr, &asize);}int client_handler(int client) { char buf[32] = {0}; int ret = recv(client, buf, sizeof(buf) - 1, 0); if (ret > 0) { buf[ret] = 0; printf("Recv: %s\n", buf); } return ret;}int clint_except_handler(int client) { char buf[2] = {0}; int r = recv(client, buf, sizeof(buf), MSG_OOB); if (r > 0) { buf[r] = 0; printf("OOB: %s\n", buf); } return r;}int main(){ int server = 0; struct sockaddr_in saddr = {0}; int max = 0; int num = 0; fd_set reads = {0}; fd_set temps = {0}; fd_set except = {0}; struct timeval timeout = {0}; server = socket(PF_INET, SOCK_STREAM, 0); if (server == -1) { printf("server socket error\n"); return -1; } saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = htonl(INADDR_ANY); saddr.sin_port = htons(8888); if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) { printf("server bind error\n"); return -1; } if (listen(server, 1) == -1) { printf("server listeb error\n"); return -1; } printf("server start success\n"); FD_ZERO(&reads); FD_SET(server, &reads); max = server; while (1) { temps = reads; except = reads; timeout.tv_sec = 0; timeout.tv_usec = 10000; num = select(max + 1, &temps, 0, &except, &timeout); if (num > 0) { int i = 0; for (i=0; i<=max; ++i) { if (FD_ISSET(i, &except)) { if (i != server) { clint_except_handler(i); } } if (FD_ISSET(i, &temps)) { if (i == server) { int client = server_handler(server); if (client > -1) { FD_SET(client, &reads); max = (client > max) ? client : max; printf("accept client: %d\n", client); } } else { int r = client_handler(i); if (r == -1) { FD_CLR(i, &reads); close(i); } } } } int client = server_handler(server); } }}
输入:
server start successaccept client: 4OOB: gRecv: Delpin-Tan
小结
- read() / write() 可用于收发一般数据(不具备扩大性能)
- send() / recv() 可通过选项信息扩大更多功能
- TCP 紧急数据可标识 256 种紧急事件(异样事件)
- 通过 select 可能及时处理紧急数据,并辨别一般数据