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

相关推荐
✿ ༺ ོIT技术༻1 小时前
Linux:自定义协议+序列反序列化
网络
Gauss松鼠会1 小时前
GaussDB高安全—数据保护:数据透明加密
网络·数据库·人工智能·sql·安全·开源·gaussdb
wuyunhang1234563 小时前
网络原理--UDP的特点
网络·网络协议·udp
晚秋大魔王11 小时前
windwos与linux环境下Iperf3带宽测试工具的安装、使用
网络·iperf3·带宽测试
多敲代码防脱发11 小时前
Spring框架基本使用(Maven详解)
java·网络·后端·spring·maven
wssswsss12 小时前
docker容器网络配置及常用操作
网络·docker·容器
RFID舜识物联网13 小时前
RFID测温技术:电力设备安全监测的新利器
网络·人工智能·嵌入式硬件·物联网·安全
靖节先生14 小时前
Wireshark详解
网络·测试工具·wireshark
m0_7482365815 小时前
本地部署轻量级web开发框架Flask并实现无公网ip远程访问开发界面
前端·tcp/ip·flask
技术小齐15 小时前
网络运维学习笔记 021 HCIA-Datacom新增知识点02 SDN与NFV概述
运维·网络·学习