TCP/IP网络编程(10) IO函数

news2024/12/26 21:37:33

在Linux下,一般使用read & write函数完成数据IO,因为Linux下的套接字,可视为文件,其操作方式与文件类似,当套接字分配之后,会为其分配对应的文件描述符。在Windows下,则需要使用recv & send函数完成数据IO

1. Linux下的recv & send 函数

Linux下的recv 和send函数与windows下的其实并无差别,其参数顺序,含义,使用方法完全相同,与实际区别不大:

#include <sys/socket.h>

ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);

/*
sockfd: 与数据传输对象连接的套接字文件描述符
buf:    待传输数据的缓冲区
nbytes: 待传输的字节数
flags:  传输数据时指定的可选项信息
*/
#include <sys/socket.h>

ssize_t recv(int sockfd, const void* buf, size_t nbytes, int flags);

/*
sockfd: 与数据接收对象连接的套接字文件描述符
buf:    待接收数据的缓冲区
nbytes: 待接收的字节数
flags:  接收数据时指定的可选项信息
*/

send()和recv()函数的最后一个参数表示数据收发的可选项,该选项可使用位或运算同时传递多个信息:

可选项含义sendrecv
MSG_OOB用于传输带外数据(out-of-band data)
MSG_PEEK验证输入缓冲中是否存在接收的数据
MSG_DONTROUTE数据传输过程中不参照路由表,在本地网络中寻找目的地
MSG_DONTWAIT调用IO函数时不阻塞,用于使用非阻塞IO
MSG_WAITALL防止函数返回,直到接收全部请求的字节数

1.1 MSG_OOB发送紧急消息:

MSG_OOB可用于创建特殊发送方法和特殊发送通道,发送带外数据紧急消息。使用MSG_OOB收发消息例程如下所示:

客户端client.cpp代码:

/*
    send the msg oob data
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define BUFF_SIZE    30

#define RECV_ADDRESS    "127.0.0.1"
#define RECV_PORT       54100

void error_handler(char* message);


int main(int argc, char* argv[])
{
    int sock;
    struct sockaddr_in recvAddr;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recvAddr, 0, sizeof(recvAddr));
    recvAddr.sin_family = AF_INET;
    recvAddr.sin_addr.s_addr = inet_addr(RECV_ADDRESS);        // 地址
    recvAddr.sin_port = htons(RECV_PORT);                // 端口

    printf("Connecting to server......\n");

    if (connect(sock, (struct sockaddr*)&recvAddr, sizeof(recvAddr)) == -1)
    {
        char message[50];
        sprintf(message, "Failed to connect to address: %s  %d", RECV_ADDRESS, RECV_PORT);
        error_handler(message);
    }

    printf("Successfully connectiing server.\n");

    write(sock, "1234", strlen("1234"));
    send(sock, "1131", strlen("1131"), MSG_OOB);     // 紧急发送消息,带外数据 out of band (OOB)
    write(sock, "333", strlen("333"));
    send(sock, "991", strlen("991"), MSG_OOB);       // 紧急发送消息,带外数据 out of band (OOB)

    close(sock);

    return 0;
}

void error_handler(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

服务端server.cpp代码:

/* server receive data */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>

#define BUFF_SZIE   30
#define SERVER_ADDRESS    "127.0.0.1"
#define SERVER_PORT       54100

void error_handler(char* message);
void urge_handler(int signo);

int server_sock;
int client_sock;

int main(int argc, char* argv[])
{
    struct sockaddr_in serverAddr, clientAddr;
    
    int strLen, state;
    
    socklen_t addrSize = sizeof(serverAddr);

    struct sigaction act;

    char buf[BUFF_SZIE];
    memset(buf, 0, BUFF_SZIE);

    // 信号初始化
    act.sa_handler = urge_handler;       // 信号处理函数
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    server_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serverAddr.sin_port = htons(SERVER_PORT);

    if (bind(server_sock, (struct sockaddr*)&serverAddr, addrSize) == -1)
    {

        error_handler("Failed to bind()\n");
    }

    listen(server_sock, 5);

    client_sock = accept(server_sock, (struct sockaddr*)&serverAddr, &addrSize);

    if (client_sock == -1)
    {
        error_handler("Failed to accept new connection from client.\n");
    }

    fcntl(client_sock, F_SETOWN, getpid());

    state = sigaction(SIGURG, &act, 0);       // 注册信号

    while ((strLen = recv(client_sock, buf, BUFF_SZIE - 1, 0)) != 0)
    {
        /* code */
        if (strLen == -1)
            continue;

        char content[2*BUFF_SZIE];
        memset(content, 0, 2*BUFF_SZIE);

        sprintf(content, "Receive buffer is : %s\n", buf);
        puts(content);

        memset(buf, 0, BUFF_SZIE);
    }
    

    return 0;
}

void error_handler(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

/*
   如果客户端调用了紧急发送带外数据的函数,则系统会产生SIGURGE信号,并调用注册的信号处理函数进行处理
*/
void urge_handler(int signo)
{
    char buff[BUFF_SZIE];
    memset(buff, 0, BUFF_SZIE);

    int recvLen = recv(client_sock, buff, BUFF_SZIE - 1, MSG_OOB);

    printf("Urge Message: %s\n", buff);
}

输出:

需要介绍一下fcntl函数的作用:

fcntl函数可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性;

介绍:fcntl 函数 - 天池渔隐 - 博客园 (cnblogs.com)

    fcntl(client_sock, F_SETOWN, getpid());

上述语句含义表示:文件描述符client_sock指向的套接字引发的SIGURG信号处理进程将变为getpid函数返回值用作ID的进程。

上述描述中的处理SIGURG信号表示的是“调用SIGURG信号处理函数”。在多进程服务器中,多个进程可以共同拥有一个套接字文件描述符。例如,通过fork函数创建了子进程,并同时复制了文件描述符,此时如果发生了SIGURG信号,应该调用哪个进程的信号处理函数就成为了一个问题。因此,在处理信号的时候,必须指定哪一个进程来处理信号。在上述代码中,处理SIGURG信号的时候必须指定处理信号的进程,而getpid函数返回调用此函数的进程。则fctl语句指定的是当前进程作为处理SIGURG信号的主体。

*通过观察程序的输出结果,发现通过MSG_OOB可选项传递数据时,只会返回一个字节;通过MSG_OOB可选项传输数据时并不会加快数据的传输速度,而且通过信号处理函数读取传输过来的数据时,也只能读取到一个字节的数据,剩余的数据只能通未设置MSG_OOB可选项的普通输入函数进行读取。因为TCP不存在真正意义上的带外数据(Out of b band),真正意义上的带外数据需要通过单独的通信路径高速传输数据,TCP不提供这样的传输路径,仅是利用TCP的紧急模式进行传输。

检查输入缓冲区

同时设置MSG_PEEK和MSG_DONTWAIT选项,可以验证输入缓冲中是否存在接收的数据。设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲中的数据,也不会将缓冲中的数据进行删除。此选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读取数据是否存在。示例代码如下所示:

客户端代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define  ADDRESS   "127.0.0.1"
#define  PORT      38400

int main(int argc, char** argv)
{
    int sock;
    struct sockaddr_in address;   

    sock  = socket(PF_INET, SOCK_STREAM, 0);

    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(ADDRESS);
    address.sin_port = htons(PORT);

    if (connect(sock, (struct sockaddr*)&address, sizeof(address)))
    {
        printf("Failed to connect to server.\n");
        return -1;
    }

    write(sock, "3456", strlen("3456"));

    close(sock);

    return 0;
}


服务端代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define  ADDRESS      "127.0.0.1"
#define  PORT         38400
#define  BUFF_SIZE    30

int main(int argc, char** argv)
{

    int serverSock, clientSock;

    struct sockaddr_in servAddr, clientAddr;

    socklen_t addrSize = sizeof(servAddr);

    char buffer[BUFF_SIZE];
    memset(buffer, 0, BUFF_SIZE);

    memset(&servAddr, 0, sizeof(servAddr));
    memset(&clientAddr,0 , sizeof(clientAddr));

    serverSock = socket(PF_INET, SOCK_STREAM, 0);

    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(ADDRESS);
    servAddr.sin_port = htons(PORT);

    int bindRes = bind(serverSock, (struct sockaddr*)&servAddr, sizeof(servAddr));

    if (bindRes == -1)
    {
        printf("Failed to bind the socket.");
        return -1;
    }

    // 开始监听
    listen(serverSock, 5);

    clientSock = accept(serverSock, (struct sockaddr*)&servAddr, &addrSize);

    while (true)
    {
        /* code */
        int recvLen = recv(clientSock, buffer, BUFF_SIZE-1, MSG_PEEK | MSG_DONTWAIT);

        if (recvLen > 0)
            break;
    }

    printf("Buffer content: %s\n", buffer);

    printf("Clearing the buffer.\n");

    memset(buffer, 0, BUFF_SIZE);

    recv(clientSock, buffer, BUFF_SIZE-1, 0);

    printf("Read again: %s\n", buffer);

    close(clientSock);
    close(serverSock);

    return 0;
}

运行结果:

 可以看到在第一次读取完缓冲区中的数据之后,并未将数据进行清除,还可再次读取数据

readv函数和writev函数 (矢量IO操作)

”这两个函数主要用于对数据进行整合传输以及发送“

即writev函数可以将分散保存在多个缓冲区中的数据一并发送,通过readv函数可以由多个缓冲区分别接收,因此适当使用这个两个函数可减少IO函数的调用次数。

#include <sys/uio.h>

ssize_t writev(int filedes, const struct iovec* iov, int iovcnt);

filedes: 表示数据传输对象的套接字文件描述符,但是该函数并不只限于套接字,因此可以向read函数一样向其传输文件或标准输出描述符。

返回值:失败时返回-1,成功时返回发送的字节数

iov: iovec结构体数组的地址值,结构体iovec中包含待发送数据的位置和大小信息

iovcnt:  iovec结构体数组的长度

iovec结构体的声明如下所示:

struct iovec
{
    void* iov_base;     // 缓冲地址
    size_t iovlen;      // 缓冲大小
}

示例代码如下:

#include <stdio.h>
#include <sys/uio.h>

int main(int argc, char* argv[])
{
    struct iovec vec[2];

    char buf1[] = "sdkjfcdc";
    char buf2[] = "4613241356";

    vec[0].iov_base = buf1;
    vec[0].iov_len  = 3;

    vec[1].iov_base = buf2;
    vec[1].iov_len  = 5;

    int lengtn = writev(1, vec, 2);

    puts("");     // 输出一个换行

    printf("write length is: %d", lengtn);

    return 0;
}

输出结果:

readv函数的定义:

#include <sys/uio.h>

ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);

// 返回值:失败时返回-1,成功时返回接受到的总字节数
filedes:传递接收数据的文件(套接字)描述符

示例代码:

#include <stdio.h>
#include <sys/uio.h>

#define BUFF_SIZE  100

int main(int argc, char* argv[])
{
    struct iovec vec[2];

    char buf1[BUFF_SIZE] = {0};
    char buf2[BUFF_SIZE] = {0};

    vec[0].iov_base = buf1;
    vec[0].iov_len  = 5;

    vec[1].iov_base = buf2;
    vec[1].iov_len  = BUFF_SIZE;

    int lengtn = readv(0, vec, 2);

    puts("");     // 输出一个换行

    printf("read length is: %d\n", lengtn);

    printf("First message is : %s\n", vec[0].iov_base);
    printf("Second message is : %s\n", vec[1].iov_base);
    printf("");

    return 0;
}

输出结果:

使用场景:需要传输的数据位于不同的缓冲区时,可以通过1次writev函数调用替代多次write函数调用,提高效率。同样,需要将输入缓冲区中的数据读入不同的位置时,也可以利用1次readv函数进行读取,从而提高效率。仅从C语言的角度来讲,减少函数的调用次数,也能相应的提高性能,但其更大的意义在于减少了数据包的个数,因此writev函数在不在Nagle算法时更有价值。

如果将不同位置数据按照发送的顺序,移动到一个大数组,再调用write一次发送,达到的效果与writev函数相同,但是很明显writev函数更为方便~

---------------------------------------end-------------------------------------------------

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/14303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[oeasy]python0016_编码_encode_编号_字节_计算机

编码(encode) 回忆上次内容 上次找到了字符和字节状态之间的映射对应关系 字符对应着二进制字节二进制字节也对应着字符 这种字节状态是用2位16进制数来表示的hex(n)可以把数字转化为 ​​16进制​​字符串 hexadecimal bin(n)可以把数字转化为 ​​2进制​​字符串 integer …

显示控件——AV输入显示

通过修改变量地址打开或者关闭AV显示&#xff0c;需要硬件支持。 位置信息&#xff1a;控件在工程页面区域的位置 “X”“Y”为控件区域左上角坐标。 “W”“H”为控件区域宽度和高度&#xff0c;单位为像素点。 名称&#xff1a;默认为AVInputView,可以重新设置。 叙述指针…

生产制造管理系统对中小型企业的作用有哪些?

随着企业数字化转型的概念深入人心&#xff0c;传统生产制造企业也渐渐重视起来了。对于资金雄厚的大型生产制造企业而言&#xff0c;企业数字化转型还是比较可以快速适用并且付出行动。而对于很多的中小型企业而言&#xff0c;对于企业数字化转型则没那么重视&#xff0c;甚至…

Beam failure Recovery

微信同步更新欢迎关注同名modem协议笔记 这篇来看BFR 过程&#xff0c;这里把38.300中对于BFD和BFR流程的描述再贴一遍。 BFD 发生在Pcell时: UE 通过在Pcell上进行RA 过程来触发BFR&#xff1b;UE 要选择suitable beam去进行BFR&#xff08;如果gNB 配置了某些beams 的dedica…

NodeJs实战-待办列表(2)-待办列表增删

NodeJs实战-待办列表-增删改查项目结构增加表单提交事件修改 index.htmlNodeJS 服务端增删改查修改 server.js效果图初始页面输入 1111&#xff0c;点击添加输入 1111 点击完成项目结构 增加表单提交事件 修改 index.html 引入 jquery <script type"text/javascript…

对抗生成网络GAN系列——f-AnoGAN原理及缺陷检测实战

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

[iOS- Mac终端上传Git and 生成Token]

前言 我先写了如何上传的每一步&#xff0c;都有截图&#xff0c;在文章的后面写了出现的问题&#xff0c;即网上找到的解决方法&#xff01;&#xff01;&#xff01;谨以此篇博客纪念我忙了一个晚上的麻烦 累死个人 首先要配置好git 这里不多说了Mac上传Git 搞了我一下午晚…

异常检测 | MATLAB实现基于支持向量机和孤立森林的数据异常检测(结合t-SNE降维和DBSCAN聚类)

异常检测 | MATLAB实现基于支持向量机和孤立森林的数据异常检测(结合t-SNE降维和DBSCAN聚类) 目录 异常检测 | MATLAB实现基于支持向量机和孤立森林的数据异常检测(结合t-SNE降维和DBSCAN聚类)效果一览基本介绍模型准备模型设计参考资料效果一览 基本介绍 提取有用的特征,机器…

遨博机械臂——ROS通讯机制

文章目录知识目标1. 遨博机械臂SDK二次开发接口2. 机械臂MoveIt!低级控制器配置3. aubo ROS中间件通讯机制知识目标 学习遨博机械臂SDK开发常用接口&#xff1b;学习MoveIt!功能包中机械臂控制器的配置通用流程&#xff1b;学习遨博ROS驱动与底层SDK与上层move_group之间的通讯…

记录一次成功的frida编译

长期接手python外包和爬虫&#xff0c;私聊哦 文章目录前言一、Frida是什么&#xff1f;二、Frida一些资源相关三、Frida源码编译准备1.安装包准备2.环境变量配置四、开始编译源码结束前言 某社交平台X信的风控越发恶心&#xff0c;目前面临项目被砍的风险&#xff0c;做了n多…

学生HTML个人网页作业作品----(画家企业8页)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 公司官网网站 | 企业官网 | 酒店官网 | 等网站的设计与制 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&#…

ScanContext 论文详解 - 用途:Lidar SLAM 回环检测、空间描述符

深蓝学院&#xff08;深蓝学院 - 专注人工智能与自动驾驶的学习平台&#xff09;是专注于人工智能的在线教育平台&#xff0c;已有数万名伙伴在深蓝学院平台学习&#xff0c;很多都来自于国内外知名院校&#xff0c;比如清华、北大等。​ 来源&#xff1a; “深蓝前沿教育” …

汽车以太网线束测试及如何破局

今日&#xff0c;AEM联合维信仪器在深圳国际会展中心参加了第二十四届中国国际高新技术成果交易会https://baike.baidu.com/item/%E7%AC%AC%E4%BA%8C%E5%8D%81%E5%9B%9B%E5%B1%8A%E4%B8%AD%E5%9B%BD%E5%9B%BD%E9%99%85%E9%AB%98%E6%96%B0%E6%8A%80%E6%9C%AF%E6%88%90%E6%9E%9C%…

【自然语言处理(NLP)】基于注意力机制的中-英机器翻译

【自然语言处理&#xff08;NLP&#xff09;】基于注意力机制的中-英机器翻译 作者简介&#xff1a;在校大学生一枚&#xff0c;华为云享专家&#xff0c;阿里云专家博主&#xff0c;腾云先锋&#xff08;TDP&#xff09;成员&#xff0c;云曦智划项目总负责人&#xff0c;全国…

Java常用类和对象---尚硅谷Java入门视频学习

1.Object 常用方法&#xff1a; toString() 将对象转换成字符串。 toString默认打印的就是对象的内存地址&#xff0c;所以&#xff0c;为了能够更直观理解对象的内容&#xff0c;可以重写这个方法 hashCode() 获取对象的内存地址 equals() 判断两个对象是否相等, 如果相等&…

Netty-RPC

RPC&#xff1a;&#xff08;Remote Procedure Call&#xff09;-- 远程过程调用 &#xff08;1&#xff09;一个计算机通信协议。该协议允许运行与A计算机的程序调用运行于另一台计算机的子程序&#xff0c;而程序员无需额外滴为这个交互作用编程。 &#xff08;2&#xff09…

华为机试 - 考古学家

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 有一个考古学家发现一个石碑&#xff0c;但是很可惜&#xff0c;发现时其已经断成多段&#xff0c;原地发现n个断口整齐的石碑碎片。为了破解石碑内容&#xff0c;考古学家希望有程序能帮忙计算复原后…

[附源码]java毕业设计旅游管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[数据结构]二叉树之堆的实现

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【数据结构】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站…

各种信息收集

谷歌hack语法 site: baidu.com 搜索关于baidu.com的相关子域名网站 黑客 site:baidu.com 搜索关于baidu.com的子域名网站中有关字符“黑客”的网页 inurl: admin/login.php 搜索含有"admin/login.php"的url inurl: login site:baidu.com …