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

本章将继续介绍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的紧急工作模式相关,下面介绍紧急模式的工作原理

  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端

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

相关推荐
听风吹等浪起3 分钟前
改进系列(3):基于ResNet网络与CBAM模块融合实现的生活垃圾分类
网络·深度学习·神经网络·分类·生活
速盾cdn39 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
叫我龙翔1 小时前
【计网】实现reactor反应堆模型 --- 框架搭建
linux·运维·网络
不爱学习的YY酱2 小时前
【计网不挂科】计算机网络期末考试——【选择题&填空题&判断题&简述题】试卷(4)
网络·计算机网络
装睡的小5郎2 小时前
家庭宽带如何开启公网ipv4和ipv6
网络
yfs10242 小时前
压缩Minio桶中的文件为ZIP,并通过 HTTP 响应输出
网络·网络协议·http
有谁看见我的剑了?2 小时前
Ubuntu 22.04.5 配置vlan子接口和网桥
服务器·网络·ubuntu
hgdlip2 小时前
有什么办法换网络ip动态
网络·tcp/ip·智能路由器
超栈2 小时前
HCIP(11)-期中综合实验(BGP、Peer、OSPF、VLAN、IP、Route-Policy)
运维·网络·网络协议·计算机网络·web安全·网络安全·信息与通信
დ旧言~2 小时前
【网络】应用层——HTTP协议
开发语言·网络·网络协议·http·php