FD_WRITE 事件的触发时机

本文详细解析了网络编程中FD_WRITE事件的触发机制及其应用场景,包括客户端和服务端建立连接时的触发条件,以及发送数据过程中缓冲区满时如何处理。
 

常见的网络事件中,FD_ACCEPT和FD_READ都比较好理解。一开始我唯一困惑的就是FD_WRITE,搞不清楚到底什么时候才会触发这个网络事件,后来仔细查了MSDN又看了一些文章并测试了下,终于搞懂了FD_WRITE的触发机制。

下面是MSDN中对FD_WRITE触发机制的解释:

The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with connect/WSAConnect or accepted with accept/WSAAccept, and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set

FD_WRITE事件只有在以下三种情况下才会触发

①client 通过connect(WSAConnect)首次和server建立连接时,在client端会触发FD_WRITE事件

②server通过accept(WSAAccept)接受client连接请求时,在server端会触发FD_WRITE事件

③send(WSASend)/sendto(WSASendTo)发送失败返回WSAEWOULDBLOCK,并且当缓冲区有可用空间时,则会触发FD_WRITE事件

①②其实是同一种情况,在第一次建立连接时,C/S端都会触发一个FD_WRITE事件。

主要是③这种情况:send出去的数据其实都先存在winsock的发送缓冲区中,然后才发送出去,如果缓冲区满了,那么再调用send(WSASend,sendto,WSASendTo)的话,就会返回一个 WSAEWOULDBLOCK的错误码,接下来随着发送缓冲区中的数据被发送出去,缓冲区中出现可用空间时,一个 FD_WRITE 事件才会被触发,这里比较容易混淆的是 FD_WRITE 触发的前提是 缓冲区要先被充满然后随着数据的发送又出现可用空间,而不是缓冲区中有可用空间,也就是说像如下的调用方式可能出现问题

else if(event.lNetworkEvents & FD_WRITE)
{
    if(event.iErrorCode[FD_WRITE_BIT] == 0)
     {
         send(g_sockArray[nIndex], buffer, buffersize);
         ....
     }
     else
     {
     }
}

问题在于建立连接后 FD_WRITE 第一次被触发, 如果send发送的数据不足以充满缓冲区,虽然缓冲区中仍有空闲空间,但是 FD_WRITE 不会再被触发,程序永远也等不到可以发送的网络事件。

基于以上原因,在收到FD_WRITE事件时,程序就用循环或线程不停的send数据,直至send返回WSAEWOULDBLOCK,表明缓冲区已满,再退出循环或线程。当缓冲区中又有新的空闲空间时,FD_WRITE 事件又被触发,程序被通知后又可发送数据了。

上面代码片段中省略的对 FD_WRITE 事件处理

else if(event.lNetworkEvents & FD_WRITE)
{
    if(event.iErrorCode[FD_WRITE_BIT] == 0)
     {
        while(TRUE)
         {
            // 得到要发送的buffer,可以是用户的输入,从文件中读取等
             GetBuffer....
            if(send(g_sockArray[nIndex], buffer, buffersize, 0) == SOCKET_ERROR)
             {
                // 发送缓冲区已满
                if(WSAGetLastError() == WSAEWOULDBLOCK)
                    break;
                 else
                     ErrorHandle...
             }
         }
     }
     else
     {
         ErrorHandle..
        break;
     }
}

 

/*! *\fn bool tpsocket_write(struct tpsocket_fd *sock, struct tpsocket_buf*buf); * *\brief Write a socket buffer to a socket tx buffer queue. * *\param[in] sock The socket to write to, will be freed on failure. *\param[in] buf The socket buffer to be written. * *\return Always true if buffer length is under limit. */ bool tpsocket_write(struct tpsocket_fd *sock, struct tpsocket_buf*buf) { return tpsocket_write_force(sock, buf, false); }/*! *\fn int tpsocket_write_force(struct tpsocket_fd *sock, struct tpsocket_buf*buf, bool force); * *\brief Write a socket buffer to a socket tx buffer queue. * *\param[in] sock The socket to write to, will be freed on failure. *\param[in] buf The socket buffer to be written. *\param[in] force Force the socket buffer to be written. * *\return Always true if buffer length is under limit. */ bool tpsocket_write_force(struct tpsocket_fd *sock, struct tpsocket_buf*buf, bool force) { if (buf) { if (!force && !tpsocket_writable(sock)) { if (!sock->writeDisable) { DEBUG("SOCK %p WRITE BUFFER OVER FLOW! %d > %d\n", sock, sock->write_buf_length , sock->handler.write_buf_max); sock->writeDisable = 1; } tpbuf_free(buf); return false; } else if (tpsocket_writable(sock)) { if (sock->writeDisable) { DEBUG("SOCK %p WRITE BUFFER WRITEABLE! %d < %d\n", sock, sock->write_buf_length , sock->handler.write_buf_max); sock->writeDisable = 0; } } list_add_tail(&buf->list, &sock->write_buf); sock->write_buf_length += tpbuf_data_len(buf); if (sock->fd.fd < 0) { return true; } else if (sock->type & TPSOCKET_TYPE_FILE) { if (!(sock->pending_flag & ULOOP_WRITE)) { sock->pending_flag |= ULOOP_WRITE; tpsocket_timer_set(sock, &sock->poll, 0); } } else { if (!sock->fd.error && !(sock->pending_flag & ULOOP_WRITE)) { if (!(sock->fd.flags & ULOOP_WRITE)) { tpsocket_fd_add(sock, sock->fd.flags | (ULOOP_WRITE)); } } } #ifndef TPSOCKET_SUPPORT_PTHREAD_ULOOP if (sock->sp && sock->thread_write_len < SOCKET_BUF_FINE) { tpsocket_write_cb(sock, SOCKET_BUF_BIG, &sock->thread_write_len); } #endif } return true; }
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值