小弟在编写一个网络通信程序中要用到select模型,但在测试时,发现select模型在判断套接字可读的时候有问题。 客户端的一次数据发送,服务器端被认为是多次数据发送,因此就造成如下问题无法形成一次完整的数据接收,请问这是select模型本身就存在的问题还是自己对select模型理解不彻底?代码如下:
server:int main()
{
USHORT nPort = 4567; // 此服务器监听的端口号 // 创建监听套节字
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(nPort);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
// 绑定套节字到本地机器
if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf(" Failed bind() \n");
return -1;
}
// 进入监听模式
::listen(sListen, 5); // select模型处理过程
// 1)初始化一个套节字集合fdSocket,添加监听套节字句柄到这个集合
fd_set fdSocket; // 所有可用套节字集合
FD_ZERO(&fdSocket);
FD_SET(sListen, &fdSocket);
while(TRUE)
{
// 2)将fdSocket集合的一个拷贝fdRead传递给select函数,
// 当有事件发生时,select函数移除fdRead集合中没有未决I/O操作的套节字句柄,然后返回。
fd_set fdRead = fdSocket;
int nRet = ::select(0, &fdRead, NULL, NULL, NULL);
if(nRet > 0)
{
// 3)通过将原来fdSocket集合与select处理过的fdRead集合比较,
// 确定都有哪些套节字有未决I/O,并进一步处理这些I/O。
for(int i=0; i<(int)fdSocket.fd_count; i++)
{
if(FD_ISSET(fdSocket.fd_array[i], &fdRead))
{
if(fdSocket.fd_array[i] == sListen) // (1)监听套节字接收到新连接
{
if(fdSocket.fd_count < FD_SETSIZE)
{
sockaddr_in addrRemote;
int nAddrLen = sizeof(addrRemote);
SOCKET sNew = ::accept(sListen, (SOCKADDR*)&addrRemote, &nAddrLen);
FD_SET(sNew, &fdSocket);
printf("接收到连接(%s)\n", ::inet_ntoa(addrRemote.sin_addr));
}
else
{
printf(" Too much connections! \n");
continue;
}
}
else
{ char szText[256];
int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0);
if(nRecv > 0) // (2)可读
{
szText[nRecv] = '\0';
printf("接收到数据:%s \n", szText);
}
else // (3)连接关闭、重启或者中断
{
::closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i], &fdSocket);
}
}
}
}
}
else
{
printf(" Failed select() \n");
break;
}
}
return 0;
}client: 阻塞模式 send函数

解决方案 »

  1.   

    send的时候,如果数据比较多,是有可能被分解成多次发送过去的.
      

  2.   

    对TCP了解不深,TCP是流式的(没定界符),不像UDP是数据报(有严格的定界符)
      

  3.   

    TCP协议在发送和接收时,会对数据进行重新组合,可能多次发送的数据一起接收到,也可能一次发送的数据会分多次到达,每次只能接收到一部分,TCP保证数据有序到达,但发送与接收不是一一对应的,在程序设计上应该多次接收,直到所需数据全部收到(或遇到错误)为止。
      

  4.   

    目前我还没有考虑这个问题,但我想应该socket api提供的函数应该具备这些功能。
      

  5.   

    是你的理解不彻底,Socket的send和recv并不是说客户端一次send多少字节,服务器不能接收到多少字节,所以无论接收还是发送都是循环接收和发送。
    你的代码:
    int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0); 
    这个应该是循环接收,因为有时网络影响不一定一次性全部接收完数据。
      

  6.   

    TCP协议是面向流的协议, 不是面向消息协议。TCP协议只确保发送出去的码流正确且有序的被上层应用收到,但是没有定义是一个消息一个消息组包好了再让上层应用接受。select / socket 框架也支持SCTP协议,这个协议是一个类TCP协议,目前在电信领域被广泛运用,如果创建socket时制定用SCTP协议进行传输,可以保证消息收到的时候是经过组包的,也就是收到的是一个完整的消息,不会被中间截断。
    如果一定要用TCP协议,则需要自己封装一层组包的代码,这种代码网上很多,自己写也不难,大体思路是消息采用  长度+内容的格式,每次对socket进行预读,根据长度字段,读满一个消息后,再将消息发到上层。
      

  7.   

    顶楼上解决TCP粘包问题是基础,做法楼上有提。基本上网络上代码很多,但完全解决这问题的很少。多看,多想,多写,多跟踪调试。
      

  8.   

    你要自己控制接收的长度和发送的长度,不能依靠api自己
      

  9.   

    类似SOAP,加包长校验,循环收发。