網絡編程第六講Select模型


                網絡模型第六講Select模型

一丶Select模型是什么

    以前我們講過一個迭代模型.就是只服務一個客戶端連接.但是實際網絡編程中.復雜的很多. 比如一個 C/S架構程序 (客戶端/服務端) 客戶端很多的情況下.都要連接服務器.

不可能一個服務器只服務一個客戶端. 就像現在很火的一款游戲 .PUBG. 絕地求生. 他就是 CS結構程序. 玩的人很多.不可能買了很多很多服務器吧.所以我們就要寫模型. 來管理這些客戶端的Socket

並對去進行讀寫操作. 當前 Select模型只針對小網絡程序使用. 不可能應用到游戲上. 因為它能管理的Socket 實在有限. 如果是Windows的話可能以后會接觸到事件模型.消息模型.以及IOCP模型.

其實說白了就是對Socket進行管理.有效的進行讀寫.減少開銷. 隨着模型等級越高.所需要的知識點就越多.就越來越困難.

 

二丶Select 方法

   socket 套接字為我們提供了一個Select 方法.

int WSAAPI select(
  int           nfds,            //默認為0
  fd_set        *readfds,        //一個指針集合.. 針對讀操作 accept,recg
  fd_set        *writefds,       //針對寫操作 connect send
  fd_set        *exceptfds,      //針對出現的異常
  const timeval *timeout         //超時設置,為NULL就是一直等待結果.
);
返回值:
  1超時返回0
  2.出錯返回 SOCKET_ERROR

其中FD_SET是一個結構體.

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */  有多少套接字
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */ 套接字的數據
} fd_set;

FD_SETSIZE = 64 
所以說不能針對大網絡.因為只有64個.當然你可以更改.不過更改之后還不如用更好的模型.

常用的方法;

  FD_ZERO 清零

  FD_SET    添加套接字  也就是往數組里面添加指針

  FD_ISSET 校驗函數.如果參數是集合的成員.則返回 非0 ,否則返回0

int getsockopt(
  SOCKET s,            //查詢的套接字
  int    level,        //選項的等級. SOL_SOCKET IPPROTO_TCP 原始套接字還是
  int    optname,      //SOCKET選項名字 SO_ERROR SO_ACCEPTCONN
  char   *optval,      //用於接受數據的緩沖區
  int    *optlen       //緩沖區大小
);
函數作用: 查看套接字的狀態
返回值: 成功0 失敗 SOCKET_ERROR

具體作用可以查詢MSDN

select 函數的作用:

  上面說了怎么多.可能大家就很暈.該如何編寫代碼. 編寫代碼前說一下.或許能豁然開朗.

其實我們 定義了數組(集合) 當有事件來的時候.select會返回. 返回的時候.會把我們集合里面的值進行設置. 這樣我們可以對集合的值進行判斷.如果是accept 則調用accept. 

代碼如下:

  

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

#include "stdafx.h"
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")



int main()
{
   
        WSADATA wsaData;
        int iResult;
        u_long iMode = 0;

        iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (iResult != NO_ERROR)
            printf("Error at WSAStartup()\n");

        //-------------------------
        // Create a SOCKET object.
        SOCKET m_socket;
        m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (m_socket == INVALID_SOCKET) {
            printf("Error at socket(): %ld\n", WSAGetLastError());
            WSACleanup();
            return 0;
        }

        sockaddr_in hServerAddr;
        hServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
        hServerAddr.sin_family = AF_INET;
        hServerAddr.sin_port = htons(8564);

        bind(m_socket, (sockaddr *)&hServerAddr, sizeof(hServerAddr));

        //監聽

        listen(m_socket, 1);

        //主要是進行連接的時候.代碼不一樣了.因為是非阻塞.所以返回的錯誤是資源暫時不可用.
        sockaddr_in hClientAddr;
        int nAddrSize = sizeof(hClientAddr);
        SOCKET hClientSocket;

        fd_set Read, Write, Except; //定義三個集合.並且清零

        FD_ZERO(&Read);
        FD_ZERO(&Write);
        FD_ZERO(&Except);

        //添加SOCKET 到集合中
        FD_SET(m_socket, &Read);
        FD_SET(m_socket, &Write);
        FD_SET(m_socket, &Except);

        //使用Select 進行監聽 無限監聽
        int nRet = select(0, &Read, &Write, &Except,NULL);

        //如果有讀操作.則Read集合則被改變.

        nRet = FD_ISSET(m_socket, &Read);//只判斷讀
       
        char szBuf[0x1000] = { 0 };
        if (SOCKET_ERROR != nRet)
        {
               //我們就可以進行接收了.因為讀操作只有接收.不可能是Recv
            hClientSocket = accept(m_socket, (sockaddr*)&hClientAddr, &nAddrSize);
            //繼續進行清零.看看是否是讀操作還是寫操作.

            recv(hClientSocket, szBuf, 0x1000, 0);
            FD_ZERO(&Read);
            FD_ZERO(&Write);
            FD_ZERO(&Except);

            //講客戶端套接字跟服務端套接字都放到集合中.

            FD_SET(m_socket, &Read);
            FD_SET(m_socket, &Write);
            FD_SET(m_socket, &Except);

            FD_SET(hClientSocket, &Read);
            FD_SET(hClientSocket, &Write);          // 如果都設置了.那么在此進行Select監聽的時候.服務端套Read集合則為2.因為要接受.客戶端的Write則為1.因為他發送了send
            FD_SET(hClientSocket, &Except);

            nRet = select(0, &Read, &Write, &Except, NULL);

            nRet = FD_ISSET(hClientSocket, &Write); //客戶端的寫操作回來.所以影響的是客戶端的write 服務端的不影響.

            int nRet2 = FD_ISSET(m_socket, &Write);
           

        }
    return -1;
}

 

當有客戶端連接的時候.我們的集合就重置了.

例如下圖:

可以看到套接字是一個f4 有一個.所以下方我們進行判斷是否是讀操作.如果是讀操作我們就進行接受連接

接受連接之后.我們把客戶端的套接字也設置到集合中.當監聽客戶端操作的時候.寫操作就會來了.

總結一下:

    1.send recv connect accept 都是一個狀態.

    2.slecet 會根據這些來設置我們集合中數組的狀態.

    3.我們根據判斷集合中數組的狀態.對其進行操作即可.

如果出現異常.我們就需要用 

getsockopt 來檢索錯誤值了.
因為我們使用的FD_xxx都是宏. 如果在使用GetLastError 則會出現錯誤.結果不准確. 所以直接使用這個函數進行錯誤代碼的獲取.
例子:

 

if (FD_ISSET(hSocket,&Except))  //如果出現異常了.
{
    int nErrCode;
    int nErrCodeLen;
    getsocket(hSocket,SOL_SOCKET,SO_ERROR,&nErrcode,&nErrcodelen);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM