欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 艺术 > 《TCP/IP网络编程》(第十三章)多种I/O函数(1)

《TCP/IP网络编程》(第十三章)多种I/O函数(1)

2024/11/30 10:42:54 来源:https://blog.csdn.net/m0_53115174/article/details/139307952  浏览:    关键词:《TCP/IP网络编程》(第十三章)多种I/O函数(1)

本章将继续介绍Linux系统中的send/recv函数,同时还会介绍Linux系统中的新I/O函数readv/writev函数

1.Linux中的send/recv

基本语法

ssize_t send(
int sockfd,       //套接字的文件描述符
const void *buf,  //保存待传输数据的缓冲地址
size_t len, 	  //待传输的字节数
int flags         //传输数据时的可选项
);
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的紧急工作模式相关,下面介绍紧急模式的工作原理

  1. 设置URG标志和紧急指针: 当TCP套接字上的数据需要以紧急模式发送时,数据包的URG(Urgent)标志会被设置。这告诉TCP栈该数据包包含紧急数据。同时TCP头部中设置一个紧急指针字段,它指示紧急数据的下一个位置,结构如下所示:

    上图是紧急消息输出缓冲,URG指针指向偏移量为3的位置,代表偏移量为3之前的数据都是紧急消息。
    在这里插入图片描述
    上图是简化版的TCP数据包,只标注了本章需要的东西,实际上还有其他的。
    URG=1: 表示该TCP数据包包含带外数据;URG指针=3: 指紧急指针位于偏移量为3的位置。
  2. 发送紧急数据: 实际上TCP协议只会将字符串的最后一个字节0作为紧急消息发送,因为根据TCP协议的规范,带外数据(紧急消息)通常只限于发送一个字节的消息。
  3. 接收紧急数据: 接收端的TCP栈在接收到带有URG标志的数据包时,会将紧急数据放入特殊的紧急队列中。
  4. 紧急数据的大小限制: TCP的紧急模式通常只允许发送和接收1字节的紧急数据

示例代码
①send端

#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端

#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端函数

#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端函数

#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);这段读取代码就会删除输入缓冲中的数据

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com