bufferevent客户端发送文件并处理超时和断开事件

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <event2/event_struct.h>
#include <fcntl.h>
#include <event.h>
#include <event2/listener.h>
#include <event2/bufferevent_struct.h>
#include <event2/event-config.h>
#include <event2/event.h>
#include <errno.h>
#include <sys/socket.h>
#include <string.h>

//bufferevent_settimeout();设置断开事件
void read_cb(struct bufferevent *bev, void *arg)
{
    printf("read\n");
}
void write_cb(struct bufferevent *bev, void *arg)
{
    printf("write_cb\n");
    //
    FILE *fp=(FILE*)arg;
    char data[1024]={0};
    int len=fread(data,1,sizeof(data)-1,fp);
    if(len <=0)
    {
        fclose(fp);
        //立刻清零可能回造成缓冲数据没有发送请求
       // bufferevent_free(bev);
        bufferevent_disable(bev,EV_WRITE);
        return ;
    }else printf("%s\n",data);
    //输出到buffer缓冲
    //bufferevent_write(bev,data,len);
}
void event_cb(struct bufferevent *bev, short what, void *arg)
{
     printf("event_cb\n");
     if(what&BEV_EVENT_TIMEOUT&&what&BEV_EVENT_READING)
     {
         printf("time out\n");
         bufferevent_free(bev);
         return;
     }else if(what &BEV_EVENT_ERROR){
                  printf("BEV_EVENT_ERROR\n");

         bufferevent_free(bev);
         return;

     }else if(what &BEV_EVENT_EOF)
     {//服务器关闭
         printf("server close\n");
         bufferevent_free(bev);
     }
     if(what&BEV_EVENT_CONNECTED)
     {//成功连接
         printf("connect sueccess\n");
//触发write
        bufferevent_trigger(bev,EV_WRITE,0);
     }
}
void listeb_cb(struct evconnlistener *ev, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)

{

}
int main(int argc,char *argv[])
{

    struct event_config *evconfig=event_config_new();

    struct event_base *base=event_base_new_with_config(evconfig);
    event_config_free(evconfig);//释放配置
    if(!base)
    {
        perror("event base error");
        exit(1);
    }
    if(signal(SIGPIPE,SIG_IGN)==SIG_ERR)
        return 1;
    // 获取当前网络模型
    printf("current net methods:%s\n",    event_base_get_method(base));
    //-1内部创建socket
    struct bufferevent *bev=bufferevent_socket_new(base,-1,BEV_OPT_CLOSE_ON_FREE);

    struct sockaddr_in sin;
    memset(&sin,0,sizeof(sin));
    sin.sin_family=AF_INET;
    sin.sin_port=htons(8888);
   // sin.sin_addr.s_addr=inet_addr("127.0.0.1");
    evutil_inet_pton(AF_INET,"127.0.0.1",&sin.sin_addr.s_addr);
    FILE *fp=fopen("client.c","rb");
    //设置回调函数
    bufferevent_setcb(bev,read_cb,write_cb,event_cb,fp);
     bufferevent_enable(bev,EV_READ|EV_WRITE);
     int ret=bufferevent_socket_connect(bev,(struct sockaddr*)&sin,sizeof(sin));
     if(ret<0)
     {
         printf("connect error\n");
         exit(1);

     }

    event_base_dispatch(base);
    event_base_free(base);



}

<think>我们之前已经讨论过recv函数的行为,现在详细解释在不同情况下的表现: 1. 在没有设置超时(阻塞模式)时: - 如果客户端正常断开发送FIN包),recv会返回0。 - 如果客户端异常断开(如网络中断),recv可能会一直阻塞,直到有数据到来才会发现错误(例如返回-1,错误码为ECONNRESET等)。 - 如果没有数据到来,recv会一直阻塞直到有数据到达。 2. 在设置超时(通过SO_RCVTIMEO)后: - 在超时时间内,如果客户端断开(正常或异常),recv会立即返回0(正常断开)或负值(异常断开设置错误码)。 - 在超时时间内没有数据且客户端断开,recv会阻塞直到超时,然后返回-1,设置错误码为EAGAIN或EWOULDBLOCK(在Linux)或WSAETIMEDOUT(在Windows)。 注意:在非阻塞模式下(通过fcntl或ioctlsocket设置O_NONBLOCK/FIONBIO),recv的行为是: - 如果没有数据可用,则立即返回-1,设置错误码为EAGAIN/EWOULDBLOCK(Linux)或WSAEWOULDBLOCK(Windows)。 - 如果有数据,则返回接收到的字节数。 - 如果连接断开,则返回0(正常关闭)或负值(异常断开)。 现在,我们将这些情况整理成表格: | 情况 | 阻塞模式(无超时) | 超时模式(设置SO_RCVTIMEO) | 非阻塞模式 | |--------------------------------|------------------------------|----------------------------------------|--------------------------| | 正常收到数据 | >0(接收字节数) | >0(接收字节数) | >0(接收字节数) | | 客户端正常断开(FIN) | 返回0 | 返回0(即使超时未到也会立即返回) | 返回0 | | 客户端异常断开(如RST) | 返回-1,错误码指示错误 | 返回-1,错误码指示错误 | 返回-1,错误码指示错误 | | 无数据(连接正常) | 阻塞直到有数据或断开 | 阻塞直到超时,返回-1(错误码为超时) | 立即返回-1(错误码为EAGAIN/EWOULDBLOCK)| 注意:在超时模式下,如果超时时间内发生断开,会立即返回(不会等到超时时间结束)。这是因为断开事件会立即中断等待。 因此,在代码中处理recv时,我们通常这样判断: - 返回值>0:正常数据 - 返回值==0:客户端正常关闭 - 返回值<0: - 如果是超时错误(EAGAIN/EWOULDBLOCK或WSAETIMEDOUT),可以继续重试(如果是超时设置引起的)或做其他处理 - 其他错误则视为异常断开 在之前的代码示例中,我们设置了超时(5秒),因此如果5秒内没有数据,recv会返回-1,设置错误码为超时,然后我们根据错误码决定是继续还是断开。 下面是一个修正后的代码片段,展示如何区分这些情况: ```cpp // 设置接收超时(5秒) struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; setsockopt(clientSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); while (true) { int n = recv(clientSocket, buffer, sizeof(buffer), 0); if (n > 0) { // 处理数据 } else if (n == 0) { // 客户端正常关闭 break; } else { // n < 0 #ifdef _WIN32 int error = WSAGetLastError(); if (error == WSAETIMEDOUT) { // 超时,可以继续等待或发送心跳包检查 continue; } else { // 其他错误,断开连接 break; } #else if (errno == EAGAIN || errno == EWOULDBLOCK) { // 超时,继续等待 continue; } else { break; } #endif } } ``` 注意:在超时的情况下,我们选择继续循环(即再次调用recv),但实际中可能会在超时一定次数后断开,或者发送心跳包检查连接是否存活。 另外,关于客户端断开:无论是正常断开发送FIN)还是异常断开(如RST),在调用recv时都会返回(0或-1),而不会一直阻塞(在阻塞模式下,如果没有设置超时,异常断开可能不会立即被发现,除非有数据发送)。 因此,设置超时是一种常见的做法,以避免客户端无响应时服务器线程被无限期阻塞。 §§相关问题§§ 1. 在 TCP 协议中,客户端正常断开发送 FIN 包)异常断开(如 RST 包)有什么区别? 2. 设置 SO_RCVTIMEO 超时选项后,recv 函数在超时时间内检测到客户端断开连接会立即返回吗? 3. 除了设置套接字超时,还有哪些方法可以实现非阻塞网络通信检测连接状态? 4. 在 C++ 网络编程中,如何处理 recv 返回的负值错误码以区分不同的网络错误? 5. 如何设计一个心跳机制来检测长时间没有数据交互的客户端连接是否存活? </think>### 回答 `recv()` 函数的行为在不同场景下有显著差异,具体表现如下表所示(以 Linux 系统为例): | **场景** | **无超时设置(阻塞模式)** | **有超时设置(SO_RCVTIMEO)** | |-----------------------------------|----------------------------------------|-----------------------------------------| | **客户端正常断开**<br>(发送 FIN 包) | 立即返回 `0` | 立即返回 `0` | | **客户端异常断开**<br>(如断网/崩溃) | TCP 重传超时后返回 `-1`<br>(约 20 分钟) | 在设定超时后返回 `-1`<br>(errno=ETIMEDOUT) | | **无数据到达**<br>(连接保持) | 永久阻塞 | 超时后返回 `-1`<br>(errno=EAGAIN) | | **收到完整数据** | 立即返回接收字节数 `>0` | 立即返回接收字节数 `>0` | | **收到部分数据** | 立即返回已接收字节数 `>0` | 立即返回已接收字节数 `>0` | #### 关键行为解析 1. **客户端断开检测** - **立即响应**:操作系统内核在接收到 FIN/RST 包时会立即中断 `recv()` 等待 - **返回值差异**: ```cpp if (recv() == 0) // 客户端正常关闭 if (recv() == -1) // 客户端异常关闭或超时 ``` 2. **超时控制机制** - **设置方法**(Linux示例): ```cpp struct timeval tv{5, 0}; // 5秒超时 setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); ``` - **精确度**:实际超时可能有 ±10% 的误差,受系统调度影响 3. **阻塞风险规避** ```cpp // 最佳实践:三重检测逻辑 int bytes = recv(sockfd, buf, len, 0); if (bytes > 0) { // 正常数据处理 } else if (bytes == 0) { // 客户端主动断开连接 } else { // bytes < 0 if (errno == EINTR) continue; // 被信号中断 if (errno == EAGAIN) continue; // 超时无数据 // 处理其他错误(ECONNRESET/ENOTCONN) } ``` #### 底层原理说明 - **内核缓冲区机制**: - 当接收缓冲区有数据时,`recv()` 立即返回 - 缓冲区空时进入休眠状态(被移出 CPU 调度队列) - **TCP 状态机干预**: - 收到 FIN 包 → 将套接字标记为 `CLOSE_WAIT` - 收到 RST 包 → 强制终止连接释放资源 - **超时实现**:内核使用 `hrtimer` 高精度定时器触发唤醒 #### 特殊场景处理 1. **同时发生断开超时**: - 断开事件优先级更高,`recv()` 会立即返回 2. **信号中断**: ```cpp // 重启被中断的系统调用 while ((bytes = recv(sockfd, buf, len, 0)) == -1) { if (errno != EINTR) break; // 非中断错误退出 } ``` 3. **非阻塞模式差异**: - 设置 `fcntl(sockfd, F_SETFL, O_NONBLOCK)` - 无数据时立即返回 `-1`,`errno=EAGAIN`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值