send & recv函数
Linux中的send & recv
send函数:
recv函数:
send函数和recv函数的最后一个参数是收发数据的可选项。利用位或运算同时传递多个信息。
MSG_OOB:发送紧急消息
就例如医院的急诊病人优先处理以及单独的急诊室。
MSG_OOB可选项用于创建特殊发送方法俄通道以发送紧急消息。
MSG_OOB使用示例:
服务器端:
/* MSG_OOB:紧急接收使用示例*/ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<signal.h> #include<arpa/inet.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()); //getpid返回调用此函数的进程ID,此语句指定当前进程为处理SIGURG信号的主体 state = sigaction(SIGURG,&act,0); //收到MSG_OOB紧急消息时,操作系统产生SIGURG信号,并调用Handler 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); }
客户端:
/* MSG_OOB:紧急发送使用示例*/ #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); }
运行结果:
运行结果不符预期。这是因为TCP不存在真正意义上的"带外数据“。实际上,MSG_OOB中的OOB是指Out-of-band,而”带外数据“的含义是:"通过完全不同的通信路径传输的数据"。
真正意义上的Out-of-band需要通过单独的通信路径告诉传输数据。只利用TCP的紧急模式进行传输。
紧急模式工作原理
下面给出设置MSG_OOB可选项状态下的数据传输过程
字符0右侧偏移量为3的位置存有紧急指针。紧急指针指向紧急消息的下一个位置(偏移量加1)。
实际只用一个字节表示紧急消息信息。通过用于数据传输的TCP数据包(段)的结构看得清楚。
TCP头含有如下两种消息:
URG = 1:载有消息的数据包
URG指针:紧急指针位于偏移量为3的位置
紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息。
检查输入缓冲
同时设置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) { fputs(message,stderr); fputc('\n',stderr); exit(1); } 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; }
下面给出使用MSG_PEEK和MSG_DONTWAIT选项的结果:
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) { fputs(message,stderr); fputc('\n',stderr); exit(1); } 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(0); } 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); //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; }
运行结果:
通过运行结果验证:
仅发送一次的数据被读取了两次,因为第一次调用recv函数时设置了MSG_PEEK可选项,以上就是MSG_PEEK可选项的功能。
readv & writev函数
readv & writev 函数有助于提高数据通信效率。
使用readv & writev函数
readv & writev 函数的功能可概括如下:对数据进行整合传输及发送的函数。
writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接收。
writev函数:
第二个参数数组iovec结构体的声明如下:
struct iovec { void * iov_base; //缓冲地址 size_t iov_len; //缓冲大小 }
函数的使用方法:
第三个参数为2,所以浏览两个iovec结构体变量,发送这这些指针指向的缓冲数据。
示例:writev.c
#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; }
运行结果:
readv函数:
示例:
#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; //第一个数据保存在buf1,长度为5 vec[1].iov_base = buf2; vec[1].iov_len = BUF_SIZE; //第二个数据保存在buf2,长度为100 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; }
运行结果:
由结果可知,第8行的vec数组保存了数据。
合理使用readv & writev函数
当需要传输的数据分别位于不同的缓冲时,需要多次调用write函数,此时就可以通过1次writev函数调用替代操作,会提高效率.....
减少函数调用次数也能提高性能,更大的意义在于减少数据包个数。