python socket recv 重复 python socket recv返回值_网络


一般在经典的socket教程中,调用send()和recv()时都会判断一下返回值,如果返回值是-1(或者SOCKET_ERROR),那么就进行错误处理(一般是打印出错信息,关闭socket,退出)。在我的一个项目中我因为嫌麻烦就想当然没有判断send和recv的返回值,结果遇到了点小麻烦。

简单地说明一下问题:

服务器端支持多线程,每个线程用while(1)循环地接收客户端的请求并加以处理;客户端发送请求后,关闭socket,退出。pretty straight forward,uh?

出现的问题是,就算客户端只发送一条请求,服务器还是能一直接收到请求,如果在recv()函数后打印出接收的信息,也是能打

印出的:

while(1)
{recv(socket, buf, 128,0);
printf("%s/n",buf);
}

假设客户只发送了一条"HELLO",那么服务器端就会一直不停地打印出"HELLO"。

我开始时很奇怪,首先,recv()如果接收不到信息不是应该阻塞吗?照这样说,服务器应该接收到一次“HELLO”后就阻塞在那里了;其次,为什么每次都会打印出同样的信息呢?

后来发现原因很简单。

  1. 客户端关闭后,recv()函数会一直返回-1,这就是recv()没有阻塞的原因。(也就是说recv()已经执行了,只不过失败了)
  2. 因为recv()的失败,buf的内容就一直不会变。所以打印出的buf的内容其实一直都是第一次buf的内容"HELLO",而并不是说服务器能够一直接收到"HELLO"这条信息。

if (recv(sock,buff,sizeof(buff),0) == -1){
 id = WSAGetLastError();}


可得到错误类型为10054 ,即WSAECONNRESET
ECONNRESET是linux环境网络编程产生的错误,错误码为104,

WSAECONNRESET是windows环境网络编程产生的错误,错误码为10054

两者产生的原因都一样,分以下几种情况:

1接收端recv或者read, 对端已经关闭连接,recv/read返回该错误

2 对端重启连接,还未建立连接

3 发送端已经断开连接,但是调用send会触发这个错误

第二点第三点都可以通过判断返回值解决,第一点在一些砍死正常情况下也会触发该错误。

比如对端close(fd),接收端调用recv并没有返回0,而是-1,打印错误码为104或
10054
,**按道理讲这种情况按照返回值为0处理是可以的,但是尽量将代码写的规范一些,

避免不必要的错误。

为什么close(fd)会导致接收端读到复位RST,也就是收到错误的104呢,
因为close(fd)只是将文件描述符关闭,并没有关闭tcp建立起来的连接,断开连接需要四次握手,
倘若发送端发送缓冲区有数据未发送完或者接受缓冲区有数据未读完,调用close(fd),那么连接并没有关闭,这样,接收端
收到的就是所谓的104或10054错误了
。如何避免这个错误呢,就需要我们判断发送端发送和接受操作

是否进行完,也就是判断缓冲区是否有数据,如果有数据需要等待数据处理完毕在关闭,否则会出现上述错误。

我有一个做法是通过调用shutdown(s,SHUT_WR);关闭发送端的写端,这样发送端不发送数据,然后调用close

这次会发送关闭连接的FIN标志,接收端接收到FIN,那么recv或者read返回的就是0.

int shutdown(int sockfd,int how);
  Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。

进程将不能对该套接字发出任何读操作。

对 TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
SHUT_WR:关闭连接的写端,进程不能在对此套接字发出写操作
SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR

下面摘用网上的一段话来说明二者的区别:
close-----关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这个链接,能读或写这个socket id
shutdown–则破坏了socket 链接,读的时候可能侦探到EOF结束符,写的时候可能会收到一个SIGPIPE信号,这个信号可能直到
socket buffer被填充了才收到。

close(sockfd);使用close中止一个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。

而且shutdown只是处理连接关闭,并不能回收描述符,所以最终还是要调用close(fd)才能回收描述符,在所有描述符引用次数为0时发送FIN消息给对端。

出了采取shutdown的方式,还可以通过设置socket属性,调用close时,检测在socket完成缓冲区读写后,才关闭连接。

struct linger {
 
     int l_onoff; /* 0 = off, nozero = on */
 
     int l_linger; /* linger time */
 
};

有下列三种情况:

1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;

2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;

3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。

下面是代码:

int z;
 int s;      
 struct linger so_linger;
  
 so_linger.l_onoff = 1
 so_linger.l_linger = 30;
 z = setsockopt(s,SOL_SOCKET,SO_LINGER,&so_linger,
     sizeof so_linger);
 if ( z )
     perror("setsockopt(2)");
     close(s);

到目前为止,我觉得比较好的主动关闭方式是:

关闭端:

1确保发送缓存区没有数据未发送,调用shutdown(fd,SHUTWR);

2如果能接收到数据,继续接受,直到接收到对方的FIN,也就是

read返回0或者-1

3如果接收到关闭信号,那么调用close正常关闭。

当client,调用read(socketfd,buffer,n)时,返回0的情况:

1、server端调用了close(soketfd)函数

2、server调用了close(fd,SHUT_WR),关闭server端的写连接,半关闭

python socket recv 重复 python socket recv返回值_套接字_02