socket select模型 判断接套接字可读问题

时间:2021-01-08 11:02:30
小弟在编写一个网络通信程序中要用到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函数

14 个解决方案

#1


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

#2


客户端的一次数据发送代码也贴上吧。

#3


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

#4


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

#5


你是如何确认数据客户端是一次性发完数据的?

#6


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

#7


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

#8


TCP协议是面向流的协议, 不是面向消息协议。

TCP协议只确保发送出去的码流正确且有序的被上层应用收到,但是没有定义是一个消息一个消息组包好了再让上层应用接受。

select / socket 框架也支持SCTP协议,这个协议是一个类TCP协议,目前在电信领域被广泛运用,如果创建socket时制定用SCTP协议进行传输,可以保证消息收到的时候是经过组包的,也就是收到的是一个完整的消息,不会被中间截断。


如果一定要用TCP协议,则需要自己封装一层组包的代码,这种代码网上很多,自己写也不难,大体思路是消息采用  长度+内容的格式,每次对socket进行预读,根据长度字段,读满一个消息后,再将消息发到上层。

#9


顶楼上

解决TCP粘包问题是基础,做法楼上有提。基本上网络上代码很多,但完全解决这问题的很少。

多看,多想,多写,多跟踪调试。

#10


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

#11


引用楼主 scorpio_tiger 的帖子:
小弟在编写一个网络通信程序中要用到select模型,但在测试时,发现select模型在判断套接字可读的时候有问题。 客户端的一次数据发送,服务器端被认为是多次数据发送,因此就造成如下问题无法形成一次完整的数据接收,请问这是select模型本身就存在的问题还是自己对select模型理解不彻底?代码如下: 
server: 

int main() 

USHORT nPort = 4567; // 此服务器监听的端口号 

// 创建监听套节字 
SOCKET sListen = ::s…


你用哪种模型都出现这情况.这很大程度是由于发送方和接收方之间的网络环境,或两方系统硬件资源等限制.
1.发送方带宽很高,发送速度很快,但接收方带宽很低,这种不平衡就可以导致发送方将大量数据都发送成功到接收方,但接收方需要慢慢的接收(由于带宽问题)
2.接收方的受制于可用资源(例如当前很多网络程序在收发数据),那么到达接收方的数据,由于系统内核缓冲区不能同时处理完,也会被分开几次处理完.

在理想环境中(足够资源,足够带宽环境中),很少出现你所说情况(你可以在自己的内网测试下,内网的环境就理想很多了).但实际的网络,根本不存在"理想状态".

#12


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

#13


自己在发送数据的时候加上包头。。
包头是固定大小的 里面标示了接下来数据的大小
读的时候先读头(头的大小固定)根据头中的 数据大小 读接下来的数据
就OK了

#14


楼上正解!

#1


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

#2


客户端的一次数据发送代码也贴上吧。

#3


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

#4


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

#5


你是如何确认数据客户端是一次性发完数据的?

#6


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

#7


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

#8


TCP协议是面向流的协议, 不是面向消息协议。

TCP协议只确保发送出去的码流正确且有序的被上层应用收到,但是没有定义是一个消息一个消息组包好了再让上层应用接受。

select / socket 框架也支持SCTP协议,这个协议是一个类TCP协议,目前在电信领域被广泛运用,如果创建socket时制定用SCTP协议进行传输,可以保证消息收到的时候是经过组包的,也就是收到的是一个完整的消息,不会被中间截断。


如果一定要用TCP协议,则需要自己封装一层组包的代码,这种代码网上很多,自己写也不难,大体思路是消息采用  长度+内容的格式,每次对socket进行预读,根据长度字段,读满一个消息后,再将消息发到上层。

#9


顶楼上

解决TCP粘包问题是基础,做法楼上有提。基本上网络上代码很多,但完全解决这问题的很少。

多看,多想,多写,多跟踪调试。

#10


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

#11


引用楼主 scorpio_tiger 的帖子:
小弟在编写一个网络通信程序中要用到select模型,但在测试时,发现select模型在判断套接字可读的时候有问题。 客户端的一次数据发送,服务器端被认为是多次数据发送,因此就造成如下问题无法形成一次完整的数据接收,请问这是select模型本身就存在的问题还是自己对select模型理解不彻底?代码如下: 
server: 

int main() 

USHORT nPort = 4567; // 此服务器监听的端口号 

// 创建监听套节字 
SOCKET sListen = ::s…


你用哪种模型都出现这情况.这很大程度是由于发送方和接收方之间的网络环境,或两方系统硬件资源等限制.
1.发送方带宽很高,发送速度很快,但接收方带宽很低,这种不平衡就可以导致发送方将大量数据都发送成功到接收方,但接收方需要慢慢的接收(由于带宽问题)
2.接收方的受制于可用资源(例如当前很多网络程序在收发数据),那么到达接收方的数据,由于系统内核缓冲区不能同时处理完,也会被分开几次处理完.

在理想环境中(足够资源,足够带宽环境中),很少出现你所说情况(你可以在自己的内网测试下,内网的环境就理想很多了).但实际的网络,根本不存在"理想状态".

#12


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

#13


自己在发送数据的时候加上包头。。
包头是固定大小的 里面标示了接下来数据的大小
读的时候先读头(头的大小固定)根据头中的 数据大小 读接下来的数据
就OK了

#14


楼上正解!