linux c++ 网络测试-- 阻塞测试

首先说下问题的疑惑点在哪里?

1.客户端用户不发数据,服务器阻塞udp socket ,recvfrom函数是否会一直阻塞?

来,我们来写一端代码,方便我们测试,— 一定要多写代码,再简单都要写,理解和你认为你理解了,这之间的鸿沟,看似简单,实则非常深,而且不自己写一遍,心里真的是很没底的.

#include           
#include 
#include 
#include 
#include 
#include 
#include 

#define bind_local_port 9996

int main()
{

    int sfd = socket(AF_INET,SOCK_DGRAM,0);

    //bind port -- struct sockaddr_in 在头文件里
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port  = htons(bind_local_port);
    addr.sin_addr.s_addr   = INADDR_ANY;

    if ( bind( sfd , (struct sockaddr*)&addr, sizeof(addr) ) < 0 ) 
    {
        perror("bind fail");
        exit(1);
    }

    struct sockaddr_in client;
    int len;

    char recvbuf [2000];
    
    int recvlen = recvfrom(sfd,recvbuf ,sizeof(recvbuf) , 0 ,(struct sockaddr*)&client , (socklen_t*)&len) ;

    if(recvlen < 0)
    {
        switch(errno)
        {
            case EAGAIN:
            {
                printf("EAGIN event\n");
            }
            break;
            // case EWOULDBLOCK:
            // {
            //     printf("EWOULDBLOCK event\n");
            // }
            // break;
            case ECONNREFUSED:
            {
                printf("ECONNREFUSED event\n");
            }
            break;
            case EFAULT://内核内存错误---
            {
                printf("EFAULT event\n");
            }
            break;
            case EINTR://被信号打断
            {
                printf("EINTR event\n");
            }
            break;
            case ENOMEM:
            {
                printf("ENOMEM event\n");
            }
            break;
            default:
            {
                printf("unknow error event:%d \n" , errno);
            }
            break;

        }
    }

    //现在我没有用超时的来判断,我就是一个阻塞的udp socket

    //测试:
    //1.如果我一直阻塞,那么这里是否在一直等待.
    //2.如果我在阻塞函数里设置了超时,是否回直接返回,我怎么判断返回是超时触发的?
    //3.超时触发后,如果客户端直接中断连接了,那么服务器端怎么检测出来?-- 或者说超时之后,服务器是否还能够检测出来?

    if(recvlen < 0)
    {
        //判断errno
        
    }

    printf("recv len: %d \n" , recvlen);


    close(sfd);

    return 0;
}

测试结论:

阻塞函数,如果不设置超时,那么是真的会一直阻塞,直到有数据过来!!!

2.如果这个时候我们设置了超时时间(注意此阶段,客户端依然不发送任何数据),那么阻塞函数recvfrom的反馈是怎样的? 非正常返回后,我们如何判断是超时导致的错误呢?

到了这一部,我们需要给我们的sock属性设置超时时间。

好我们来设置超时时间为2秒.

 //设置超时属性
    struct timeval rto;
    rto.tv_sec = 2;
    rto.tv_usec = 0;
    setsockopt(sfd,SOL_SOCKET,SO_RCVTIMEO,(void*)&rto,sizeof(rto));

在recv的时候,我们监控时间属性:

    //开始时间
    struct timeval tb;
    gettimeofday(&tb,NULL);
    
    int recvlen = recvfrom(sfd,recvbuf ,sizeof(recvbuf) , 0 ,(struct sockaddr*)&client , (socklen_t*)&len) ;

    //recv time out结束属性
    struct timeval te;
    gettimeofday(&te,NULL);

    printf("second:%d usec:%d \n" , te.tv_sec - tb.tv_sec , te.tv_usec - tb.tv_usec);

结论:
在这里插入图片描述
从结果来看,很明显了,返回-1, errno为EAGIN,系统回告诉你,再次去读取数据.

3.在recv的过程中,如果客户端中断了连接,那么recv函数返回的情况是怎样的?

这里我们直接换用TCP连接进行测试,效果更加直接.

直接说结果:

如果说tcp stream,如果返回0,那么代表流已经终止了,返回-1代表发生了异常.

但是在udp里面,因为udp是无连接的,所以udp其实是存在接收0个长度的数据的可能性的.

这里我们一定要看官方的文档api说明.

RETURN VALUE
       These  calls  return  the  number  of bytes received, or -1 if an error
       occurred.  In the event of an error,  errno  is  set  to  indicate  the
       error.

       When a stream socket peer has performed an orderly shutdown, the return
       value will be 0 (the traditional "end-of-file" return).

       Datagram sockets in  various  domains  (e.g.,  the  UNIX  and  Internet
       domains)  permit  zero-length  datagrams.   When  such  a  datagram  is
       received, the return value is 0.

如果tcp stream 的 recv 超时呢,返回的是-1,并且errno = EAGIN,告诉我们继续去读取数据,这里和udp上面是或的是一致的.

总结下,就是就算recv失败,除非你自己调用close, 否则server端的管道是不会关闭的,但是server可以知道连接上的管道是否close掉了.

Server
gcc -o block_write block_write.c -lpthread

#include           
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//write阻塞测试

#define SERVER_PORT	    9995
#define MAX_SOCKETS 	256

void* user_coming( void *args)
{
    int cfd =  *((int*)(args));

   //内环缓冲区,默认的的缓冲区大小配置在: 系统默认在 cat /proc/sys/net/ipv4/tcp_wmem
    int default_recv_buf_size ;
    int len = 0;
    getsockopt(cfd,SOL_SOCKET,SO_RCVBUF,&default_recv_buf_size,(socklen_t*)&len);
    printf("sock recv default size:%d \n" , default_recv_buf_size);
	
	//上面获取的用户进程的写缓冲区 。
	
    uint8_t pkt [1024];
    int size_writed = 0;
    int c  ;
	//文档说明:https://linux.die.net/man/3/write
    while(1)
    {
		fprintf(stderr, "start write , size writed:%d \n", size_writed);
        c = write(cfd,pkt,sizeof(pkt));
		if ( c == -1)
		{
			switch (errno)
			{
			case EAGAIN:
			{
				fprintf(stderr," EAGAIN \n");
			}
				break;
			case EBADF:
			{
				fprintf(stderr, "EBADF \n");
			}
				break;
			case EFBIG:
			{
				fprintf(stderr, "EFBIG \n");
			}
				break;
			case EINTR:
			{
				fprintf(stderr, "EINTR \n");
			}
				break;
			case EIO:
			{
				fprintf(stderr, "EIO \n");
			}
				break;
			case ENOSPC:
			{
				fprintf(stderr, "ENOSPC \n");
			}
				break;
			case EPIPE:
			{
				fprintf(stderr, "EPIPE \n");
			}
			break;
			case ERANGE:
			{
				fprintf(stderr, "ERANGE \n");
			}
			break;
			default:
				fprintf(stderr, "Errno unknow: %d \n" , errno);
				break;
			}

			break;
		}
        //注意事项:
        //1.write函数的说明,它的返回值是怎么回事?
        size_writed += c;
		fprintf(stderr, "end write , size writed:%d \n", size_writed);
    }

	fprintf(stderr, "exit client \n");
	close(cfd);
}

int main( int argc ,char *argv[])
{

    int sfd  = 0;
    int ret = 0;
    
    sfd = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(SERVER_PORT);

	ret = bind(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    if( ret < 0 )
    {
        fprintf(stderr,"Error bind(). \n");
        goto close;
    }

    ret = listen(sfd,MAX_SOCKETS);
    if (ret < 0)
    {
        fprintf(stderr, "Error listen(). \n");
        goto close;
    }
    
    while(1)
    {
        struct sockaddr_in clientAddr;
        int len;
        int cfd = accept(sfd,(struct sockaddr_in *)&clientAddr, (socklen_t*)&len );
        if(cfd < 0)
        {
            break;
        }

        //创建线程
        pthread_t cthr_id;
        pthread_create(&cthr_id,NULL,user_coming,&cfd);
    }

    close:
        close(sfd);

    return 0;
}

结论:
如果客户端不接收,服务器会一直发送,一直到把内核的缓冲区塞满,塞满之后,此时再次write就会阻塞住,直到内核缓冲区中有空间可以继续写数据了.

如果客户端关闭了和服务器的连接,那么write就会返回-1,errno返回ECONNRESET (=104),

ECONNRESET
A write was attempted on a socket that is not connected.

你可能感兴趣的:(linux,c++,服务器编程)