TCP缓冲区测试

2022-09-06  本文已影响0人  牛奶言兼

接着上篇的问题,tcp在收包时是怎样的?关于tcp的发包,根据常识可以判断,必然是按用户的消息包“原子”拼接的,即: UserDataPackage1:UserDataPackage2:UserDataPackage3 的顺序进行拼接的,在实际发送时,可以产生UserDataPackage1:UserDataPackage2-partialUserDataPackage2-partial:UserDataPackage3两个传输包。所以,发送层面用户关注内容不是特别多,而接收处理层面则比较复杂。

示例

服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>

#include "unistd.h"
#include "netinet/in.h" // sockaddr_in
#include "arpa/inet.h" // inet_pton

#define MAXLINE 4096
#define LISTENQ 1024

#define LOG(format, ...) \
    printf("file:%s, line:%d  ", __FILE__, __LINE__); printf(format, ##__VA_ARGS__); printf("\n")

int main(int argc, char **argv) {
    LOG("==== server start ====");
    int listenfd, connfd;
    struct sockaddr_in servaddr;
    int *buff = (int *)malloc(1024 * 1024 * 1024); // 1024M * 1024KB * 1024Byte
    time_t ticks;

    buff[0] = 4 * 1024 * 1024 / sizeof(int);

    for (int i = 0; i < (1024 * 1024 * 1024 / sizeof(int) - 1); ++i) {
        buff[i + 1] = i;
    }

    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(1024);

    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) {
        LOG("bind port success.");
    } else {
        LOG("bind port fail.");
        exit(-1);
    }

    listen(listenfd, LISTENQ);
    LOG("wait recv connect request.");

    for(;;) {
        LOG("block net i/o.");
        connfd = accept(listenfd, (struct sockaddr *)NULL, NULL);
        LOG("accept connect request.");
        ticks = time(NULL);
        // snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        write(connfd, buff, 4 * 1024 * 1024); // 发生4MB数据,必然分包传输
        close(connfd);
        break; // 如果不想结束服务,注掉这里
    }
    LOG("==== server end ====");
    exit(0);
}

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "unistd.h"
#include "netinet/in.h" // sockaddr_in
#include "arpa/inet.h" // inet_pton

#define MAXLINE 4 * 1024 * 1024

#define LOG(format, ...) \
    printf("file:%s, line:%d  ", __FILE__, __LINE__); printf(format, ##__VA_ARGS__); printf("\n")

int main(int argc, char **argv) {
    LOG("==== client start ====");
    int sockfd, n;
    char *recvline = (char *)malloc(1024 * 1024 * 1024);; //[MAXLINE + 1];
    struct sockaddr_in servaddr;

    if (argc != 2) {
        LOG("usage: a.out <IPaddress>");
        exit(1);
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        LOG("socket error");
        exit(1);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(1024);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
        LOG("inet_pon error for %s", argv[1]);
        exit(1);
    }

    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { // sock.h connect
        LOG("connect error");
        exit(1);
    }

    while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
        LOG("read bytes: %d", n); // 将接收到的字节数打印出来
        recvline[n] = 0;
        LOG("recv content:");
        LOG("%d", *(int*)recvline);
        // if (fputs(recvline, stdout) == EOF) {
        //     LOG("fputs error.");
        //     exit(1);
        // }
    }

    if (n < 0) {
        LOG("read error");
        exit(1);
    }

    LOG("==== client end ====");
    exit(0);
}

执行过程

./server 
file:timeserv.c, line:18  ==== server start ====
file:timeserv.c, line:37  bind port success.
file:timeserv.c, line:44  wait recv connect request.
file:timeserv.c, line:47  block net i/o.
file:timeserv.c, line:49  accept connect request.
file:timeserv.c, line:56  ==== server end ====
./client 127.0.0.1
file:time.c, line:15  ==== client start ====
file:time.c, line:44  read bytes: 65536
file:time.c, line:46  recv content:
file:time.c, line:47  1048576
file:time.c, line:44  read bytes: 65536
file:time.c, line:46  recv content:
file:time.c, line:47  16383
file:time.c, line:44  read bytes: 2783743
file:time.c, line:46  recv content:
file:time.c, line:47  32767
file:time.c, line:44  read bytes: 1279489
file:time.c, line:46  recv content:
file:time.c, line:47  186547968
file:time.c, line:59  ==== client end ====

从客户端的读取来看,缓冲区的数据是循环使用的,也就是说在读取的过程中,如果有新的数据接收到,而此时还在读取过程中的话,内核会将新收到的数据仍纳入到本次用户数据请求处理中,直到读取完毕或达到用户空间缓冲区大小结束(??感觉是,但不知道是不是,需要看看内核源码,但似乎又不是??)。

这就是tcp无界的表现,它不管发送方是如何组织的消息分段,在tcp这一层,它就是一串字节流,且流是连续的,可以“无限读”。

基于这样的特征,应用层可以设计特定分界符或分段消息头来进行处理,完成分界。

衍生出一个问题
如果接受缓冲区被打满了,会发生什么?

上一篇下一篇

猜你喜欢

热点阅读