程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> C語言入門知識 >> C語言實現 select模式編程

C語言實現 select模式編程

編輯:C語言入門知識

server:

// TcpServer.cpp : 定義控制台應用程序的入口點。
//
#include "stdafx.h"
#include    
#include 

#pragma comment(lib,"WS2_32.lib")   

#define   PORT   9990   
#define   DATA_BUFSIZE   8192   

// 定義套接字信息
typedef   struct   _SOCKET_INFORMATION   {   
    CHAR   Buffer[DATA_BUFSIZE];        // 發送和接收數據的緩沖區
    WSABUF   DataBuf;                       // 定義發送和接收數據緩沖區的結構體,包括緩沖區的長度和內容
    SOCKET   Socket;                            // 與客戶端進行通信的套接字
    DWORD   BytesSEND;                  // 保存套接字發送的字節數
    DWORD   BytesRECV;                  // 保存套接字接收的字節數
} SOCKET_INFORMATION,   *   LPSOCKET_INFORMATION;   

DWORD   TotalSockets = 0;               // 記錄正在使用的套接字總數量
LPSOCKET_INFORMATION   SocketArray[FD_SETSIZE];         // 保存Socket信息對象的數組,FD_SETSIZE表示SELECT模型中允許的最大套接字數量

// 創建SOCKET信息
BOOL   CreateSocketInformation(SOCKET   s)   
{   
    LPSOCKET_INFORMATION   SI;                                      // 用於保存套接字的信息       
 //   printf("Accepted   socket   number   %d\n",   s);         // 打開已接受的套接字編號
    // 為SI分配內存空間
    if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,   
              sizeof(SOCKET_INFORMATION)))   ==   NULL)   
    {   
        printf("GlobalAlloc()   failed   with   error   %d\n",   GetLastError());   
        return   FALSE;   
    }   
    // 初始化SI的值    
    SI->Socket = s;   
    SI->BytesSEND = 0;   
    SI->BytesRECV = 0;   
    // 在SocketArray數組中增加一個新元素,用於保存SI對象 
    SocketArray[TotalSockets]   =   SI;       
    TotalSockets++;                     // 增加套接字數量

    return(TRUE);   
}     

// 從數組SocketArray中刪除指定的LPSOCKET_INFORMATION對象
void   FreeSocketInformation(DWORD   Index)   
{   
    LPSOCKET_INFORMATION SI = SocketArray[Index];   // 獲取指定索引對應的LPSOCKET_INFORMATION對象
    DWORD   i;   
    // 關閉套接字
    closesocket(SI->Socket);   
    //printf("Closing   socket   number   %d\n",   SI->Socket);   
    // 釋放指定LPSOCKET_INFORMATION對象資源
    GlobalFree(SI);   
    // 將數組中index索引後面的元素前移
    for (i = Index; i < TotalSockets; i++)   
    {   
        SocketArray[i] = SocketArray[i+1];   
    }   
     TotalSockets--;        // 套接字總數減1
}   

// 主函數,啟動服務器
int _tmain(int argc, _TCHAR* argv[])
{
    SOCKET   ListenSocket;                  // 監聽套接字
    SOCKET   AcceptSocket;                  // 與客戶端進行通信的套接字
    SOCKADDR_IN   InternetAddr;         // 服務器的地址
    WSADATA   wsaData;                      // 用於初始化套接字環境
    INT   Ret;                                          // WinSock API的返回值
    FD_SET   WriteSet;                          // 獲取可寫性的套接字集合
    FD_SET   ReadSet;                           // 獲取可讀性的套接字集合
    DWORD   Total = 0;                              // 處於就緒狀態的套接字數量
    DWORD   SendBytes;                      // 發送的字節數
    DWORD   RecvBytes;                      // 接收的字節數


    // 初始化WinSock環境
    if ((Ret = WSAStartup(0x0202,&wsaData)) != 0)   
    {   
        printf("WSAStartup()   failed   with   error   %d\n",   Ret);   
        WSACleanup();   
        return -1;   
    }   
    // 創建用於監聽的套接字 
    if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,   
              WSA_FLAG_OVERLAPPED))   ==   INVALID_SOCKET)     
    {   
        printf("WSASocket()   failed   with   error   %d\n",   WSAGetLastError());   
        return -1;   
    }   
    // 設置監聽地址和端口號
    InternetAddr.sin_family = AF_INET;   
    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);   
    InternetAddr.sin_port = htons(PORT);   
    // 綁定監聽套接字到本地地址和端口
    if(bind(ListenSocket, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)   
    {   
        printf("bind()   failed   with   error   %d\n",   WSAGetLastError());   
        return -1;   
    }   
    // 開始監聽
    if   (listen(ListenSocket,   5))   
    {   
        printf("listen()   failed   with   error   %d\n",   WSAGetLastError());   
        return -1;   
    }   


/*  // 設置為非阻塞模式
    ULONG NonBlock = 1;   
    if(ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)   
    {   
        printf("ioctlsocket() failed with error %d\n", WSAGetLastError());   
        return -1;   
    }   
    // 為ListenSocket套接字創建對應的SOCKET_INFORMATION
    // 這樣就可以把ListenSocket添加到SocketArray數組中

    */

    CreateSocketInformation(ListenSocket);    
    while(TRUE)   
    {   
        // 准備用於網絡I/O通知的讀/寫套接字集合
        FD_ZERO(&ReadSet);   
        FD_ZERO(&WriteSet);   
        // 向ReadSet集合中添加監聽套接字ListenSocket
        FD_SET(ListenSocket,   &ReadSet);   
        // 將SocketArray數組中的所有套接字添加到WriteSet和ReadSet集合中
        // SocketArray數組中保存著監聽套接字和所有與客戶端進行通信的套接字
        // 這樣就可以使用select()判斷哪個套接字有接入數據或者讀取/寫入數據
        for   (DWORD i=0; iSocket,   &WriteSet);   
            FD_SET(SocketInfo->Socket,   &ReadSet);   
        }
        // 判斷讀/寫套接字集合中就緒的套接字    
        if((Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR)   
        {   
            printf("select()   returned   with   error   %d\n",   WSAGetLastError());   
            return -1;   
        }      
        // 依次處理所有套接字。本服務器是一個回應服務器,即將從客戶端收到的字符串再發回到客戶端。
        for   (DWORD i=0; iSocket,   &ReadSet))   
            {   
                if(SocketInfo->Socket == ListenSocket)      // 對於監聽套接字來說,可讀表示有新的連接請求
                {
                    Total--;    // 就緒的套接字減1
                    // 接受連接請求,得到與客戶端進行通信的套接字AcceptSocket
                    if((AcceptSocket = accept(ListenSocket, NULL, NULL))   !=   INVALID_SOCKET)   
                    {   
                        printf("連接成功!\n");
                        /*
                        // 設置套接字AcceptSocket為非阻塞模式
                        // 這樣服務器在調用WSASend()函數發送數據時就不會被阻塞
                        NonBlock   =   1;   
                        if(ioctlsocket(AcceptSocket, FIONBIO, &NonBlock)   ==   SOCKET_ERROR)   
                        {   
                            printf("ioctlsocket()   failed   with   error   %d\n",   WSAGetLastError());   
                            return -1;                             
                        }   
                        */

                        // 創建套接字信息,初始化LPSOCKET_INFORMATION結構體數據,將AcceptSocket添加到SocketArray數組中
                        if(CreateSocketInformation(AcceptSocket) == FALSE)   
                            return -1;   
                    }   
                    else   
                    {   
                        if(WSAGetLastError() != WSAEWOULDBLOCK)   
                        {   
                            printf("accept()   failed   with   error   %d\n",   WSAGetLastError());   
                            return -1;   
                        }   
                    }                   
                }
                else   // 接收數據
                {
                    // 如果當前套接字在ReadSet集合中,則表明該套接字上有可以讀取的數據
                    if   (FD_ISSET(SocketInfo->Socket,   &ReadSet))   
                    {   
                        Total--;                // 減少一個處於就緒狀態的套接字
                        memset(SocketInfo->Buffer, ' ', DATA_BUFSIZE);  
                        ZeroMemory(SocketInfo->DataBuf.buf,DATA_BUFSIZE);// 初始化緩沖區
                        SocketInfo->DataBuf.buf = SocketInfo->Buffer;           // 初始化緩沖區位置
                        SocketInfo->DataBuf.len   =   DATA_BUFSIZE;             // 初始化緩沖區長度
                        // 接收數據
                        DWORD  Flags = 0;   
                        if(WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes, &Flags, 
                            NULL, NULL) == SOCKET_ERROR)   
                        {   
                            // 錯誤編碼等於WSAEWOULDBLOCK表示暫沒有數據,否則表示出現異常
                            if(WSAGetLastError() != WSAEWOULDBLOCK)     
                            {   
                                printf("WSARecv()   failed   with   error   %d\n",   WSAGetLastError());   
                                FreeSocketInformation(i);       // 釋放套接字信息
                            }   
                            continue;   
                        }     
                        else   // 接收數據
                        {   
                            SocketInfo->BytesRECV = RecvBytes;      // 記錄接收數據的字節數                           
                            if(RecvBytes == 0)                                  // 如果接收到0個字節,則表示對方關閉連接
                            {   
                                FreeSocketInformation(i);   
                                continue;   
                            }   
                            else                                                            // 如果成功接收數據,則打印收到的數據
                            {
                                printf(SocketInfo->DataBuf.buf);
                                printf("\n");

                                //
                            }
                        }
                    }   
                }
            }
            else
            {
                // 如果當前套接字在WriteSet集合中,則表明該套接字的內部數據緩沖區中有數據可以發送
                if(FD_ISSET(SocketInfo->Socket,  &WriteSet))   
                {   
                    Total--;            // 減少一個處於就緒狀態的套接字
                    SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;           // 初始化緩沖區位置
                    SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;    // 初始化緩沖區長度
                    if(SocketInfo->DataBuf.len > 0)     // 如果有需要發送的數據,則發送數據
                    {
                        if(WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,   
                                        NULL, NULL) == SOCKET_ERROR)   
                        {   
                            // 錯誤編碼等於WSAEWOULDBLOCK表示暫沒有數據,否則表示出現異常
                            if(WSAGetLastError() != WSAEWOULDBLOCK)   
                            {   
                                printf("WSASend()   failed   with   error   %d\n", WSAGetLastError());   
                                FreeSocketInformation(i);       // 釋放套接字信息
                            }   
                            continue;   
                        }   
                        else   
                        {   
                            SocketInfo->BytesSEND += SendBytes;         // 記錄發送數據的字節數
                            // 如果從客戶端接收到的數據都已經發回到客戶端,則將發送和接收的字節數量設置為0
                            if (SocketInfo->BytesSEND   ==   SocketInfo->BytesRECV)   
                            {   
                                SocketInfo->BytesSEND   =   0;   
                                SocketInfo->BytesRECV   =   0;   
                            }   
                        }   
                    }
                }               

            }   // 如果ListenSocket未就緒,並且返回的錯誤不是WSAEWOULDBLOCK(該錯誤表示沒有接收的連接請求),則出現異常

        }           

    }   
    // 暫停,按任意鍵退出
    system("pause");
    return 0;
}

client:

// TcpClient.cpp : 定義控制台應用程序的入口點。
//

#include "stdafx.h"

#include    
#include 
#include 

#pragma comment(lib,"WS2_32.lib")   
#define BUF_SIZE    64          // 緩沖區大小  

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA     wsd;                    // 用於初始化Windows Socket   
    SOCKET      sHost;                  // 與服務器進行通信的套接字   
    SOCKADDR_IN servAddr;           // 服務器地址   
    char        buf[BUF_SIZE];          // 用於接受數據緩沖區   
    int         retVal;                         // 調用各種Socket函數的返回值   
    // 初始化Windows Socket
    if(WSAStartup(MAKEWORD(2,2),&wsd) != 0)   
    {   
        printf("WSAStartup failed !\n");   
        return 1;   
    }     
    // 創建套接字   
    sHost = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);   
    if(INVALID_SOCKET == sHost)   
    {   
        printf("socket failed !\n");   
        WSACleanup();   
        return -1;   
    }    

    // 設置服務器地址   
    servAddr.sin_family = AF_INET;   
    servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");     // 用戶需要根據實際情況修改
    servAddr.sin_port = htons(9990);                                                    // 在實際應用中,建議將服務器的IP地址和端口號保存在配置文件中
    int sServerAddlen = sizeof(servAddr);                                               // 計算地址的長度       
    // 循環等待
    while(true)
    {
        // 連接服務器   
        Sleep( 200 );
        retVal = connect(sHost,(LPSOCKADDR)&servAddr,sizeof(servAddr));   
        Sleep( 200 );
        if(SOCKET_ERROR == retVal)   
        {   
            int err = WSAGetLastError();
            if(err == WSAEWOULDBLOCK || err == WSAEINVAL)           // 無法立即完成非阻塞套接字上的操作
            {
                //Sleep(500);
                continue;
            }
            else if(err == WSAEISCONN)                                              // 已建立連接
            {
                break;
            }
            else
            {
                                continue;

                //printf("connect failed !\n");   
                //closesocket(sHost);   
                //WSACleanup();   
                //return -1;   
            }
        }   
    }
    // 循環向服務器發送字符串,並顯示反饋信息。
    // 發送quit將使服務器程序退出,同時客戶端程序自身也將退出
    while(true)
    {
        // 向服務器發送數據   
        printf("Please input a string to send: ");
        // 接收輸入的數據
        std::string str;
        std::getline(std::cin, str);
        // 將用戶輸入的數據復制到buf中
        ZeroMemory(buf,BUF_SIZE);  
        ZeroMemory(buf,BUF_SIZE);   
        strcpy(buf,str.c_str());   
        // 循環等待
        while(true)
        {
            // 向服務器發送數據
            retVal = send(sHost,buf,strlen(buf),0);   
            if(SOCKET_ERROR == retVal)   
            {   
                int err = WSAGetLastError();
                if(err == WSAEWOULDBLOCK)           // 無法立即完成非阻塞套接字上的操作
                {

                    continue;
                }
                else
                {
                    printf("send failed !\n");   
                    closesocket(sHost);   
                    WSACleanup();   
                    return -1;   
                }
            }
            break;
        }     

        while(true)
        {
            ZeroMemory(buf,BUF_SIZE);                       // 清空接收數據的緩沖區
            retVal = recv(sHost,buf,sizeof(buf)+1,0);   // 接收服務器回傳的數據   
            if(SOCKET_ERROR == retVal)   
            {   
                int err = WSAGetLastError();                // 獲取錯誤編碼
                if(err == WSAEWOULDBLOCK)           // 接收數據緩沖區暫無數據
                {
                    Sleep(100);
                    printf("waiting back msg !\n");
                    continue;
                }
                else if(err == WSAETIMEDOUT || err == WSAENETDOWN)
                {
                    printf("recv failed !\n");   
                    closesocket(sHost);   
                    WSACleanup();   
                    return -1;
                }
                break;
            }   
            break;
        }   
        //ZeroMemory(buf,BUF_SIZE);                     // 清空接收數據的緩沖區
        //retVal = recv(sHost,buf,sizeof(buf)+1,0);   // 接收服務器回傳的數據   

        printf("Recv From Server: %s\n",buf);   
        // 如果收到quit,則退出
        if(strcmp(buf, "quit") == 0)
        {
            printf("quit!\n");
            break;
        }
    }
    // 釋放資源   
    closesocket(sHost);   
    WSACleanup();   
    // 暫停,按任意鍵繼續
    system("pause");
    return 0;  
}
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved