首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网络技术 > 网络协议 >

非阻塞EWOULDBLOCK的有关问题

2012-02-26 
非阻塞EWOULDBLOCK的问题服务器使用select模型,当客户端有连接后,用if (FD_ISSET(clientSocket,&read_set)

非阻塞EWOULDBLOCK的问题
服务器使用select模型,当客户端有连接后,用if (FD_ISSET(clientSocket,&read_set))判断是否有读操作,结果客户端没发任何数据,但这个判断一直为true,使用recv接收数据,返回值一直小于0,并且是EWOULDBLOCK,请问这个问题是否正常?如果不正常如何处理?

[解决办法]

C/C++ code
#include "stdafx.h"#include <windows.h>#include <iostream>#pragma comment(lib,"ws2_32.lib")using namespace std;BOOL InitSocket(){    WSADATA wsaData;    WORD wVersionRequested;    int err=0;    wVersionRequested=MAKEWORD(2,2);    err=WSAStartup(wVersionRequested,&wsaData);    if(err!=0)    {        cout<<"Could not find a usage WinSock DLL"<<GetLastError()<<endl;        return false;    }    if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2)    {        cout<<"Could not find a usage WinSock DLL"<<GetLastError()<<endl;        WSACleanup();        return false;    }    return true;}int _tmain(int argc, _TCHAR* argv[]){    if(!InitSocket())        return -1;    SOCKET ListenSock;    SOCKET AcceptSock;    SOCKADDR_IN InterAddr;    ListenSock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);    InterAddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);    InterAddr.sin_family=AF_INET;    InterAddr.sin_port=htons(8000);    if(ListenSock==INVALID_SOCKET)    {        cout<<"A error ocured while build a socket"<<GetLastError()<<endl;        closesocket(ListenSock);        WSACleanup();        return -1;    }    if(bind(ListenSock,(sockaddr *)&InterAddr,sizeof(sockaddr))==SOCKET_ERROR)    {        cout<<"A error ocured while binding a socket"<<GetLastError()<<endl;        closesocket(ListenSock);        WSACleanup();        return -1;    }    if(listen(ListenSock,5)==SOCKET_ERROR)    {        cout<<"A error ocured while listening on a socket"<<GetLastError()<<endl;        closesocket(ListenSock);        WSACleanup();        return -1;    }    char sendbuf[512]={'\0'};    char recvbuf[512]={'\0'};    fd_set fd_read,fd_write;    FD_ZERO(&fd_read);    FD_ZERO(&fd_write);    TIMEVAL timeout;    timeout.tv_sec=13;    timeout.tv_usec=0;    int ret=0;    int bytes_recv;    AcceptSock=accept(ListenSock,NULL,NULL);    if(AcceptSock==INVALID_SOCKET)    {        cout<<"A error ocured while accept connect request from client"<<GetLastError()<<endl;        closesocket(ListenSock);        closesocket(AcceptSock);        WSACleanup();        return -1;    }    while(true)    {        FD_SET(AcceptSock,&fd_read);        FD_SET(AcceptSock,&fd_write);        ret=select(NULL,&fd_read,&fd_write,NULL,&timeout);        if(ret==SOCKET_ERROR)            continue;        if(ret>0)        {            if(FD_ISSET(AcceptSock,&fd_read))            {                if((bytes_recv=recv(AcceptSock,recvbuf,sizeof(recvbuf),NULL))==SOCKET_ERROR)                {                    cout<<"A error ocured while receive data from client"<<GetLastError()<<endl;                    closesocket(AcceptSock);                    closesocket(ListenSock);                    WSACleanup();                    return -1;                }                else                    cout<<recvbuf<<endl;            }            else             {                if(FD_ISSET(AcceptSock,&fd_write))                {                    gets(sendbuf);                    if(send(AcceptSock,sendbuf,sizeof(sendbuf),NULL)==SOCKET_ERROR)                    {                        cout<<"A error ocured while send data to client"<<GetLastError()<<endl;                        closesocket(AcceptSock);                        closesocket(ListenSock);                        WSACleanup();                        return -1;                    }                }            }        }    }    return 0;} 


[解决办法]
当一个已完成的连接准备好被accept的时候,select会把监听socket标记为可读;因此,如果用select等待外来的连接时,应该不需要把监听socket设置为非阻塞模式,因为如果select告诉我们连接已经就绪,accept就不应该被阻塞;
不过这样做的时候有一个BUG:当客户端在跟服务器建立连接之后发送了一个RST包,这个时候accept就会阻塞,直到有下一个已完成的连接准备好被accept为止.
struct linger的l_onoff标志设为1,l_linger设为0.这个时候,如果关闭TCP连接时,会先在socket上发送一个RST包;这个时候会出现下面的问题:
A:select向服务器返回监听socket可读,但是服务器要在一段时间之后才能调用accept;
B:在服务器从select返回和调用accept之前,收到从客户发送过来的RST;
C:这个已经完成的连接被从队列中删除,我们假设没有其它已完成的连接存在;
D:服务器调用accept,但是由于没有其它已完成的连接存在,因而服务器被阻塞了;
注意,服务器会被一直阻塞在accept调用上,直到另外一个客户建立一个连接为止;但是如果一直没有其它客户建立连接,那么服务器将仍然一直被阻塞在accept调用上,不处理任何其他已就绪的socket;
解决这个问题的办法是:
A:如果使用select来获知何时有链接已就绪可以accept时,总是把监听socket设置为费阻塞模式,并且
B:在后面的accept调用中忽略以下错误:EWOULDBLOCK(源自Berkeley的实现在客户放弃连接时出现的错误)、ECONNABORTED(Posix.1g的实现在客户放弃连接时出现的错误)、EPROTO(SVR4的实现在客户放弃连接时出现的错误)和EINTR(如果信号被捕获).

照着上面的试试看能不能解决问题
[解决办法]
“因为如果select告诉我们连接已经就绪,accept就不应该被阻塞;”

lishengkai:你的回复,看不明白啊,代码里面不是先建立了连接(accept)之后才使用select的嘛?

能不能解析的更加清楚点啊?

对于这个问题我也挺感兴趣的,我也继续调查调查,同时也希望有高手解答!
[解决办法]
maoxing63570
对于产生的现象,不知道是不是这样,可以试一下:
在accept以后,客户端会给服务器发送一个ACK确认连接信号,这个时候,你在下面的调用中,select就会变成可读被判断为true,即使你客户端没有发送任何数据。
在代码中为了避免这个错误现象,在select之前需要对read,write之前对检视的套接字IO集合进行初始话,也就是在accept之后,select之前。
代码修改为:
 fd_set fd_read,fd_write;
while(true)
{
FD_ZERO(&fd_read);
FD_ZERO(&fd_write);
 
FD_SET(AcceptSock,&fd_read);
FD_SET(AcceptSock,&fd_write);

热点排行