111,076
社区成员




最近在整tcp连接,需要实现对客户端处于连接没有断开,但是也没有发送消息的状态时的判断。我发现Receive()函数在客户端未发送消息时并不返回0?
有没有会的大佬指点一下
在TCP通信中,判断客户端是否处于连接但没有发送消息的状态是一个常见的需求。recv()
函数在客户端未发送消息时并不会返回0,除非连接被关闭(即对方调用了close()
或类似的操作)。在这种情况下,可以采用以下几种方法来检测这种“连接但没有发送数据”的情况。
通过设置recv()
函数的超时时间,可以检测一定时间内是否有数据到达。以下是一个示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char buffer[1024];
struct timeval timeout;
// 创建套接字

server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Socket creation failed");
exit(1);
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_fd);
exit(1);
}
// 监听
if (listen(server_fd, 5) < 0) {
perror("Listen failed");

close(server_fd);
exit(1);
}
printf("Waiting for connections...
");
// 接受客户端连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("Accept failed");
close(server_fd);
exit(1);
}
printf("Client connected.
");
// 设置接收超时时间
timeout.tv_sec = 5; // 超时时间:5秒

timeout.tv_usec = 0; // 微秒部分
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
// 接收数据
while (1) {
memset(buffer, 0, sizeof(buffer));
int ret = recv(client_fd, buffer, sizeof(buffer), 0);
if (ret > 0) {
printf("Received message: %s
", buffer);
} else if (ret == 0) {
printf("Client disconnected.
");
break;
} else {
perror("No data received (timeout or error)");
// 检测到超时或错误
break;
}
}
close(client_fd);
close(server_fd);
return 0;
}
客户端和服务器之间可以定期发送心跳信息,例如每分钟发送一次“Hello”消息。如果客户端在一定时间内没有发送心跳信息,服务器可以认为客户端已经断开连接。
客户端和服务器之间可以交换PING和PONG消息。客户端发送PING消息到服务器,服务器回应PONG消息。如果客户端在一定时间内没有收到PONG消息,或者客户端没有发送PING消息,服务器可以认为客户端已经断开连接。
服务器可以设置一个超时时间,如果客户端在这个时间内没有发送任何信息,服务器可以认为客户端已经断开连接。
TCP协议本身提供了Keep-Alive机制,可以在一段时间内没有数据传输时自动发送探测包。可以通过设置setsockopt
函数来启用和配置Keep-Alive参数。
public static byte[] KeepAlive(int onOff, int keepAliveTime, int keepAliveInterval) {
byte[] buffer = new byte[12];
BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
return buffer;
}
推荐书籍 | 图书特点 |
---|---|
《TCP/IP详解 卷1:协议》 | 作者:W. Richard Stevens,出版社:机械工业出版社,适合深入了解TCP/IP协议的读者。内容详尽,涵盖了从基础到高级的协议知识。 |
《UNIX网络编程》 | 作者:W. Richard Stevens,出版社:清华大学出版社,适合学习网络编程的读者。书中详细讲解了UNIX环境下的网络编程技术,包括TCP/IP协议栈的实现。 |
《C#网络编程实战》 | 作者:John Sharp,出版社:人民邮电出版社,适合C#开发者学习网络编程。书中通过实例讲解了如何使用C#进行网络编程,包括TCP和UDP协议的实现。 |
《网络是怎样连接的》 | 作者:户根勤,出版社:人民邮电出版社,适合初学者了解网络基础知识。书中用通俗易懂的语言讲解了网络通信的基本原理和实现方法。 |
《深入理解计算机系统》 | 作者:Randal E. Bryant,David R. O'Hallaron,出版社:机械工业出版社,适合深入学习计算机系统的读者。书中详细讲解了计算机系统的硬件和软件交互,包括网络通信的底层实现。 |
通过以上方法和书籍,可以有效地判断客户端是否处于连接但没有发送消息的状态,并深入学习相关的网络编程知识。
在TCP通信中,判断客户端是否处于连接但没有发送消息的状态是一个常见的需求。recv() 函数在客户端未发送消息时确实不会返回 0,除非连接被关闭(也就是对方调用了 close() 或类似的操作)。在这种情况下,你可以采用一些常见的方法来检测这种“连接但没有发送数据”的情况。
方法一:使用 SO_RCVTIMEO 设置接收超时
通过设置 recv() 函数的超时时间,你可以检测一定时间内是否有数据到达。
c
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char buffer[1024];
struct timeval timeout;
// 创建套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Socket creation failed");
exit(1);
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_fd);
exit(1);
}
// 监听
if (listen(server_fd, 5) < 0) {
perror("Listen failed");
close(server_fd);
exit(1);
}
printf("Waiting for connections...\n");
// 接受客户端连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("Accept failed");
close(server_fd);
exit(1);
}
printf("Client connected.\n");
// 设置接收超时时间
timeout.tv_sec = 5; // 超时时间:5秒
timeout.tv_usec = 0; // 微秒部分
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
// 接收数据
while (1) {
memset(buffer, 0, sizeof(buffer));
int ret = recv(client_fd, buffer, sizeof(buffer), 0);
if (ret > 0) {
printf("Received message: %s\n", buffer);
} else if (ret == 0) {
printf("Client disconnected.\n");
break;
} else {
perror("No data received (timeout or error)");
// 检测到超时或错误
break;
}
}
close(client_fd);
close(server_fd);
return 0;
}
关键点:
设置 SO_RCVTIMEO 超时时间(单位为秒+微秒)。
如果 recv() 在超时时间内没有接收到数据,会返回 -1 并设置 errno 为 EWOULDBLOCK 或 EAGAIN。
要正确理解网络连接。名为“连接”其实并没有真正连接,并没有一根线连接起来,所谓连接是否正常还是靠应答来确认的,你等待数据的时候无法知道对方是否还在。TCP协议的keep-alive机制希望从系统层面解决服务器连接的失效问题(以便清除失效的客户端连接),方法就是发包确认,但是过程很漫长,最终报告连接断开可能要2小时,所以应用程序会自己搞个心跳机制,比如5分钟一次,这就可接受了。
学习一下
你的问题在于.. 如果客户端真断开了. 立马告诉你 是不是就没这个问题了?
不管对端是异常关闭 或无网络 或信号不好 甚至是拔网线.. 如果你第一时间就知道. 那么就没有后续的事了.
因为你监听到了 状态就是"离线" 至于其他的 都不关心.
我这里有一个方法
public static byte[] KeepAlive(int onOff, int keepAliveTime, int keepAliveInterval)
{
byte[] buffer = new byte[12];
BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
return buffer;
}
在客户端发起连接的时候. 在服务端一定会有一个OnConnection的事件(没有封装就是第一次发消息吧).参数为SocketAsyncEventArgs
然后调用当前对象的一个方法.如下
ar.AcceptSocket.IOControl(IOControlCode.KeepAliveValues, KeepAlive(1, 1000, 1000), null);
1秒掉线 1秒检测
当客户端的对象有了这个属性(什么属性我也不清楚)之后,.对方掉线的话 你会里面收到通知.(封装过的就是OnClose事件,在你集合中移除或标识成离线)
屡试不爽. 虽然这代码很久了..得有个10年了吧. 当时就是硬件芯片(名字好像是什么esp8266)使用tcp连接服务端并实现远程操作.
然后发现有的时候 设备虽然:"在线" 但是我发消息没反映. 而他又说他没收到消息. 后来发现可能是网络的问题.就是各种测试. 包括断网 拔网线 什么的
最终就搜索到了这套代码.这样对方在异常的时候 我就立马知道了
心跳机制(Heartbeat mechanism):客户端和服务器之间可以定期发送心跳信息,例如每分钟发送一次“Hello”消息。如果客户端在一定时间内没有发送心跳信息,服务器可以认为客户端已经断开连接。
PING/PONG机制:客户端和服务器之间可以交换PING和PONG消息。客户端发送PING消息到服务器,服务器回应PONG消息。如果客户端在一定时间内没有收到PONG消息,或者客户端没有发送PING消息,服务器可以认为客户端已经断开连接。
超时机制(Timeout mechanism):服务器可以设置一个超时时间,如果客户端在这个时间内没有发送任何信息,服务器可以认为客户端已经断开连接。
最后补充一条
Receive()是同步阻塞方法,结果是4个
1.缓存有数据,返回给你数据
2.缓存没有数据,他不返回,他同步阻塞,等待缓冲数据
3. 对方主动断开,socket释放,触发异常
4.长时间无窗口数据,触发保活探测,探测失败,socket释放。触发异常
所以。你看到了。如果能执行到一句代码就是要么有数据,要么异常。为0时他其实阻塞了没有返回。
另外解释一下,为啥不是0的问题
如果你用Receive(),那么大概率是故园的代码
while(true)
{ byte[] buffer=byte[1024]
Receive()
sleep(100)
}
这代码其实坑多的狠
优先解释不是0的问题,1024最大1024,如果对方发给你的是1026,你一次取不完。或者tcp移动窗口就不是一次过来的。(有关tcp移动窗口我不解释你可以自己查资料),此时可能不为0.当然如果你们手动弄了“心跳包”他可能永远不会为0
在说其他的坑
1.1024,每次都分1024?系统需要频繁的分配,释放内存。同时按你的描述预估你是服务器,那么100连接就是100*1024,而且还是没100ms就要重新分配一次这么大的内存。这种方式做演示demo可以,但用在实际生产环境,指不定啥时候内存就爆了
2. Receive()同步处理。按园子的写法,估计还是线程。那么100个连接就是100个线程,而是还是火力全开的线程。另外按园子的一贯写法还要另开一个线程去拼接解析封包,所以内存翻倍,线程翻倍。所以实际生产环境,指不定啥时候cpu 100%了
还有一种方式,我前面不提是因为如果你对tcp不熟悉不建议那样,但我们还是顺带提提
tcp其实自己有默认保活机制,也就是如果他完全没有任何数据了(包括内部tcp的握手消息syn)他会自动发起3个询问探测,如果依旧没消息,他会自动断开并发异常给你。只不过这个默认保活时长非常长默认是90分钟。
我们可以修改这个默认保活时长到你觉着合适的时长,比如5分钟。你5分钟没有任何数据,他自己断,不需要你处理了
当然我们说如果你对tcp不熟,我们不建议这样。同时这个保活的机制条件是网络完全没消息,包括内部的状态syn消息,并不是你说对方的逻辑消息,如果你是判定逻辑消息,还是自己实现Session会话过期依赖比较符合要求
如果你们没手动弄啥“心跳包”的情况,只需要简单下一个时间依赖缓存。用RX这类也可以。
rx.timeout(timespan.fromxxxx(1)).处理error() //大概的代码描述,意思是如果约定时间没有数据会异常,你处理这个异常。
如果你不理解我在说啥,可以参考传统asp.net的session,传统的session其实就是你要的,你不继续动作默认20分钟就过期了
同样看到Receive()描述时候,我们也不得不多提一下,某园子自己都玩不下去,他那些文章还是少看。我们现在可不建议园子的那些博文的 sleep(100) Receive()。
哎·,园子埋怨百度降他权,哪是百度降他权啊。是俺们程序员圈子降他权,那些文章一代一代的坑,坑了一批又一批。俺们也不得不常年累月的帮他填坑。