首先说下问题的疑惑点在哪里?
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.