您的位置:首页 > 理论基础 > 计算机网络

WSAEventSelectI/O模型中FD_WRITE事件的处理以及如何发送数据

2014-05-20 10:30 1011 查看
网上很多人都在问什么时候才会触发FD_WRITE,触发了我该怎么发送数据,如果没有触发,我又该如何发数据?

当第一次连接成功的时候会触发FD_WRITE,还有就是当send 返回SOCKER_ERROR并且 WSAGetLastError()返回 WSAEWOULDBLOCK 这时表示缓冲区已满,当数据发送出去,又有空闲的缓冲区时会触发FD_WRITE,表示缓冲区可写。

也就是说第一次连接成功,表示你现在可以发送数据会触发FD_WRITE还有就是缓冲区有空闲空间的时候会触发FD_WRITE事件,这里有两种解释:

1.至于缓冲区是否满,得等到你发送数据的时候才知道因为可能你的系统里面,还有其他的网络程序在发送数据。

2.因为你才连接成功对于这个SOCKET而言你还没发数据,那么相对于这个SOCKET的缓冲器区,肯定是空闲的因为你还没发数据。

这里就涉及到缓冲区是每个SOCKET都有缓冲区还是所有SOCKET共享一个缓冲区,我个人觉得应该是每一个SOCKET都有一个缓冲区。

实际上send只是把发送的数据拷贝到缓冲区,并不是真正的已经发送完毕,真正的发送是底层调用网卡驱动把每个SOCKET的拷贝过来的缓冲区数据根据不同的IP地址和端口再进行发送。

如果是阻塞的模型下send直到把所有数据拷贝到缓冲区才返回,如果缓冲区满了那么就直接阻塞等待,这个就是为什么有时候send会阻塞一下才返回的原因。

行了跑题了,我们最关心的肯定是send的时候返回SOCKER_ERROR 并且 WSAGetLastError()返回 WSAEWOULDBLOCK 的时候如何发送数据。

不多说了直接上代码:

//队
struct MyQueue
{
    CHAR mLeftBuffer[ 8192 ];
    WORD mBufferSize;
};
//队列
std::list< MyQueue > MyQueues;

bool MySend( const LPVOID Buffer, const WORD BufferSize )
{
    //这里要临界区锁定一下,因为我的网络事件通知在另一个线程里面,也在操作MyQueues队列

    //锁定代码你自己添加
    //EnterCriticalSection();
    //第一次发送数据或者已经把其他的数据发完了
    if( MyQueues.empty() )
    {
        WORD SendBytes = 0;
        int TranslatedBytes = 0;
        do
        {
            TranslatedBytes = send( YourSocket, Buffer + SendBytes, BufferSize - SendBytes, 0 );
            if( SOCKET_ERROR == TranslatedBytes )
            {
                if( WSAEWOULDBLOCK == WSAGetLastError() )
                {
                    //缓冲区满啦无法再发送数据啦
                    //这种情况其实也算是正常的情况,那么我要保存余下的数据和余下数据字节数
                    //等待出现FD_WRITE的时候接着把余下的数据发送完成
                    //直接加入队列里面等待
                    MyQueue Queue;
                    memcpy( Queue.mLeftBuffer, Buffer + SendBytes, BufferSize - SendBytes );//拷贝余下的数据
                    Queue.mBufferSize = BufferSize - SendBytes;        //记录还有多少字节没法
                    MyQueues.push_back( Queue );  //加入队列
                    return true;
                }
                //出现其他的错误了,返回false
                //一定要离开临界区
                //LeaveCriticalSection();
                assert( false );
                return false;
            }
            //累计发送的字节数
            SendBytes += TranslatedBytes;
        }while( SendBytes < BufferSize );
        //运行到这里说明数据肯定发送成功了,而且缓冲区还没有满
    }
    else
    {
        //说明发送数据的时候上次还有没发完的,那么直接加入队列即可
        MyQueue Queue;
        memcpy( Queue.mLeftBuffer, Buffer, BufferSize );    //拷贝所有数据
        Queue.mBufferSize = BufferSize;                                    //发送的数据大小
        MyQueues.push_back( Queue );
    }
    //离开临界区
    //LeaveCriticalSection();
    return true;
}

//网络通知线程部分代码
UINT WINAPI NetworkEventThread( LPVOID P )
{
    //其他的都比较简单略过
    //这里只写FD_WRITE的处理其他的你自己添加即可
    WSANETWORKEVENTS NetEvent;
    MyQueue Queue;
    int TranslatedBytes = 0;
    WORD SendBytes = 0;
    WSAEnumNetworkEvents( YourSocket, YourEvent, &NetEvent);
    //缓冲区有空间可写啦!!
    if( NetEvent.lNetworkEvents & FD_WRITE )
    {
        //没出错
        if(NetEvent.iErrorCode[FD_WRITE_BIT] == 0)
        {
            //临界区锁定,请自己添加
            //EnterCriticalSection();
            while( false == MyQueues.empty() )
            {
                //取出一个队
                memcpy( &Queue, &MyQueues.front(), sizeof( Queue ) );
                do
                {
                    //接着发余下的数据
                    TranslatedBytes = send( YourSocket, Queue.mLeftBuffer + SendBytes, Queue.mBufferSize - SendBytes );
                    if( SOCKET_ERROR == TranslatedBytes )
                    {
                        if( WSAEWOULDBLOCK == WSAGetLastError() )
                        {
                            //缓冲区又满啦,直接跳出循环等待下次通知FD_WRITE事件
                            //这里要跳出两次循环用goto 方便 简单 快捷!
                            //为什么要跳出两次循环?
                            //因为这个队不能再发了,再发有可能还是返回这个错,很可能会死循环
                            //必须要等FD_WRITE通知到来我们才能写其他数据
                            //因为你不知道什么时候数据可写,只有操作系统和网卡通知我们
                            goto SKIP_LOOP;
                        }
                        //出现其他的错误啦!!!
                        //离开临界区
                        //LeaveCriticalSection();
                        assert( false );
                        return 0;
                    }
                    //累计发送的字节数
                    SendBytes += TranslatedBytes;
                }while( SendBytes < Queue.mBufferSize );
                //数据发完啦,缓冲区还没满
                MyQueues.pop_front();//弹出这个队
                SendBytes = 0;    //累计的发送字节数清空,为下一个队列做准备
                //如果队列不为空的话发送下一个队列直到缓冲器满为止或者都发完。
            }
            //别跳过头了一定要在离开临界区前,不然就死锁了。
            SKIP_LOOP:;
            //离开临界区
            //LeaveCriticalSection();
        }
        else
        {
            //出错啦!!
            assert( false );
            return 0;
        }
    }

    return 0;
}


上面都有注释有什么不明白的可以给我留言,当然上面的代码肯定不能直接拿来用我只写的关键部分还有就是有些地方要自己动手修改一下才能用,大家有什么建议或者想法也可以给我提欢迎指教大家共同进步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐