我们知道, udp socket的接收缓冲区大小是有限的, 可以查到最大值。 以server端为例, 如果server端socket接收缓冲区满了, 那么client端新进的请求不会得到及时处理, 出现丢包。 即使server端的socket的接收缓冲区没有满, 但仍有一些请求在其中排队, 那么从client端发过来的新请求, 也自然会排队, 很可能没有等到server端来得及去recvfrom并做逻辑操作, 这个请求包就已经超时了, client端等不及了。 

       接收缓冲区排队堵塞(即使没有塞满), 会造成新来的请求排队, 如果client端对时延要求较高, 那么很可能出现所有的请求都超时, 尽管server端还在苦逼忙碌地处理排队的请求, 但server端对外表现的服务能力为0 (因为新进请求都因排队而超时, 而server端一直在处理已经超时的请求, 做无用功), 这就是雪崩。 

       我们来模拟下:

       sever端udp代码为:

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

int main(int argc, char **argv)
{
    struct sockaddr_in srvAddr;
    bzero(&srvAddr,sizeof(srvAddr));
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    srvAddr.sin_port = htons(atoi(argv[1]));
    int srvAddrLen = sizeof(srvAddr);

    int iSock = socket(AF_INET, SOCK_DGRAM, 0);  // udp
    int iRet = bind(iSock, (struct sockaddr *)&srvAddr, sizeof(srvAddr));
	
    while(1)
    {
		struct sockaddr_in cliAddr;
		bzero(&cliAddr,sizeof(cliAddr));
		cliAddr.sin_family = AF_INET;
		int cliAddrLen = sizeof(cliAddr);
		char szBuf[40960] = {0};
        recvfrom(iSock, szBuf, sizeof(szBuf) - 1, 0, (struct sockaddr *)&cliAddr, (socklen_t*)&cliAddrLen); 
		
		char *p = strstr(szBuf, "=");
		if(p != NULL)
		{
			*p = 0;
		}
		
		usleep(500 * 1000); // 延时500ms, 这一部分相当于业务逻辑处理
		
		unsigned int len = strlen(szBuf);
		sendto(iSock, szBuf, len + 1, 0, (struct sockaddr *)&cliAddr, sizeof(cliAddr));
    }

    close(iSock);
    return 0;
}

      看看client 1的代码:

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

int main(int argc, char **argv)
{
    struct sockaddr_in srvAddr;
    bzero(&srvAddr, sizeof(srvAddr));
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_addr.s_addr = inet_addr("172.17.0.15");
    srvAddr.sin_port = htons(atoi(argv[1]));

    int iSock = socket(AF_INET, SOCK_DGRAM, 0); // udp
	unsigned int i = 0;
	
	char szTmp[1024] = {0};
	for(unsigned int j = 0; j < sizeof(szTmp) - 100; j++)
	{
		szTmp[j] = 'a';
	}
	
	struct timeval tv;
	tv.tv_sec = 5;
	tv.tv_usec =  0;
	setsockopt(iSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); 
		
    while(1)
    {
		char szBuf[1024] = {0};
		sprintf(szBuf, "%u=%s", ++i, szTmp);
		
        sendto(iSock, szBuf, strlen(szBuf) + 1, 0, (struct sockaddr *)&srvAddr, sizeof(srvAddr));
		
		int iRet = recvfrom(iSock, szBuf, sizeof(szBuf) - 1, 0, NULL, NULL);  // client端允许的超时时间为5s
		if(iRet <= 0)
		{
			printf("time out, or error");
		}
		else
		{
			printf("%s\n", szBuf);		
		}
		
		getchar();
    }

	close(iSock);
    return 0;
}

      看看client 2的代码:

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

int main(int argc, char **argv)
{
    struct sockaddr_in srvAddr;
    bzero(&srvAddr, sizeof(srvAddr));
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_addr.s_addr = inet_addr("172.17.0.15");
    srvAddr.sin_port = htons(atoi(argv[1]));

    int iSock = socket(AF_INET, SOCK_DGRAM, 0); // udp
	unsigned int i = 0;
	
	char szTmp[1024] = {0};
	for(unsigned int j = 0; j < sizeof(szTmp) - 100; j++)
	{
		szTmp[j] = 'a';
	}
	
		
    while(1)
    {
		char szBuf[1024] = {0};
		sprintf(szBuf, "%u=%s", ++i, szTmp);
        sendto(iSock, szBuf, strlen(szBuf) + 1, 0, (struct sockaddr *)&srvAddr, sizeof(srvAddr));  // 不停地给server端发包,去塞满server端socket的接收缓冲区
    }

	close(iSock);
    return 0;
}

     

       测试一: 

       开启server, 然后开启client 1,  可以看到, client 1每次都能及时收到server端的回包

ubuntu@VM-0-15-ubuntu:~/taoge/client$ ./a.out 8888
1

2

3

4

5

6

   一切正常。 好, 关闭上面的server和client 1

 

      测试二: 

     开启server端, 然后开启client 2,  不停往server端发包。 由于server端处理业务逻辑有500ms的延时, 而client 2在while中疯狂发包, 所以server端socket的接收缓冲区会逐渐堆积, 我们可以在逐渐堆积的过程中启动client 1来看表现, 但是client 2太疯狂了, 一会就满了, 来不及在堆积过程中开启client 1.

      server端socket接收缓冲区的堆满过程很快, 如下:

ubuntu@VM-0-15-ubuntu:~/taoge$ netstat -au
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp   214272      0 *:8888                  *:*                                
udp     5376      0 *:34237                 *:*                                
ubuntu@VM-0-15-ubuntu:~/taoge$ netstat -au
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp   214272      0 *:8888                  *:*                                
udp     6912      0 *:34237                 *:*                                
ubuntu@VM-0-15-ubuntu:~/taoge$ 

 

        现在, client 1还没有开启, 我们来关闭client 2, 停止疯狂发包, 那么server端的recvfrom就会逐渐从缓冲区中取出数据了, server端socket的接收缓冲区的内容会逐渐变小(逐渐远离爆满的状态), 我们来看看变化的过程:

 

ubuntu@VM-0-15-ubuntu:~/taoge$ netstat -au
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp   214272      0 *:8888                  *:*                                
udp     5376      0 *:34237                 *:*                                
ubuntu@VM-0-15-ubuntu:~/taoge$ netstat -au
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp   214272      0 *:8888                  *:*                                
udp     6912      0 *:34237                 *:*                                
ubuntu@VM-0-15-ubuntu:~/taoge$ 
ubuntu@VM-0-15-ubuntu:~/taoge$ netstat -au
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp   205056      0 *:8888                  *:*                                
ubuntu@VM-0-15-ubuntu:~/taoge$ 
ubuntu@VM-0-15-ubuntu:~/taoge$ netstat -au
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp   191232      0 *:8888                  *:*                                
ubuntu@VM-0-15-ubuntu:~/taoge$ 
ubuntu@VM-0-15-ubuntu:~/taoge$ netstat -au
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
udp   168192      0 *:8888                  *:*                            

       可以看到, 缓冲区的内容在逐渐减少。 

       此时, 我们打开client 1来发起请求, 虽然server端接收缓冲区中的内容在减少, 但毕竟还有内容, 所以client 1发的请求依然要排队等待, 如果超过client 1预期的5s, 那么就会超时, 来看看:

ubuntu@VM-0-15-ubuntu:~/taoge/client$ ./a.out 8888
time out, or error
time out, or error
time out, or error
time out, or error
1
2
3

       可以看到, 最开始的几个请求都超时了, 等server端的socket的接收缓冲区不堵塞(逐渐减少到零)时, client 1发起的请求, 都能及时收到回包。

       在上述案例中,排队引起了雪崩。 但是, 我们要注意, 排队不一定会导致雪崩。是否雪崩取决于排队多少和server端的处理速度, 当然, 也不要以为只有缓冲区满才会雪崩。 

       如上就是雪崩的过程, 怎么预防呢? client端和server端都要努力, 雪崩的预防, 我们在之前的文章中讨论过, 故不再赘述。 

 

       最后, 我要说, 我是很讨厌雪崩的。

 

 

 

 

 


本文转载:CSDN博客