linux非阻塞的socket发送数据出现EAGAIN错误的处理方法

一、非阻塞socket

        非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。 

int32_t flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);

二、EAGAIN错误
        当应用程序在socket中设置O_NONBLOCK属性后,如果发送缓存被占满,send就会返回EAGAIN或EWOULDBLOCK 的错误。在将socket设置O_NONBLOCK属性后,通过socket发送一个100K大小的数据,第一次成功发送了13140数据,之后继续发送并未成功,errno数值为EAGAIN错误。

三、EPOLL模式下EAGAIN错误处理方式
        方法:需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回发送的字节数。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试。

int32_t socket_send(int fd, char* data, int32_t size)
{
    if (NULL == data || size <= 0)
    {
        return -1;
    }
    int32_t remainded = size;
    int32_t sended = 0;
    char* pszTmp = data; 
    while(remainded > 0)
    {
        sended = send(fd, pszTmp, (size_t)remainded, 0);
        if (sended > 0)
        {
            pszTmp += sended;
            remainded -= sended;
        }
        else if (errno == EAGAIN)
        {
            continue;
        }
        else
        {
           break;
        }
    }
    return (size - remainded);
} 

       这种方式并不很完美,当发送大数据的时候,如果客户端一直不调用recv函数接受数据,那么服务器就会卡死在while循环中(持续调用send函数返回EAGAIN错误)。对服务器来说,出现这种情况是致命的,届时服务器的所有功能都不能正常运转。

       如果当send函数出现EAGAIN错误的时候,直到当前socket状态变成可写之前,不应该继续调用send函数发送数据。在发送数据之前,将socket的监听的事件增加EPOLLOUT,在数据全部发送之后,再取消EPOLLOUT的监听。
       socket监听EPOLLOUT代码:

void epoll_event_mod(int epoll_socket_fd, int fd)
{
    struct epoll_event epollEvent;
    memset(&epollEvent, 0x0, sizeo(epollEvent));
    epollEvent.data.fd = fd;
    epollEvent.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLOUT; 
    epollEvent.data.ptr = NULL;
    epoll_ctl(epoll_socket_fd, EPOLL_CTL_MOD, fd, &m_epoll_event);
}
       socket缓存结构体代码:

struct stSocketBuffer
{
    int32_t m_iHead;
    int32_t m_iTail;
    char     m_szBuffer[max_socket_buffer_size];
};
       socket待发送数据放入缓存结构代码:

int32_t push_socket_data(int fd, char* data, int32_t size)
{
    if (NULL == data || size <= 0)
    {
        return -1;
    }
    stSocketBuffer* pstBuffer = get_socket_buffer(fd);
    if (NULL == pstBuffer)
    {
        return -2;
    }
    if ( size > max_socket_buffer_size + m_iHead - m_iTail)
    {
        return -3;
    }
    if (size + m_iTail > max_socket_buffer_size)
    {
        memcopy(&pstBuffer->m_szBuffer[0], &pstBuffer->m_szBuffer[pstBuffer->m_iHead], pstBuffer->m_iTail  - pstBuffer->m_iHead);
        pstBuffer->m_iTail -= pstBuffer->m_iHead;
        pstBuffer->m_iHead = 0;
    }
    memcpy(&pstBuffer->m_szBuffer[pstBuffer->m_iTail], data, size);
    pstBuffer->m_iTail += size;
    return 0;
}
将缓存区数据发送出去代码:

int32_t socket_send(int fd)
{
    stSocketBuffer* pstBuffer = get_socket_buffer(fd);
    if (NULL == pstBuffer)
    {
        return -1;
    }

    int32_t remainded = pstBuffer->m_iTail - pstBuffer->m_iHead;
    int32_t sended = 0;
    char* pszTmp = &pstBuffer->m_szBuffer[pstBuffer->m_iHead]; 
    int32_t again_count = 0;
    while(remainded > 0 && again_count < 2)
    {
        sended = send(fd, pszTmp, (size_t)remainded, 0);
        if (sended > 0)
        {
            pstBuffer->m_iHead += sended;
            pszTmp += sended;
            remainded -= sended;
        }
        else if (errno == EAGAIN)
        {
            ++ again_count;
            continue;
        }
        else
        {
            break;
        }
    }
    return (size - remainded);
}
       总结,当需要向socket发送数据时,现将数据压入发送缓存区(stSocketBuffer结构体中),并且将socket加入可写事件监听。当socket触发可写事件(EPOLLOUT)时,调用 socket_send函数发送数据,所有数据发送完毕,再清除EPOLLOUT事件。





  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值