有什么方法来判断客户端处于连接但是没有发送信息吗

lololyuu 2024-07-23 13:58:20

最近在整tcp连接,需要实现对客户端处于连接没有断开,但是也没有发送消息的状态时的判断。我发现Receive()函数在客户端未发送消息时并不返回0?

有没有会的大佬指点一下

...全文
707 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复

在TCP通信中,判断客户端是否处于连接但没有发送消息的状态是一个常见的需求。recv()函数在客户端未发送消息时并不会返回0,除非连接被关闭(即对方调用了close()或类似的操作)。在这种情况下,可以采用以下几种方法来检测这种“连接但没有发送数据”的情况。

方法一:使用SO_RCVTIMEO设置接收超时

通过设置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;

    // 创建套接字
![配图](https://hunyuan-prod-1258344703.cos.ap-guangzhou.myqcloud.com/text2img/de4afd800048ccc92ecb6920a5a0fe3b/20250124092321h0_512c5cdc560832e428d4e6ce0b3d588867b.png?q-sign-algorithm=sha1&q-ak=AKIDRl074nOsGdJ9zjMsCRWP3ShmgS3VtX4S&q-sign-time=1737681801;1769217801&q-key-time=1737681801;1769217801&q-header-list=host&q-url-param-list=&q-signature=1daa9d0e9fc1733ca5fd2e008600ba3e7c0b777d)
    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");
![配图](https://hunyuan-prod-1258344703.cos.ap-guangzhou.myqcloud.com/text2img/de4afd800048ccc92ecb6920a5a0fe3b/20250124092322h0_983cfc8b19afae708bca778436859eaf86c.png?q-sign-algorithm=sha1&q-ak=AKIDRl074nOsGdJ9zjMsCRWP3ShmgS3VtX4S&q-sign-time=1737681802;1769217802&q-key-time=1737681802;1769217802&q-header-list=host&q-url-param-list=&q-signature=0db1740481c99fef75ffdc9d82b640461c8e6d09)
        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秒
![配图](https://hunyuan-prod-1258344703.cos.ap-guangzhou.myqcloud.com/text2img/de4afd800048ccc92ecb6920a5a0fe3b/20250124092322h0_9346c75d88695a7d050307c8e9e5c725a03.png?q-sign-algorithm=sha1&q-ak=AKIDRl074nOsGdJ9zjMsCRWP3ShmgS3VtX4S&q-sign-time=1737681802;1769217802&q-key-time=1737681802;1769217802&q-header-list=host&q-url-param-list=&q-signature=d8499ebba0e0232501826e651f53f51917419dea)
    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;
}

方法二:心跳机制(Heartbeat Mechanism)

客户端和服务器之间可以定期发送心跳信息,例如每分钟发送一次“Hello”消息。如果客户端在一定时间内没有发送心跳信息,服务器可以认为客户端已经断开连接。

方法三:PING/PONG机制

客户端和服务器之间可以交换PING和PONG消息。客户端发送PING消息到服务器,服务器回应PONG消息。如果客户端在一定时间内没有收到PONG消息,或者客户端没有发送PING消息,服务器可以认为客户端已经断开连接。

方法四:超时机制(Timeout Mechanism)

服务器可以设置一个超时时间,如果客户端在这个时间内没有发送任何信息,服务器可以认为客户端已经断开连接。

方法五:使用TCP的Keep-Alive机制

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/IP详解 卷1:协议》:适合深入了解TCP/IP协议的读者,内容详尽,但需要一定的基础知识。
  • 《UNIX网络编程》:适合学习网络编程的读者,书中详细讲解了UNIX环境下的网络编程技术,但主要针对UNIX系统。
  • 《C#网络编程实战》:适合C#开发者学习网络编程,书中通过实例讲解了如何使用C#进行网络编程,适合快速上手。
  • 《网络是怎样连接的》:适合初学者了解网络基础知识,书中用通俗易懂的语言讲解了网络通信的基本原理和实现方法。
  • 《深入理解计算机系统》:适合深入学习计算机系统的读者,书中详细讲解了计算机系统的硬件和软件交互,包括网络通信的底层实现,但内容较为深入。

通过以上方法和书籍,可以有效地判断客户端是否处于连接但没有发送消息的状态,并深入学习相关的网络编程知识。

已隐藏部分内容,更多查看原文

从以前 01-04
  • 打赏
  • 举报
回复

在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。

初级代码游戏 2024-08-11
  • 打赏
  • 举报
回复

要正确理解网络连接。名为“连接”其实并没有真正连接,并没有一根线连接起来,所谓连接是否正常还是靠应答来确认的,你等待数据的时候无法知道对方是否还在。TCP协议的keep-alive机制希望从系统层面解决服务器连接的失效问题(以便清除失效的客户端连接),方法就是发包确认,但是过程很漫长,最终报告连接断开可能要2小时,所以应用程序会自己搞个心跳机制,比如5分钟一次,这就可接受了。

2301_79386209 2024-07-26
  • 打赏
  • 举报
回复

学习一下

by_封爱 版主 2024-07-25
  • 打赏
  • 举报
回复

你的问题在于.. 如果客户端真断开了. 立马告诉你 是不是就没这个问题了?

不管对端是异常关闭 或无网络 或信号不好 甚至是拔网线.. 如果你第一时间就知道. 那么就没有后续的事了.

因为你监听到了 状态就是"离线" 至于其他的 都不关心.

我这里有一个方法

        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连接服务端并实现远程操作.

然后发现有的时候 设备虽然:"在线" 但是我发消息没反映. 而他又说他没收到消息. 后来发现可能是网络的问题.就是各种测试. 包括断网 拔网线 什么的

最终就搜索到了这套代码.这样对方在异常的时候 我就立马知道了

寂然如故 2024-07-23
  • 打赏
  • 举报
回复

心跳机制(Heartbeat mechanism):客户端和服务器之间可以定期发送心跳信息,例如每分钟发送一次“Hello”消息。如果客户端在一定时间内没有发送心跳信息,服务器可以认为客户端已经断开连接。
PING/PONG机制:客户端和服务器之间可以交换PING和PONG消息。客户端发送PING消息到服务器,服务器回应PONG消息。如果客户端在一定时间内没有收到PONG消息,或者客户端没有发送PING消息,服务器可以认为客户端已经断开连接。
超时机制(Timeout mechanism):服务器可以设置一个超时时间,如果客户端在这个时间内没有发送任何信息,服务器可以认为客户端已经断开连接。

wanghui0380 2024-07-23
  • 打赏
  • 举报
回复 1

最后补充一条
Receive()是同步阻塞方法,结果是4个
1.缓存有数据,返回给你数据
2.缓存没有数据,他不返回,他同步阻塞,等待缓冲数据
3. 对方主动断开,socket释放,触发异常
4.长时间无窗口数据,触发保活探测,探测失败,socket释放。触发异常

所以。你看到了。如果能执行到一句代码就是要么有数据,要么异常。为0时他其实阻塞了没有返回。

lololyuu 2024-07-24
  • 举报
回复
@wanghui0380 哇塞,谢谢大佬,受益匪浅
wanghui0380 2024-07-23
  • 打赏
  • 举报
回复

另外解释一下,为啥不是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%了

wanghui0380 2024-07-23
  • 打赏
  • 举报
回复

还有一种方式,我前面不提是因为如果你对tcp不熟悉不建议那样,但我们还是顺带提提

tcp其实自己有默认保活机制,也就是如果他完全没有任何数据了(包括内部tcp的握手消息syn)他会自动发起3个询问探测,如果依旧没消息,他会自动断开并发异常给你。只不过这个默认保活时长非常长默认是90分钟。
我们可以修改这个默认保活时长到你觉着合适的时长,比如5分钟。你5分钟没有任何数据,他自己断,不需要你处理了

当然我们说如果你对tcp不熟,我们不建议这样。同时这个保活的机制条件是网络完全没消息,包括内部的状态syn消息,并不是你说对方的逻辑消息,如果你是判定逻辑消息,还是自己实现Session会话过期依赖比较符合要求

wanghui0380 2024-07-23
  • 打赏
  • 举报
回复

如果你们没手动弄啥“心跳包”的情况,只需要简单下一个时间依赖缓存。用RX这类也可以。
rx.timeout(timespan.fromxxxx(1)).处理error() //大概的代码描述,意思是如果约定时间没有数据会异常,你处理这个异常。

如果你不理解我在说啥,可以参考传统asp.net的session,传统的session其实就是你要的,你不继续动作默认20分钟就过期了

同样看到Receive()描述时候,我们也不得不多提一下,某园子自己都玩不下去,他那些文章还是少看。我们现在可不建议园子的那些博文的 sleep(100) Receive()。
哎·,园子埋怨百度降他权,哪是百度降他权啊。是俺们程序员圈子降他权,那些文章一代一代的坑,坑了一批又一批。俺们也不得不常年累月的帮他填坑。

111,076

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧