1. 背景

在不同电脑的Windows系统中,使用同一个服务程序和客户端程序(TCP协议)进行测试,在部分电脑(win7)出现Send 和Recv大量数据,有时会出现Send和Recv一个数据包耗时1~2s的情况。

2. 原因

最终发现是不同的电脑带宽不一样,导致Recv的速率不一样,而对于TCP协议,若内核中的Socket Recv缓冲区满,而数据没有及时被应用读取走,此时服务程序Send一个数据包的耗时就会加长(因为一直在重发数据包)。这种问题可以通过尝试修改内核中的Socket Recv缓冲区大小来解决该问题,默认Send和Recv缓冲区大小为8K。

3.SO_SNDBUF & SO_RCVBUF

2.1基本说明

SO_SNDBUF
Sets send buffer size. This option takes an int value. (it is 8K by default).
SO_RCVBUF
Sets receive buffer size. This option takes an int value.

每个套接口都有一个发送缓冲区和一个接收缓冲区,使用SO_SNDBUF & SO_RCVBUF可以改变缺省缓冲区大小。

对于客户端,SO_RCVBUF选项须在connect之前设置.
对于服务器,SO_RCVBUF选项须在listen前设置.

2.2 使用setsockopt或getsockopt进行缓冲区大小设置或大小获取

int iResult = 0; 
	int iOptVal = 0;
	int iOptLen = sizeof(int);
	iResult = getsockopt(sockClient, SOL_SOCKET, SO_RCVBUF, (char *)&iOptVal, &iOptLen);
	if (iResult == SOCKET_ERROR) {
		LOG("getsockopt for SO_KEEPALIVE failed with error: %u\n", WSAGetLastError());
	}
	else
		LOG("SO_RCVBUF Value: %ld\n", iOptVal);

	iOptVal = 1024 * 1024;
	iResult = setsockopt(sockClient, SOL_SOCKET, SO_RCVBUF, (char *)&iOptVal, iOptLen);
	if (iResult == SOCKET_ERROR) {
		LOG("setsockopt for SO_KEEPALIVE failed with error: %u\n", WSAGetLastError());
	}
	else
		LOG("Set SO_RCVBUF Value: %d\n", iOptVal);

	iOptVal = 0;
	iResult = getsockopt(sockClient, SOL_SOCKET, SO_RCVBUF, (char *)&iOptVal, &iOptLen);
	if (iResult == SOCKET_ERROR) {
		LOG("getsockopt for SO_KEEPALIVE failed with error: %u\n", WSAGetLastError());
	}
	else
		LOG("SO_RCVBUF Value: %ld\n", iOptVal);

TCP的可靠性

TCP的突出特点是可靠性比较好,主要是怎么实现的呢?
可靠性好不意味着不出错,可靠性好意味着容错能力强。
容错能力强就要求有 备份,也就是说要有缓存,这样的话才能支持重传等功能。
每个Socket都有自己的Send Buffer和Receive Buffer。
当进行send和recv操作时,立即返回,其实是将数据并没有发送出去,而是存放在对应的Send Buffer和Receive Buffer马上返回成功。

作用和意义

接收缓冲区

如何使用接收缓冲区

接收缓冲区把数据缓存入内核,应用进程一直没有调用read进行读取的话,此数据会一直缓存在相应socket的接收缓冲区内
再啰嗦一点,不管进程是否读取socket,对端发来的数据都会经由内核接收并且缓存到socket的内核接收缓冲区之中。
read所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,仅此而已

接收缓冲区buffer满之后的处理策略

接收缓冲区被TCP和UDP用来缓存网络上来的数据,一直保存到应用进程读走为止。

  • TCP
    对于TCP,如果应用进程一直没有读取,buffer满了之后,发生的动作是:通知对端TCP协议中的窗口关闭。这个便是滑动窗口的实现。
    保证TCP套接口接收缓冲区不会溢出,从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。 这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。
  • UDP
    当套接口接收缓冲区满时,新来的数据报无法进入接收缓冲区,此数据报就被丢弃。UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。

发送缓冲区

如何使用发送缓冲区

进程调用send发送的数据的时候,最简单情况(也是一般情况),将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回
换句话说,send返回之时,数据不一定会发送到对端去(和write写文件有点类似),send仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中
每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。