本章将继续介绍Linux系统中的
send/recv
函数,同时还会介绍Linux系统中的新I/O函数readv/writev
函数
1.Linux中的send/recv
基本语法
cpp
ssize_t send(
int sockfd, //套接字的文件描述符
const void *buf, //保存待传输数据的缓冲地址
size_t len, //待传输的字节数
int flags //传输数据时的可选项
);
cpp
ssize_t recv(
int sockfd, //套接字的文件描述符
void *buf, //保存接受数据的缓冲地址
size_t len, //可接受的字节数
int flags //接受数据时的可选项
);
可以看到两个函数的最后一个参数是一个可选项,该可选项可利用位或运算 传递多个信息,下面表列举了部分可选项
接下来会选取一部分(主要是不受操作系统差异影响的选项)进行详解。
2.MSG_OOB: 发送紧急消息
MSG_OOB是特定于TCP的一个选项,它用于在TCP连接中发送紧急或带外数据(out-of-band data),通常用于中断正常的数据流,它允许发送端发送一些紧急信息,这些信息会绕过正常的数据队列,直接发送给接收端,而接收端也可以优先处理这些带外数据。
①带外数据的特点:
- 优先级高: 带外数据的优先级高于普通数据,因此它通常用于传输紧急信息。
- 独立于普通数据: 带外数据独立于正常的数据流,它不会与普通数据混合。
- 有限大小: 带外数据的大小通常有限制,因为它们需要快速发送和接收。
②使用MSG_OOB的场景:
- 紧急控制信息: 在某些协议中,如TCP中,带外数据可以用于发送紧急指针更新,这是一种控制信息,用于指示发送端已经成功发送的数据量。
- 中断长连接: 在长时间保持连接的应用中,带外数据可以用来发送中断信号,比如在TCP连接中发送紧急的控制命令。
- 快速响应: 在需要快速响应的应用中,带外数据可以用于快速通知接收端某些事件的发生。
③MSG_OOB的工作原理
MSG_OOB的工作原理主要与TCP的紧急工作模式相关,下面介绍紧急模式的工作原理
- 设置URG标志和紧急指针: 当TCP套接字上的数据需要以紧急模式发送时,数据包的URG(Urgent)标志会被设置。这告诉TCP栈该数据包包含紧急数据。同时TCP头部中设置一个紧急指针字段,它指示紧急数据的下一个位置,结构如下所示:
上图是紧急消息输出缓冲,URG指针 指向偏移量为3的位置,代表偏移量为3之前 的数据都是紧急消息。
上图是简化版的TCP数据包,只标注了本章需要的东西,实际上还有其他的。
URG=1: 表示该TCP数据包包含带外数据;URG指针=3: 指紧急指针位于偏移量为3的位置。 - 发送紧急数据: 实际上TCP协议只会将字符串的最后一个字节0作为紧急消息发送,因为根据TCP协议的规范,带外数据(紧急消息)通常只限于发送一个字节的消息。
- 接收紧急数据: 接收端的TCP栈在接收到带有URG标志的数据包时,会将紧急数据放入特殊的紧急队列中。
- 紧急数据的大小限制: TCP的紧急模式通常只允许发送和接收1字节的紧急数据
示例代码
①send端
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[]){
int sock;
struct sockaddr_in recv_addr;
if(argc!=3){
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&recv_addr, 0, sizeof(recv_addr));
recv_addr.sin_family=AF_INET;
recv_addr.sin_addr.s_addr=inet_addr(argv[1]);
recv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&recv_addr, sizeof(recv_addr))==-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);//发送紧急信息,但是由于MSG_OOB的限制,只会发送最后一个字节
close(sock);
return 0;
}
void error_handling(char *message){
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
②recv端
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.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;
char buf[BUF_SIZE];
socklen_t serv_adr_sz;
struct sigaction act;
if(argc!=2)
{
printf("Usage : %s <IP> <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);
}
正常应该输出1234567890,但由于MSG_OOB机制,会优先输出紧急信息,而且即使紧急信息是三个字节
send(sock, "890", strlen("890"), MSG_OOB);
但由于MSG_OOB限制,只能输出最后一个字节0
3.检查输入缓冲
同时设置MSG_PEEK和MSG_DONTWAIT选项,可以验证输出缓冲中是否存在接受的数据。且设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲,缓冲中的数据也不会删除,因此MSG_PEEK通常和和MSG_DONTWAIT一起使用,用于调用以非阻塞方式验证待读数据存在与否的函数
①send端函数
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.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);
}
②recv端函数
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <iostream>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int acpt_sock;
int recv_sock;
struct sockaddr_in acpt_adr,recv_adr;
int str_len,state;
char buf[BUF_SIZE];
socklen_t recv_adr_sz;
if(argc!=2)
{
printf("Usage : %s <IP> <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("buf : %s \n", 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);
}
str_len = recv(recv_sock, buf, sizeof(buf)-1,MSG_PEEK|MSG_DONTWAIT );
这段读取代码并不会删除输入缓冲中的数据,所以下面还可以继续读取输入缓冲
str_len = recv(recv_sock, buf, sizeof(buf)-1,0);
这段读取代码就会删除输入缓冲中的数据