UDP 10054 的故事

      今天发现,用ace封装的udp莫名其妙的在第一次响应客户端后,无法再次响应客户端的请求。原来是10054的故事,网上有好段子,个人就不多费口舌了。感谢各位大神!

 

转自: http://blog.csdn.net/ccnucjp8136/article/details/4515002

今天碰到了一个非常怪的问题,我的主要功能是先用UDP的Sendto发送一个数据过去,同时启动一个线程,这个线程去recvfrom来自远目地主机的包,但是每次我Sendto后,recvfrom不阻塞在那里,而报10054的错误。先贴代码

第一部分:主要是初始化

 WSADATA wsaData;
 WSAStartup(MAKEWORD(2,2), &wsaData);

    /*初始化服务端地址和本地客户端地址并绑定本地套接字*/

 m_nPort = GetConfigValue("BIPSERVERCONFIG", "CATPORT", 8888);//获取服务目的端口
 m_ServerAdd = GetConfigValue("BIPSERVERCONFIG" ,"CATADDRESS", "127.0.0.1");//获取服务目的端口

    m_ServerAddress.sin_family = AF_INET;
 m_ServerAddress.sin_port   = htons(m_nPort);
 m_ServerAddress.sin_addr.s_addr = inet_addr(m_ServerAdd);
 //m_ServerAddress.sin_addr.s_addr = inet_addr("10.8.4.52");

 /*设置客户端的地址*/
 m_ClientAddress.sin_family = AF_INET;
 m_ClientAddress.sin_port   = htons(0);
 m_ClientAddress.sin_addr.s_addr = htons(INADDR_ANY);

 m_ClientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 if(m_ClientSocket == SOCKET_ERROR)
 {
  DWORD dwError = GetLastError();
  TRACE("错误码:%d", dwError);
  WriteRunLog_EX("InitSocket","初始化套接字错误/n");
  return FALSE;
 }


 /*将客户机的socket和客户机的Socket地址bind*/
    int ret = 0;
 ret = bind(m_ClientSocket, (struct sockaddr*)&m_ClientAddress, sizeof(m_ClientAddress));
 if (ret != 0)
    {  
  DWORD dwError = GetLastError();
  TRACE("错误码:%d", dwError);
  WriteRunLog_EX("InitSocket","bind套接字错误/n");
  return FALSE;
    }

 

第二部分:Sendto处代码:从这里你可以看到,我Sendto一个包给远端目的主机马上启动一个线程去收数据。

 retCode = sendto(m_ClientSocket, lpBuf,len, 0,(struct sockaddr*)&m_ServerAddress, sizeof(m_ServerAddress));
    if (SOCKET_ERROR == retCode)
    {
  return FALSE;
    }

 ///int addressLen = sizeof(addr);
 //retCode = recvfrom(m_ClientSocket, lpBuf, len, 0, (struct sockaddr*)&addr, &addressLen );

 /*发送数据后,等待远端服务端发送数据过来*/
 pRecvThread = AfxBeginThread((AFX_THREADPROC)StartRecvProc,this,THREAD_PRIORITY_NORMAL);
 if (NULL == pRecvThread)
 {  
  TRACE("启动接收线程失败!");
  WriteRunLog_EX("StartRecv","bind错误/n");
  return FALSE;

 }

 

第三部分:收线程的代码

while (TRUE)
 {
  int addressLen = sizeof(struct sockaddr);
  retCode = recvfrom(m_ClientSocket, data, sizeof(data), 0, (struct sockaddr*)&m_ServerAddress, &addressLen );
  if (SOCKET_ERROR == retCode)
  {
   DWORD dwError = GetLastError();
   TRACE("错误码为:%d",dwError);
   
   TRACE("收包错误!");
            WriteRunLog_EX("StartRecv","收包错误/n");
  }
  else
  {
   char testBuf[8192];
   memcpy(testBuf, data, retCode);
   testBuf[retCode] = '/0';
   TRACE("StartRecv收到包:%s",testBuf);
   BIP_CATMsg msg;
   msg.len = retCode;
   memcpy(msg.strBuf, data, msg.len);
   m_RecvMsgQueue.AddTail(msg);
  }
   
 } 

我的本意在这个recvfrom处应会阻塞,但是没有,GetLastErrorr后显示10054,我在网上查了一下,原来是winsock自已的bug。具体原因是:http://support.microsoft.com/kb/263823/

If sending a datagram using the sendto function results in an "ICMP port unreachable" response and the select function is set for readfds, the program returns 1 and the subsequent call to the recvfrom function does not work with a WSAECONNRESET (10054) error response. In Microsoft Windows NT 4.0, this situation causes the select function to block or time out.

解决办法:在我第一部代码初始化后加入如下代码。

 DWORD dwBytesReturned = 0;
 BOOL bNewBehavior = FALSE;
 DWORD status;

#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
 // disable  new behavior using
 // IOCTL: SIO_UDP_CONNRESET
 status = WSAIoctl(m_ClientSocket, SIO_UDP_CONNRESET,
  &bNewBehavior, sizeof(bNewBehavior),
  NULL, 0, &dwBytesReturned,
  NULL, NULL);

 if (SOCKET_ERROR == status)
 {
  DWORD dwErr = WSAGetLastError();
  if (WSAEWOULDBLOCK == dwErr)
  {
   // nothing to do
   return(FALSE);
  }
  else
  {
   printf("WSAIoctl(SIO_UDP_CONNRESET) Error: %d/n", dwErr);
   return(FALSE);
  }
 }

真是受教了,这个问题调了我好久。

 

转自 : http://windows.chinaitlab.com/soft/890969.html

因为这个问题,我很想抽微软。网上其他人有类似的问题,我也碰到了,没有很好的解决方案。这个问题是Winsock所特有的(最起码所有的问题都是在Windows平台下)。一般在使用UDP Socket时,我们发出去数据,是不管它到不到达的,而且UDP并不是面向连接的,所以在收到这个错误时,会感觉很奇怪:"连接被重置10054".
   
    这个错误一般是使用UDP Socket接受时收到(这里我不讲具体的语言了,不管你用C#、Python还是C,在Windows下都会有类似的问题,只要你调用类似ReceiveFrom函数)。这是上一次Send操作向一个地址发送,但是那个地址没有Socket监听(例如对等体崩溃),那么ICMP控制协议会向我们发送一个Port Unreachable错误,当然这个错误应该包含对方的详细地址等信息,但是Winsock把这个错误转化为Connection Reset,在你下一次调用读操作的时候,引发异常,却没把详细信息给你--例如用C#接受到的对方地址是0.0.0.0.
   
    而这个问题最要命的是,如果你不采取措施,每次调用读操作都会引发该异常!
   
    唯一恢复正常的办法就是把Socket关掉,重开。
   
    这就非常要命:你要实现一个UDP服务器,把收到的音频发给所有的客户,如果某个客户崩溃了或者网络不好,你的Send不会出问题,但是你Receive的时候却出了问题!好吧,你捕获了异常,重新Receive,还是异常!好吧,你关掉Socket重新建立,但是因为不知道是哪个客户出了问题,所以不能及时把他的地址从发送列表里去除(即使使用心跳检测也要等几秒种),下次Send还是这样,你就不停地关闭创建Socket,谁受的了?
   
    网络上的讨论,最后要么说这是一个bug,要么使用如下代码(以C#为例)
   
    const int SIP_UDP_CONNRESET=-1744830452;
   
    socket.IOControl(SIP_UDP_CONNRESET, new byte[] {0, 0, 0, 0}, null);
   
    设置这个选项忽略那个Reset错误,这样不用重建Socket,只不过每次Socket都会向一些已经关闭的客户发包,浪费了服务器资源;只能通过其他机制确定客户端已经断开,延迟一段时间后才能确定哪个客户端出问题。

 

你可能感兴趣的:(UDP 10054 的故事)