select模型的中心思想就是利用select函數,實現對I/O的管理。利用select函數,我們可以判斷套接字上是否存在數據,或者能否向一個套接字寫入數據。利用這個函數可以防止應用程序在一次I/O的綁定調用中進入鎖定狀態。
select函數原型:
int select(
__in int nfds, //忽略
__inout fd_set *readfds, //檢查可讀性
__inout fd_set *writefds, //檢查可寫性
__inout fd_set *exceptfds, //用於例外數據
__in const struct timeval *timeout //超時時間,傳遞NULL會無限期等待下去,0會立刻返回
);
可讀性:
1.有數據可以讀入。
2.連接已經關閉,重設或者中止。
3.加入調用了listen,而且一個連接正在建立,那麼accept函數調用會成功。
關於連接已經關閉,重設或者中止的判斷:
當一個套接字在調用了select之後具有可讀性,那麼這個時候我們可以通過調用recv獲得數據。如果真的有數據發送過來,那麼這個調用會成功。如果是關閉,重設或者中止,那麼recv的調用會失敗,這個時候通wsagetlasterror就可以判斷連接是否已經中斷。
可寫性:
1.有數據可以發出。
2.如果已完成了對一個非鎖定連接調用處理,連接就會成功。
對於可寫性的檢查,最好放在需要寫數據的時候進行檢查。如果和可讀性放在同一個地方進行檢查,那麼select很可能每次都會因為可寫性檢查成功而返回。
例外數據:
1.加入已完成了對一個非鎖定連接調用的處理,連接嘗試就會失敗。
2.有帶外數據可供讀寫。
fd_set的幾個宏:
FD_SETSIZE 定義了fd_set所允許存放套接字的最大個數,默認是64。如果想修改這個默認值,那麼在包含winsock2.h之前重新
定義這個宏。最大不要超過1024
FD_CLR(s, *set):從set中刪除套接字s。
FD_ISSET(s, *set):檢查s是否set集合的一名成員;如答案是肯定的是,則返回TRUE。
FD_SET(s, *set):將套接字s加入集合set。
FDZERO(*set):將set初始化成空集合。
select調用流程:
1) 使用FDZERO宏,初始化自己感興趣的每一個fd_set。
2) 使用FDSET宏,將套接字句柄分配給自己感興趣的每個fd_set。
3) 調用select函數,然後等待在指定的fd_set集合中,I/O活動設置好一個或多個套接字句柄。select完成後,會返回在所有fd_set集合中設置的套接字句柄總數,它會修改每個fd_set結構,刪除那些不存在待決I/O操作的套接字句柄
4) 根據select的返回值,我們的應用程序便可判斷出哪些套接字存在著尚未完成(待決)的I/O操作—具體的方法是使用FD_ISSET宏,對每個fd_set集合進行檢查。
5) 知道了每個集合中“待決”的I/O操作之後,對I/O進行處理,然後返回步驟1 ),繼續進行select處理。
下面是利用之前封裝的socket類來實現的一個簡單的select服務器,接受用戶的連接和消息,在收到消息之後檢查可寫性,如果可寫將收到的消息發還給用戶:
Cpp代碼
#include <stdlib.h>
#include <map>
#include "../../common/winsock/sock_accepter.h"
#include "../../common/winsock/sock_stream.h"
#include "../../common/winsock/sock_init.h"
#include "../../common/log/log.h"
int main(int argc, char** argv)
{
SockInit init(2, 2);
SockAccepter accepter;
if(!accepter.StartListen("0.0.0.0", 5000))
{
return 0;
}
fd_set read_set;
std::map<SOCKET, SockStream> users;
while (true)
{
FD_ZERO(&read_set);
FD_SET(accepter.GetHandle(), &read_set);
std::map<SOCKET, SockStream>::iterator ite = users.begin();
for (; ite != users.end(); ++ite)
{
FD_SET(ite->first, &read_set);
}
int res = select(0, &read_set, NULL, NULL, NULL);
if (res == SOCKET_ERROR)
{
Log::Instance().WriteLog(LOG_ERROR, "select failed. err = %d", WSAGetLastError());
break;
}
else
{
if(FD_ISSET(accepter.GetHandle(), &read_set))
{
SockStream stream;
if(accepter.Accept(stream))
{
users.insert(std::make_pair(stream.GetHandle(), stream));
}
}
for (unsigned int i = 0; i < read_set.fd_count; ++i)
{
std::map<SOCKET, SockStream>::iterator ite = users.find(read_set.fd_array[i]);
if(ite == users.end())
{
continue;
}
char buffer[1025];
int length = ite->second.Read(buffer, 1024);
if(-1 != length)
{
buffer[length] = 0;
Log::Instance().WriteLog(LOG_INFO, "recv msg = %s", buffer);
fd_set write_set;
FD_ZERO(&write_set);
FD_SET(ite->first, &write_set);
int temp = select(0, NULL, &write_set, NULL, 0);
if(SOCKET_ERROR == temp)
{
continue;
}
else
{
if(FD_ISSET(ite->first, &write_set))
{
ite->second.Write(buffer, length);
}
}
}
else
{
if (INVALID_SOCKET == ite->second.GetHandle())
{
users.erase(ite);
}
}
}
}
}
system("pause");
return 0;
}
作者“木頭城”