ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2021-11-01 14:33:31  阅读:168  来源: 互联网

标签:include int void buffer read Linux 缓冲区 接收数据 size


发送数据

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

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高性能服务器编程

标签:include,int,void,buffer,read,Linux,缓冲区,接收数据,size
来源: https://www.cnblogs.com/cxl-/p/15493489.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有