1. 程式人生 > >非阻塞套接字用法

非阻塞套接字用法

http://www.cppblog.com/yunboy4/archive/2010/03/16/91231.html
1.非阻塞套接字的模式
(1)伺服器端
    通常socket執行後預設為阻塞模式。要呼叫ioctlsocket函式設定非阻塞模式。
如:

    WSAData Data;
    WSAStartup(MAKEWORD(2, 2), &Data);
    SerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(INVALID_SOCKET == SerSocket)
        cout<<"Invalid Socket!\n";
    u_long iMode = 1;
    ioctlsocket(SerSocket, FIONBIO, &iMode);
    
    在接受客戶端請求的執行緒中,若接受成功就返回客戶端的套接字,否則返回INVALID_SOCKET錯誤,
若錯誤程式碼為WSAEWOULDBLOCK,說明當前沒有客戶端請求。
如:
//接受客戶端請求執行緒
DWORD WINAPI AcceptClientPro(LPVOID LpP)
{
    SOCKADDR_IN ClientAdrr;
    int AddrLen = sizeof(SOCKADDR);
    //非阻塞模式
    while (!IsConnet)
    {
        ClientSock = accept(SerSocket, (SOCKADDR *)&ClientAdrr, &AddrLen );
        if(INVALID_SOCKET == ClientSock)
        {
            int n = WSAGetLastError();
            //沒有客戶端請求
            if(WSAEWOULDBLOCK == n)    
            {
                cout<<"沒有客戶端發出請求!"<<endl;
                Sleep(1000);
                continue;
            }else
            {
                cout<<"出現錯誤!"<<endl;
                Sleep(1000);
            }
        }else
        {
            cout<<"已連線客戶端!"<<endl;
            IsConnet = true;
            break;
        }
    }
    return 0;
}
    
    就recv函式來說,在阻塞模式中,如果沒有客戶端傳送資料過來,執行緒到這裡會阻塞,直到有數
據傳送過來為止。在非阻塞模式中,沒有客戶端傳送資料過來,返回SOCKER_ERROR,錯誤程式碼為WSAEWOULDBLOCK。
如:
//接收資料執行緒
DWORD WINAPI ReceiveDataPro(LPVOID LpP)
{
    while(!IsConnet);        //保證連線後再接受資料
    while(1)
    {
        if(IsReadyRecei)        //保證緩衝區在未處理時不受新來的資料的影響
        {
            
            int ReceiLen = recv(ClientSock, (char *)&DataPack, sizeof(DataPack), 0);
            if(SOCKET_ERROR == ReceiLen)
            {
                int Err = WSAGetLastError();
                if(WSAEWOULDBLOCK == Err)
                {
                    cout<<"沒有收到資料"<<endl;
                    continue;
                }
                else if (WSAENETDOWN == Err ||//客戶端關閉了連線
                    WSAETIMEDOUT == Err ||
                    WSAECONNRESET == Err )    
                {
                    cout<<"伺服器關閉了連線"<<endl;
                    break;
                }
            }
            if(0 == ReceiLen)        //客戶端關閉了連線
            {
                cout<<"ReceiLen = 0"<<endl;
                break;
            }
            if(ReceiLen >= sizeof(DataPack))        //成功接收
            {
                cout<<"已收到資料:"<<DataPack.buf<<endl;
                IsReadyRecei = false;
                break;
            }
        }
    }
    return 0;
}    

(2)客戶端
    在客戶端的連線請求執行緒中,connect函式會返回SOCKET_ERROR,這並不是說明連線失敗,具體情況
要看它的WSAGetLastError()返回值,若它三次返回SOCKET_ERROR的Error程式碼依次為WSAEWOULDBLOCK,
WSAEINVAL,WSAEISCONN,就說明連線伺服器成功,否則失敗。但有的時候WSAEINVAL沒有出現就有WSAEISCONN
了,所以我還是以WSAEISCONN為連線完成的標誌,但要注意其實在三次返回程式碼中,第一次的WSAEWOULDBLOCK
之前的connect操作就成功了,如果沒出意外伺服器就要響應了。
如:
//連線伺服器執行緒
DWORD WINAPI ConnetServerPro(LPVOID LpP)
{
    SOCKADDR_IN ServerAddr;
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(1200);
    ServerAddr.sin_addr.s_addr = inet_addr("192.168.1.100");
    int BlockFlag = 0;
    int InvalFlag = 0;
    while (!IsConnet)
    {
        int nResu = connect(ClientSock, (SOCKADDR *)&ServerAddr, sizeof(SOCKADDR));
        if(SOCKET_ERROR == nResu)
        {
            int n = WSAGetLastError();
            
            if(WSAEWOULDBLOCK == n )        //不能立即完成
            {
                cout<<"過程1!"<<endl;
                BlockFlag++;
                continue;
            }
            else if(WSAEINVAL == n)        //監聽狀態
            {
                cout<<"過程2!"<<endl;
                InvalFlag++;
                continue;
            }
            else if(WSAEISCONN == n)        //連線完成
            {
                cout<<"已連線伺服器!"<<endl;
                IsConnet = true;
                break;
            }
            else 
            {
                cout<<"出現其他錯誤!\n"<<endl;
                Sleep(1000);
            }
        }
    }

    return 0;
}

    在傳送資料執行緒中,send()返回的是傳送資料的長度說明發送成功;返回SOCKET_ERROR時,若
錯誤程式碼為WSAEWOULDBLOCK就再重試,不是WSAEWOULDBLOCK就說明有錯誤。】
如:
//傳送資料執行緒
DWORD WINAPI SendDataPro(LPVOID LpP)
{
    while(!IsConnet);    //保證已連線伺服器
    while(1)
    {
        int len = send(ClientSock, (char *)&DataPack, DataPack.Head.len, 0);
        if(SOCKET_ERROR == len)
        {
            int Error = WSAGetLastError();
            if(WSAEWOULDBLOCK == Error)
                continue;
        }
        else        //傳送成功
        {
            cout<<"傳送成功!"<<endl;
            break;
        }
    }

    return 0;
}