#include<sys/socket.h>
ssize_t send(int sockfd,const void * buf,size_t nbytes,int flags)
成功返回发送的字节数,失败返回-1
sockfd 表示与数据传输对象的连接的套接字文件描述符。
buf 保存待传输数据的缓冲地址值
nbytes 待传输的字节数
flags 传输数据时指定的可选项信息
#include<sys/socket.h>
ssize_t recv(int sockfd,void * buf,size_t nbytes,int flags)
成功时返回接收的字节数,失败返回-1
sockfd 表示与数据接收对象的连接的套接字文件描述符。
buf 保存接收数据的缓冲地址值
nbytes 待接收的的最大字节数
flags 接收数据时指定的可选项信息
flags代表可选项,可利用位或运算传输多个信息
send&recv函数的可选项及含义
可选项 | 含义 | send | recv |
---|---|---|---|
MSG_OOB | 用于传输带外数据 | 可用 | 可用 |
MSG_PEEK | 验证输入缓冲中是否存在接收的数据 | 不可用 | 可用 |
MSG_DONTROUTE | 数据传输过程中不参照路由表(Routing),在本地Local网网络中寻找目的地 | 可用 | 不可用 |
MSG_DONTWAIT | 调用I/O函数时不阻塞,用于使用非阻塞I/O | 可用 | 可用 |
MSG_WAITALL | 防止函数返回,直到接收全部请求的字节数 | 不可用 | 可用 |
MSG_OOB可用于发送“带外数据”紧急消息 MSG_OOB用于创建特殊发送方法和通道以发送紧急消息。
send
#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);
}
recv
#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()); //将该recv_sock指向的套接字引发的SIGURG信号的处理进程改为以getpid返回值作为进程ID的进程(也就是本进程)
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);//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);
}
SIGURG信号处理函数只能有一个,所以处理SIGURG信号时必须指定信号处理的进程,而getpid函数返回调用此函数的进程ID,所以fcntl(recv_sock, F_SETOWN, getpid());这条语句,意思就是将当前进程指定为处理SIGURG信号的主体。
运行以上程序,是正常的传输顺序,而且紧急消息只有一个字节,通过MSG_OOB可选项传递数据时不会加快传输速度,信号处理函数读取数据时也只读一个字节。剩余的数据只能通过未设置MSG_OOB可选项的普通输入函数读取。
真正意义上的OOB(Out-of-band)需要通过单独的通信路径高速传输数据,但TCP不另外提供,只利用TCP的紧急模式进行传输。
MSG_OOB真正的意义在于督促数据接收对象尽快处理数据。而TCP“保持传输顺序”的传输特性依然成立。
紧急消息的最后一个字符右侧存有紧急指针。紧急指针指向紧急消息的下一个位置,同时告诉对方主机紧急指针指向的位置前面就是紧急消息。实际只用一个字节表示紧急消息 TCP数据包头包含下面两个消息
URG=1:载有紧急消息的数据包序号为1
URG指针:紧急指针的位置
紧急消息890是字符串890还是90?如若不是,是否为单个字符0
这并不重要,除紧急指针前面的一个字节外,数据接收方通过调用常用输入函数接收剩余部分,换而言之,紧急消息意义在于督促消息处理,而非紧急传输想传输的消息
同时设置MSG_PEEK和MSG_DONTWAIT选项,来验证输入缓冲中是否存在接收的数据。设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲中的数据也不会删除。因此通常与MSG_DONTWAIT(用于非阻塞I/O)合作使用,调用以非阻塞方式验证待读取数据是否存在的函数。
功能概括:对数据进行整合一起发送 writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以从多个缓冲分别接收,可以减少I/O函数的调用次数。
#include<sys/uio.h>
ssize_t writev(int filedes,const struct iovec * iov,int iovcnt);
成功时返回发送的字节数,失败返回-1
filedes 表示数据传输对象的文件描述符。(不仅限于套接字,可以是文件和标准输出文件描述符)
iov iovec结构体数组的地址值,结构体iovec包含待发送数据位置和大小信息
iovcnt 第二参数的数组长度
iovec结构体
struct iovec
{
void * iov_base; //缓冲地址
size_t iov_len; //缓冲大小
}
结构体iovec保存待发送数据的缓冲地址值和实际发送的数据长度构成
writev(1,ptr,2);
第三个参数为2说明struct iovec数组长度为2,也就是ptr指针就扫描两个iovec结构体。
#include<sys/uio.h>
ssize_t readv(int filedes,const struct iovec * iov,int iovcnt);
参数含义以及结构体与前面一样
#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100
int 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;
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&writev函数,假设要传输的数据分别位于不同位置,可以通过一次writev(readv)传输读取,不需要多次调用write(read)函数,提高了效率。 其更大的意义在于可以减少数据包的个数。 假设有些服务器为了提高效率禁用了Nagle算法 假设数据位于三个地方,用write函数发送极有可能通过三个数据包发送,用writev可能只需要一个数据包。
将不同位置的数据按照位置放在一个大数组再用write函数发送也可以做到相同的效果,但是writev函数更便利。
Windows并不存在像Linux那样的信号处理机制
MSG_OOB可选项的设置,在Windows中没有针对该选项的事件处理,所以通过select函数解决这一问题。
select监视的对象:
“异常”是不同寻常的程序执行流,Out-of-band也属于异常。也就是说可以用select函数这一特性监视Windows平台接收的Out-of-band数据。
oob_send_win
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#define BUF_SIZE 30
void ErrorHandling(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN sendAdr;
if(argc!=3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hSocket=socket(PF_INET, SOCK_STREAM, 0);
memset(&sendAdr, 0, sizeof(sendAdr));
sendAdr.sin_family=AF_INET;
sendAdr.sin_addr.s_addr=inet_addr(argv[1]);
sendAdr.sin_port=htons(atoi(argv[2]));
if(connect(hSocket, (SOCKADDR*)&sendAdr, sizeof(sendAdr))==SOCKET_ERROR)
ErrorHandling("connect() error!");
send(hSocket, "123", 3, 0);
send(hSocket, "4", 1, MSG_OOB);
send(hSocket, "567", 3, 0);
send(hSocket, "890", 3, MSG_OOB);
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
oob_recv_win
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#define BUF_SIZE 30
void ErrorHandling(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hAcptSock, hRecvSock;
SOCKADDR_IN recvAdr;
SOCKADDR_IN sendAdr;
int sendAdrSize, strLen;
char buf[BUF_SIZE];
int result;
fd_set read, except, readCopy, exceptCopy;
struct timeval timeout;
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
ErrorHandling("WSAStartup() error!");
hAcptSock=socket(PF_INET, SOCK_STREAM, 0);
memset(&recvAdr, 0, sizeof(recvAdr));
recvAdr.sin_family=AF_INET;
recvAdr.sin_addr.s_addr=htonl(INADDR_ANY);
recvAdr.sin_port=htons(atoi(argv[1]));
if(bind(hAcptSock, (SOCKADDR*)&recvAdr, sizeof(recvAdr))==SOCKET_ERROR)
ErrorHandling("bind() error");
if(listen(hAcptSock, 5)==SOCKET_ERROR)
ErrorHandling("listen() error");
sendAdrSize=sizeof(sendAdr);
hRecvSock=accept(hAcptSock, (SOCKADDR*)&sendAdr, &sendAdrSize);
FD_ZERO(&read);
FD_ZERO(&except);
FD_SET(hRecvSock, &read);
FD_SET(hRecvSock, &except);
while(1)
{
readCopy=read;
exceptCopy=except;
timeout.tv_sec=5;
timeout.tv_usec=0;
result=select(0, &readCopy, 0, &exceptCopy, &timeout);
if(result>0)
{
if(FD_ISSET(hRecvSock, &exceptCopy))//发生变化时,检测异常的数组是否注册了接收套接字的文件描述符(对应位是否为1),如果注册了说明接收到了紧急消息,需要处理。
{
strLen=recv(hRecvSock, buf, BUF_SIZE-1, MSG_OOB);
buf[strLen]=0;
printf("Urgent message: %s \n", buf);
}
if(FD_ISSET(hRecvSock, &readCopy))
{
strLen=recv(hRecvSock, buf, BUF_SIZE-1, 0);
if(strLen==0)
{
break;
closesocket(hRecvSock);
}
else
{
buf[strLen]=0;
puts(buf);
}
}
}
}
closesocket(hAcptSock);
WSACleanup();
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
Windows并没有与writev和readv对应的函数,但是可以通过重叠I/O得到同样的效果
本文章使用limfx的vscode插件快速发布