Linux教程

Linux网络编程 - 发送/接收数据 & 缓冲区

本文主要是介绍Linux网络编程 - 发送/接收数据 & 缓冲区,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

发送数据

可以用以下三个函数发送数据。每个函数都是单独使用的,使用的场景略有不同。

ssize_t write (int socketfd, const void *buffer, size_t size);
  
#include <sys/socket.h>
ssize_t send (int socketfd, const void *buffer, size_t size, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  • write 就是常见的文件写函数(Linux中一切皆为文件)。

    • 对于普通文件描述符而言,操作系统内核不断地往文件系统中写入字节流。
      • 写入的字节流大小通常和输入参数 size 的值是相同的,否则表示出错。
    • 对于套接字描述符而言,它代表了一个双向连接。
      • 写入的字节数有可能比请求的数量少
  • send 可以指定选项,发送带外数据(一种基于 TCP 协议的紧急数据,用于客户端 - 服务器在特定场景下的紧急处理)。

  • sendmsg 指定多重缓冲区传输数据,以结构体 msghdr 的方式发送数据。

    msghdr定义:

    struct iovec {                    /* Scatter/gather array items */
     void  *iov_base;              /* Starting address */
     size_t iov_len;               /* Number of bytes to transfer */
    };
    struct msghdr {
     void         *msg_name;       /* Optional address */
     socklen_t     msg_namelen;    /* Size of address */
     struct iovec *msg_iov;        /* Scatter/gather array */
     size_t        msg_iovlen;     /* # elements in msg_iov */
     void         *msg_control;    /* Ancillary data, see below */
     size_t        msg_controllen; /* Ancillary data buffer len */
     int           msg_flags;      /* Flags on received message */
    };
    

发送缓冲区

当 TCP 三次握手成功,TCP 连接成功建立后,操作系统内核会为每一个连接创建配套的基础设施,比如发送缓冲区

发送缓冲区的大小可以通过套接字选项来改变,当应用程序调用 write 函数时,实际所做的事情是把数据从应用程序中拷贝到操作系统内核的发送缓冲区中,并不一定是把数据通过套接字写出去。

image-20211101133547721

wirte() 的返回时机:直到某一个时刻,应用程序的数据可以完全放置到发送缓冲区里,write() 阻塞调用返回。

读取数据

ssize_t read (int socketfd, void *buffer, size_t size);

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

read 函数要求操作系统内核从套接字描述字 socketfd 读取最多多少个字节(size),并将结果存储到 buffer 中。

返回值表示实际读取的字节数目,如果返回值为 0,表示 EOF(end-of-file),这在网络中表示对端发送了 FIN 包,要处理断连的情况;如果返回值为 -1,表示出错。

read 是最多读取 size 个字节。循环读取,每次都读到 size 个字节的函数:

size_t readn(int fd, void *buffer, size_t size) {
    char *buffer_pointer = (char *)buffer;
    int length = size;

    while (length > 0) {
        int result = read(fd, buffer_pointer, length);

        if (result < 0) {
            if (errno == EINTR)
                continue;     /* 考虑非阻塞的情况,这里需要再次调用read */
            else
                return (-1);
        } else if (result == 0)
            break;                /* EOF(End of File)表示套接字关闭 */

        length -= result;
        buffer_pointer += result;
    }
    return (size - length);        /* 返回的是实际读取的字节数*/
}

例子

common.h :

#pragma once

#include <iostream>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <string>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* error - print a diagnostic and optionally exit */
void error(int status, int err, char *fmt, ...) {
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    if (err) {
        fprintf(stderr, ": %s (%d)\n", strerror(err), err);
    }
    if (status) {
        exit(status);
    }
}

int tcp_client(char *address, int port) {
    int socket_fd;
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    inet_pton(AF_INET, address, &server_addr.sin_addr);

    socklen_t server_len = sizeof(server_addr);
    int connect_rt = connect(socket_fd, (struct sockaddr *) &server_addr, server_len);
    if (connect_rt < 0) {
        error(1, errno, "connect failed ");
    }

    return socket_fd;
}

size_t readn(int fd, void *buffer, size_t size) {
    char *buffer_pointer = (char *)buffer;
    int length = size;

    while (length > 0) {
        int result = read(fd, buffer_pointer, length);

        if (result < 0) {
            if (errno == EINTR)
                continue;     /* 考虑非阻塞的情况,这里需要再次调用read */
            else
                return (-1);
        } else if (result == 0)
            break;                /* EOF(End of File)表示套接字关闭 */

        length -= result;
        buffer_pointer += result;
    }
    return (size - length);        /* 返回的是实际读取的字节数*/
}

Client:

#include "tcp_send_read/common/commom.h"

# define MESSAGE_SIZE 1024 * 1024 * 100

void send_data(int sockfd) {
    char *query;
    query = (char *)malloc(MESSAGE_SIZE + 1);
    for (int i = 0; i < MESSAGE_SIZE; i++) {
        query[i] = 'a';
    }
    query[MESSAGE_SIZE] = '\0';

    const char *cp;
    cp = query;
    size_t remaining = strlen(query);
    while (remaining) {
        int n_written = send(sockfd, cp, remaining, 0);
        fprintf(stdout, "send into buffer %ld \n", n_written);
        if (n_written <= 0) {
            error(1, errno, "send failed");
            return;
        }
        remaining -= n_written;
        cp += n_written;
    }

    return;
}

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

    if (argc != 2)
        error(1, 0, "usage: tcpclient <IPaddress>");

    int sockfd = tcp_client(argv[1], 12345);
    
    send_data(sockfd);
    exit(0);
}

Server:

#include "tcp_send_read/common/commom.h"

#define BUFFER_SIZE 1024 * 1024

void read_data(int sockfd) {
    ssize_t n;
    char buf[BUFFER_SIZE];

    int time = 0;
    for (;;) {
        fprintf(stdout, "block in read\n");
        if ((n = readn(sockfd, buf, BUFFER_SIZE)) == 0) { // EOF
            fprintf(stdout, "read 0, close it!\n");
            fflush(stdout); // must flust it!
            return;
        }

        time++;
        fprintf(stdout, "1M read for %d \n", time);
        usleep(2000);
    }
}


int main(int argc, char **argv) {
    int listenfd, connfd;
    socklen_t clilen;
    struct sockaddr_in cliaddr, servaddr;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(12345);

    /* bind到本地地址,端口为12345 */
    bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
    /* listen的backlog为1024 */
    listen(listenfd, 1024);

    /* 循环处理用户请求 */
    for (;;) {
        clilen = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
        read_data(connfd);   /* 读取数据 */
        close(connfd);          /* 关闭连接套接字,注意不是监听套接字*/
    }
}

此程序能说明几个问题:

  1. 阻塞式套接字最终发送返回的实际写入字节数和请求字节数是相等的

  2. 对于 send 来说,返回成功仅表示数据写到发送缓冲区成功,并不意味着对端已经成功收到(对端何时收到,对发送者透明

reference

[1] 极客时间 · 网络编程实战 :05 | 使用套接字进行读写:开始交流吧

[2] Linux高性能服务器编程

这篇关于Linux网络编程 - 发送/接收数据 & 缓冲区的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!