網絡編程——select模型(總結)

爲何要使用select模型?

答:解決基本C/S模型中,accept()recv()send()阻塞的問題數組

select模型與C/S模型的不一樣點

  • C/S模型中accept()會阻塞一直傻等socket來連接
  • select模型只解決accept()傻等的問題,不解決recv(),send()執行阻塞問題
其實select模型解決了 實現多個客戶端連接,與多個客戶端分別通訊
兩個模型都存在recv(),send()執行阻塞問題
  • 因爲服務器端,客戶端不須要(客戶端只有一個socket,能夠經過加線程解決同時recv和send)

select模型邏輯

  1. 將全部的socket(服務器端+客戶端)裝進一個數組中
  2. 經過select()遍歷socket數組
  3. 取出有相應的socket放進另外一個數組(都是有響應的socket)
  4. 對裝有響應的socket數組集中處理
  5. 服務器socket響應:客戶端連接,調用accept()
  6. 客戶端socket響應:客戶端通訊,調用send()或recv()

圖片描述


select()

fd_set

做用:定義一個用來裝socket的結構體服務器

#ifndef FD_SETSIZE
#define FD_SETSIZE      64   /*默認64個*/
#endif /* FD_SETSIZE */

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

默認裝socket大小爲64,能夠經過在winsock2.h頭文件前聲明宏,給一個更大的值socket

#define FD_SETSIZE 128
#include <WinSock2.h>

由於原理就是不停遍歷檢測,越多效率越低,延遲越大,因此合適大小最好。
select模型應用,就是小用戶量訪問函數

四個操做fd_set的操做宏
操做宏 做用 代碼
FD_ZERO 將客戶端socket集合清零 FD_ZERO(&clientSockets);
FD_SET 添加一個socket(超過默認值大小再也不處理) FD_SET(socketListen,&clientSockets);
FD_CLR 從集合中刪除指定的socket,必定要close,手動釋放 FD_CLR(socketListen, &clientSockets);closesocket(socketListen);
FD_ISSET 查詢socket是否在集合中,不存在返回0,存在返回非0 int a = FD_ISSET(socketListen, &clientSockets);

select()函數

做用:監視socket集合,若是某個socket發生響應(連接或者收發數據),經過返回值以及參數告訴咱們哪一個socket有響應spa

int WSAAPI select(
  int           nfds,   /*填0*/
  fd_set        *readfds, /*檢查是否有可讀的socket*/
  fd_set        *writefds, /*檢查是否有可寫的socket*/
  fd_set        *exceptfds, /*檢查socket上的異常錯誤*/
  const timeval *timeout
);
參數1:忽略填0

爲了兼容Berkeley sockets線程

參數2:指向一組socket數組,用來保存有響應的數組
  • 只針對recv()、accept()
  • 這個用來保存有響應的數組初始化爲包含全部的socket,經過select函數投放給系統,系統遍歷數組後,只將有響應的socket再賦值回來,調用後,這個參數只剩下有請求的socket
參數3:指向一組socket數組,用來保存可發送的數組
  • 能夠給哪些客戶端socket發送消息,針對send
  • 這個用來保存可發送的數組初始化爲包含全部的socket,經過select函數投放給系統,系統遍歷數組後,只將可發送的socket再賦值回來,調用後,這個參數只剩下可發送的socket
參數4:檢查socket上的異常錯誤

用法和參數二、3同樣,將有異常錯誤的socket裝進來,反饋給咱們3d

/*獲得異常socket上的具體錯誤碼*/
getsockopt(socket, SOL_SOCKET, SO_ERROR, buf, buflen);

若是調用這個函數(針對這個getsockopt函數)沒有錯誤,返回0,不然返回SOCKET_ERROR,而且能夠調用WSAGetLastError來獲得錯誤代碼。code

參數5:最大等待時間

一個結構體blog

struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* and microseconds */
};

當客戶端沒有響應時,select能夠選擇等一段時間,不等,等到有socket響應,三種方式圖片

tv_sec tv_usec 做用
0 0 不等待,馬上返回
3 4 等待3秒4微秒沒有消息再返回

NULL :死等,直到有socket響應

返回值
  • 0:在等待時間沒有客戶端socket響應,continue進行下一次等待
  • >0:有客戶端socket響應‘
  • SOCKET_ERROR:發送錯誤

select模型代碼

fd_set allsockets;
    //清零
    FD_ZERO(&allSockets);
    //服務器裝進去
    FD_SET(socketServer, &allSockets);
    while (1)
    {
        fd_set readSockets = allSockets;
        fd_set writeSockets = allSockets;
        fd_set errorSockets = allSockets;

        //時間段
        struct timeval st;
        st.tv_sec = 3;
         st.tv_usec = 0;

        //select
        int nRes = select(0, &readSockets, &writeSockets, &errorSockets, &st);
        if (0 == nRes) //沒有響應的socket
        {
            continue;
        }
        else if (nRes > 0)
        {
            //處理錯誤
            for (u_int i = 0; i < errorSockets.fd_count; i++)
            {
                char str[100] = { 0 };
                int len = 99;
                if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len))
                {
                    printf("沒法獲得錯誤信息\n");
                }
                printf("%s\n", str);    
            }

            for (u_int i = 0; i < writeSockets.fd_count; i++)
            {
                //printf("服務器%d,%d:可寫\n", socketServer, writeSockets.fd_array[i]);
                if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", 2, 0))
                {
                    int a = WSAGetLastError();
                }
            }

            //有響應
            for (u_int i = 0; i < readSockets.fd_count; i++)
            {
                if (readSockets.fd_array[i] == socketServer)
                {
                    //accept
                    SOCKET socketClient = accept(socketServer, NULL, NULL);
                    if (INVALID_SOCKET == socketClient)
                    {
                        //連接出錯
                        continue;
                    }
                    
                    FD_SET(socketClient, &allSockets);
                    //send
                }
                else
                {
                    char strBuf[1500] = { 0 };
                    //客戶端吧
                    int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0);
                    //send
                    if (0 == nRecv)
                    {
                        //客戶端下線了
                        //從集合中拿掉
                        SOCKET socketTemp = readSockets.fd_array[i];
                        FD_CLR(readSockets.fd_array[i], &allSockets);
                        //釋放
                        closesocket(socketTemp);
                    }
                    else if (0 < nRecv)
                    {
                        //接收到了消息
                        printf(strBuf);
                    }
                    else //SOCK_ERROR
                    {
                        //強制下線也叫出錯 10054
                        int a = WSAGetLastError();
                        switch (a)
                        {
                        case 10054:
                            {
                                SOCKET socketTemp = readSockets.fd_array[i];
                                FD_CLR(readSockets.fd_array[i], &allSockets);
                                //釋放
                                closesocket(socketTemp);
                            }    
                        }
                    }    
                }
            }
        }

圖片描述

總結

select模型

將一組socket數組投遞給系統,而後在系統裏去查詢socket是否有信號,過程都是在select函數裏面去進行的,再到返回有操做的socket集合

select()函數本質

select()函數執行遍歷和返回有響應的socket,整個過程當中也是阻塞的。

等待時間 阻塞
不等待 執行阻塞
半等待 執行阻塞+軟阻塞
全等待 執行阻塞+硬阻塞

與CS模型對比

使用CS模型時,當連接了一個客戶端,執行完了recv,while循環又回到了accept(),傻等着客戶端來連接,沒法多客戶端連接通訊。
使用select模型時,是select在遍歷着socket數組,有響應的socket再取出來,沒有就一直遍歷,雖然select()函數的執行也是阻塞的。能夠理解爲,每次都是在處理只有響應的socket,因此能夠進行多客戶端連接通訊。

當第一個客戶端socket來連接時,select()函數將服務端socket從allsocket取出來,將新建的含有客戶端socket添加到allsockets數組中,接着又在遍歷allsocket,查看着時候有響應,因此不會像CS模型那種,在accept()函數阻塞着,傻等着。

select()函數主要解決的是accept()函數阻塞問題,而沒有解決recv()和send()函數阻塞問題

相關文章
相關標籤/搜索