socket通信中的分散读和集中写


解释一

在 socket 通信中,分散读和集中写是两种常见的 I/O 模型。

分散读(scatter read):在分散读模型中,应用程序可以在一次 readv() 系统调用中从socke读取多个缓冲区的数据。这意味着可以一次性读取多个数据包,而不必为每个数据包执行单独的 read() 调用。这种方式可以减少系统调用的次数,提高读取效率

集中写(gather write):在集中写模型中,应用程序可以在一次 writev() 系统调用中将多个缓冲区的数据写入socket。这意味着可以一次性发送多个数据包,而不必为每个数据包执行单独的 write() 调用。这种方式可以减少系统调用的次数,提高发送效率

分散读和集中写可以通过使用 readv() 和 writev() 系统调用来实现。这两个系统调用允许应用程序指定一个向量(vector),其中包含多个缓冲区的指针和缓冲区的长度。这样可以一次性读取或写入多个缓冲区的数据。

以下是一个简单的示例代码,展示了如何在 socket 通信中使用分散读和集中写:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

void sendData(int socket, const char *data, size_t dataSize)
{
    struct iovec iov[2];
    char buffer[1024];

    // 将数据分散到两个缓冲区
    iov[0].iov_base = data;
    iov[0].iov_len = dataSize / 2;
    iov[1].iov_base = buffer;
    iov[1].iov_len = dataSize - iov[0].iov_len;

    // 使用 writev 进行集中写
    ssize_t bytesWritten = writev(socket, iov, 2);
    if (bytesWritten == -1)
    {
        perror("Failed to send data");
        exit(EXIT_FAILURE);
    }
    printf("Sent %zu bytes of data\n", bytesWritten);
}

void receiveData(int socket)
{
    struct iovec iov[2];
    char buffer1[1024], buffer2[1024];

    // 创建两个缓冲区来接收数据
    iov[0].iov_base = buffer1;
    iov[0].iov_len = sizeof(buffer1);
    iov[1].iov_base = buffer2;
    iov[1].iov_len = sizeof(buffer2);

    // 使用 readv 进行分散读
    ssize_t bytesRead = readv(socket, iov, 2);
    if (bytesRead == -1)
    {
        perror("Failed to receive data");
        exit(EXIT_FAILURE);
    }
    printf("Received %zu bytes of data\n", bytesRead);

    // 输出接收到的数据
    printf("Received data: %s\n", buffer1);
    printf("Received data: %s\n", buffer2);
}

int main()
{
    int serverSocket, clientSocket;
    struct sockaddr_in serverAddress, clientAddress;
    socklen_t addressLength = sizeof(clientAddress);

    // 创建一个 TCP socket
    if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("Failed to create socket");
        exit(EXIT_FAILURE);
    }

    // 绑定 socket 到本地回环地址和端口 12345
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = INADDR_LOOPBACK;
    serverAddress.sin_port = htons(12345);
    if (bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) == -1)
    {
        perror("Failed to bind socket");
        exit(EXIT_FAILURE);
    }

    // 开始监听连接
    if (listen(serverSocket, 1) == -1)
    {
        perror("Failed to listen on socket");
        exit(EXIT_FAILURE);
    }

    printf("Waiting for client connection...\n");

    // 接受客户端连接
    if ((clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &addressLength)) == -1)
    {
        perror("Failed to accept connection");
        exit(EXIT_FAILURE);
    }

    printf("Connected with client\n");

    // 发送数据给客户端
    const char *data = "Hello, client!";
    sendData(clientSocket, data, strlen(data));

    // 接收客户端的数据
    receiveData(clientSocket);

    // 关闭连接
    close(clientSocket);
    close(serverSocket);

    return 0;
}

在这个示例中,创建了一个TCP服务器和一个TCP客户端。通过在客户端和服务器之间建立连接,可以使用分散读和集中写来高效地传输数据。


解释二

在socket通信中,分散读(scattered reads)和集中写(gathered writes)是两种常用的高级I/O操作,它们通过使用sendmmsg()、recvmmsg()、writev()和readv()等系统调用来提高数据传输效率。这些操作允许你将数据分成多个片段,并且可以同时发送或接收这些片段,从而减少了系统调用的次数和上下文切换的开销。

分散读(Scattered Reads)

分散读是指从socket中读取数据时,将接收到的数据分散到多个缓冲区中。这通常在你想要同时读取多个数据片段,或者你想要减少接收数据时所需的系统调用次数时很有用。在Unix-like系统中,你可以使用recvmsg()配合CMSG_SPACE()和CMSG_FIRSTHDR()宏以及readv()系统调用来实现分散读。

例如,使用readv()函数:

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/iov.h>
#include <unistd.h>

// 假设socket_fd是一个已经建立连接的socket文件描述符
int socket_fd;

// 定义两个缓冲区,用于分散读
char buffer1[1024];
char buffer2[512];

// 初始化iovec结构体数组
struct iovec iov[2];
iov[0].iov_base = buffer1;
iov[0].iov_len = sizeof(buffer1);
iov[1].iov_base = buffer2;
iov[1].iov_len = sizeof(buffer2);

// 读取数据到两个缓冲区中
ssize_t bytes_read = readv(socket_fd, iov, 2);
if (bytes_read == -1) {
    perror("readv");
    // 错误处理
}

// 现在buffer1和buffer2中包含了从socket读取的数据

集中写(Gathered Writes)

集中写是指将数据从多个缓冲区发送到一个socket中。这在你想要同时发送多个数据片段,或者你想要减少发送数据时所需的系统调用次数时非常有用。在Unix-like系统中,你可以使用sendmsg()配合CMSG_SPACE()和CMSG_FIRSTHDR()宏以及writev()系统调用来实现集中写。

例如,使用writev()函数:

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/iov.h>
#include <unistd.h>

// 假设socket_fd是一个已经建立连接的socket文件描述符
int socket_fd;

// 定义两个缓冲区,用于集中写
char buffer1[1024];
char buffer2[512];

// 假设buffer1和buffer2已经被填充了数据

// 初始化iovec结构体数组
struct iovec iov[2];
iov[0].iov_base = buffer1;
iov[0].iov_len = sizeof(buffer1);
iov[1].iov_base = buffer2;
iov[1].iov_len = sizeof(buffer2);

// 将数据从两个缓冲区发送到socket中
ssize_t bytes_written = writev(socket_fd, iov, 2);
if (bytes_written == -1) {
    perror("writev");
    // 错误处理
}

// 现在数据已经从buffer1和buffer2发送到了socket中

使用分散读和集中写可以提高数据传输的效率,因为它们允许你在一个系统调用中处理多个缓冲区,减少了系统调用的开销。此外,这些操作还可以帮助你更好地控制数据传输,例如,你可以通过调整每个缓冲区的大小和数量来优化数据传输的性能。

posted @   guanyubo  阅读(99)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示