1、winsock的两种I/O模式
Windows Socket套接字可以在两种模式下进行I/O操作:阻塞模式和非阻塞模式。
在阻塞模式下,执行I/O操作的函数在操作完成之前会一直等待,不会将这种控制权返回给程序,这样任一线程在某一时刻只能执行一个I/O操作。套接字在创建时默认为阻塞模式。在非阻塞模式下,执行I/O操作的Winsock函数会立即返回并交出控制权,之后再通过其他机制获得操作完成的通知。
2、Windows sockets下的编程模型
(1)select模式:在阻塞模式下,使用select()函数来确定一个或多个套接字的状态,如判断套接字上是否有数据可读,或者能否向一个套接字写入数据,防止应用程序 在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫进入阻塞状态。其函数原型为:
int select (
IN int nfds, //0,无意义
IN OUT fd_set* readfds, //检查可读性
IN OUT fd_set* writefds, //检查可写性
IN OUT fd_set* exceptfds, //带外数据
IN const struct timeval* timeout); //函数的返回时间,指针NULL一直等待
请求状态查询的套接字集合由一个fd_set结构给出,在函数返回时此结构更新以反映那些满足特定条件的套接字的子集,同时select函数返回满足条件的套接字的数目。头文件winsock2.h中定义了一组操作fd_set结构的宏,他们分别是:
FD_CLR(s,*set) 从集合中删除描述符s
FD_ISSET(s,*set) 如果s是集合中的一个元素,则返回非零,否则返回0;
FD_SET(s,*set) 将描述符s加入集合
FD_SETSIZE 常量,表示最多可容纳套接字的数量,默认为64
select模式工作流程:
【1】 用FD_ZERO宏来初始化fd_set
可以初始化的有select函数的第二三四个参数。
【2】用FD_SET宏来将套接字句柄分配给相应的fd_set
例如如果想要检查一个套接字有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
【3】调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
【4】用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。
【5】对满足条件的套接字执行一定的操作。贴一段别人的代码实例:
#include <winsock2.h>
#include <stdio.h>
#define PORT 8000
#define MSGSIZE 1024
#pragma comment(lib, "ws2_32.lib")
int TotalConn = 0;
SOCKET SocketArr[FD_SETSIZE];
DWORD WINAPI WorkerThread(LPVOID lpParam);
int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
int iAddrSize = sizeof(SOCKADDR_IN);
DWORD dwThreadId;
// Initialize windows socket library
WSAStartup(0x0202, &wsaData);
// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_family = AF_INET;
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_port = htons(PORT);
bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc, 0);
SocketArr[TotalConn++] = sClient;
}
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int i;
fd_set fdread;
int ret;
struct timeval tv = {1, 0};
char szMessage[MSGSIZE];
while (TRUE)
{
FD_ZERO(&fdread); //1清空队列
for (i = 0; i < TotalConn; i++)
{
FD_SET(SocketArr[i], &fdread); //2将要检查的套接口加入队列
}
// We only care read event
ret = select(0, &fdread, NULL, NULL, &tv); //3查询满足要求的套接字,不满足要求,出队
if (ret == 0)
{
// Time expired
continue;
}
for (i = 0; i < TotalConn1; i++)
{
if (FD_ISSET(SocketArr[i], &fdread)) //4.是否依然在队列
{
// A read event happened on SocketArr
ret = recv(SocketArr[i], szMessage, MSGSIZE, 0);
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
// Client socket closed
printf("1:Client socket %d closed.\n", SocketArr[i]);
closesocket(SocketArr[i]);
if (i < TotalConn-1)
{
SocketArr[i--] = SocketArr[--TotalConn]; //删除关闭的套接字
}
}
else
{
// We reveived a message from client
szMessage[ret] = '\0';
send(SocketArr[i], szMessage, strlen(szMessage), 0);
}
}
}
}
}
int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
if (TotalConn< FD_SETSIZE)
return CF_ACCEPT;
else
return CF_REJECT;
}