网络编程实战13 小数据包应对:TCP中的动态数据传输

调用数据发送接口后

数据只是从应用程序拷贝到了系统内核的发送缓冲区中。由TCP协议栈来负责发送出去。

流量控制

原因:
发送端和接收端都有自己的处理能力和存储规模,所以需要流量控制。

发送/接收窗口:
发送窗口和接收窗口时TCP连接的双方,一个作为生产者,一个作为消费者,为了达到一致协同的生产-消费速率,而产生的算法模型实现

拥塞控制

原因
TCP要考虑在多个连接共享在有限的带宽上,兼顾效率和公平性的控制

常用算法:
●慢启动通过一定的规则,慢慢地将网络发送数据的速率增加到一个阈值。超过这个阈值之后,慢启动就结束了
●拥塞避免登场。在这个阶段,TCP 会不断地探测网络状况,并随之不断调整拥塞窗口的大小。

发送窗口和拥塞窗口

发送窗口:反应了作为单TCP连接,点对点之间的流量控制模型,需要和接收端一起共同协调来调整大小。
拥塞窗口:多个TCP连接共享带宽的拥塞控制模型,发送端独立地根据网络状况来动态调整

TCP发送缓冲区数据速度的因素:
至少取决于两个因素,一般取两者的最小值作为判断依据
1.当前的发送窗口大小
2.拥塞窗口大小

几个场景和解决

第一个场景: 接收端读一点就让发送端写
 情况:接收端处理得急不可待,比如刚刚读入了 100 个字节,就告诉发送端:“喂,我已经读走 100 个字节了,你继续发”,在这种情况下,你觉得发送端应该怎么做呢?

 处理:糊涂窗口综合征 ,需要接收端优化。接收端应该在自己的缓冲区大刀一个合理的值后,再向发送端发送窗口更新通知。这个合理的值由对应的RFC规范定义。

第二个场景: 每次传输数据很小
 情况:是所谓的“交互式”场景,比如我们使用 telnet 登录到一台服务器上,或者使用 SSH 和远程的服务器交互,这种情况下,我们在屏幕上敲打了一个命令,等待服务器返回结果,这个过程需要不断和服务器端进行数据传输。这里最大的问题是,每次传输的数据可能都非常小,比如敲打的命令“pwd”,仅仅三个字符。这意味着什么?这就好比,每次叫了一辆大货车,只送了一个小水壶。在这种情况下,你又觉得发送端该怎么做才合理呢?

 处理:需要在发送端进行优化。这个优化的算法叫作Nagle算法,Nagle算法的本质就是让大批量的小数据包同时发送。为此它提出,在任何一个时刻,未被确认的小数据包(长度小于最大报文段长度MSS的TCP分组,MSS是TCP数据包每次能够传输的最大数据分段)不能超过一个。这样发送端就可以把接下来的几个小数据包存储起来,等待接收到前一个小数据包的ACK后,一次性发送出去。

第三个场景: 大量ACK报文
 情况:是从接收端来说的。我们知道,接收端需要对每个接收到的 TCP 分组进行确认,也就是发送 ACK 报文,但是 ACK 报文本身是不带数据的分段,如果一直这样发送大量的 ACK 报文,就会消耗大量的带宽。之所以会这样,是因为 TCP 报文、IP 报文固有的消息头是不可或缺的,比如两端的地址、端口号、时间戳、序列号等信息, 在这种情形下,你觉得合理的做法是什么?

 :处理:延时ACK,即收到数据不马上回复,而是累计需要发送的ACK报文,等到有数据要发送给对端时,将累计的ACK一并发送出去。不过不能无限延时(设定一个阈值,没有数据发送的话达到阈值发累计的ACK),否则发送端会认为发送失败触发重传。

Nagle算法和延时ACK的矛盾 :两个优化互相阻止,增大了处理时延
 例子:比如,客户端分两次将一个请求发送出去,由于请求的第一部分的报文未被确认,Nagle 算法开始起作用;同时延时 ACK 在服务器端起作用,假设延时时间为 200ms,服务器等待 200ms 后,对请求的第一部分进行确认;接下来客户端收到了确认后,Nagle 算法解除请求第二部分的阻止,让第二部分得以发送出去,服务器端在收到之后,进行处理应答,同时将第二部分的确认捎带发送出去。
在这里插入图片描述
 解决:
1.没有Nagle的话,请求的第二部分早就发出去了,可以关闭Nagle算法

//除非有十足的把握,否则不要改变默认的TCP Nagle算法
int on = 1; 
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)); 

2.可以将写操作合并,将请求一次性发送过去,而不是分开两部分独立发送,结果会好很多

合并写操作

作用
 调用 writev 操作时,会自动把几个数组的输入合并成一个有序的字节流,然后发送给对端。

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

iovec 结构:
定义如下,这两个函数的第二个参数都是指向某个 iovec 结构数组的一个指针

struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};

使用:


int main(int argc, char **argv) {
    if (argc != 2) {
        error(1, 0, "usage: tcpclient <IPaddress>");
    }

    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(SERV_PORT);
    inet_pton(AF_INET, argv[1], &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 ");
    }

    char buf[128];
    struct iovec iov[2];

    char *send_one = "hello,";
    iov[0].iov_base = send_one;
    iov[0].iov_len = strlen(send_one);
    iov[1].iov_base = buf;
    while (fgets(buf, sizeof(buf), stdin) != NULL) {
        iov[1].iov_len = strlen(buf);
        int n = htonl(iov[1].iov_len);
        if (writev(socket_fd, iov, 2) < 0)
            error(1, errno, "writev failure");
    }
    exit(0);
}

运行结果:
输入:

world
network

结果:

received 12 bytes: hello,world

received 14 bytes: hello,network
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值