1、select模型特点:
- 解决基本模型中accept以及recv阻塞的问题
- 实现多个客户端连接,与多个客户端分别通信
- 用于服务器
2、应用范围:
- 小用户访问量
3、select模型流程
- 打开网络库
- 校验版本
- 创建socket
- 绑定地址与端口
- 开始监听
- 调用select函数
4、原理图
4、select函数调用逻辑
本质
1、将客户端及服务器的socket装进数组里
2、通过select函数遍历数组,当某个socket有相应时,select通过参数/返回值发出反馈
3、如果是服务器socket,调用accept 4、如果是客户端socket,调用send/recv
调用流程:
1、定义客户端对应的socket结构
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 ,可以手动重新定义FD_SETSIZE。*/
fd_set操作宏:
FD_SET(int fd, fd_set *fdset); //将fd加入set集合
FD_CLR(int fd, fd_set *fdset); //将fd从set集合中清除
FD_ISSET(int fd, fd_set *fdset); //检测fd是否在set集合中,不在则返回0
FD_ZERO(fd_set *fdset); //将set清零使集合中不含任何fd
2、select函数参数
int
WSAAPI
select(
_In_ int nfds, //为了兼容Berkeley sockets,填0即可
_Inout_opt_ fd_set FAR * readfds,//检查是否有可读socket
_Inout_opt_ fd_set FAR * writefds,//检查是否有可写socket
_Inout_opt_ fd_set FAR * exceptfds,//检查套接字异常错误
_In_opt_ const struct timeval FAR * timeout //最大等待时间
);
getsocketopt:获取socket错误码
timeout 结构体:
timeout参数有三种可能
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
NULL select完全阻塞,知道客户端有反应,才继续处理
/*
* Structure used in select() call, taken from the BSD file sys/time.h.
*/
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
返回值:
返回值=0 客户端在等待时间没有反应,continue即可
返回值>0 有客户端请求 可读:accept/recv 可写 send 异常 getsocketopt
返回值为SOCKET_ERROR 请求发生错误,调用WSAGetLastError()
demo:
#define _CRT_SECURE_NO_WARNINGS
//#define FD_SETSIZE 128
#include <stdio.h>
#include <stdlib.h>
#include <Winsock2.h>
#include <string.h>
#pragma comment(lib, "Ws2_32.lib")
fd_set allSockets;
BOOL WINAPI fun(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_CLOSE_EVENT:
//释放所有socket
for (u_int i = 0; i < allSockets.fd_count; i++)
{
closesocket(allSockets.fd_array[i]);
}
//清理网络库
WSACleanup();
}
return TRUE;
}
int main(void)
{
SetConsoleCtrlHandler(fun, TRUE);
WORD wdVersion = MAKEWORD(2, 2); //2.1 //22
//int a = *((char*)&wdVersion);
//int b = *((char*)&wdVersion+1);
WSADATA wdScokMsg;
//LPWSADATA lpw = malloc(sizeof(WSADATA));// WSADATA*
int nRes = WSAStartup(wdVersion, &wdScokMsg);
if (0 != nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库");
break;
case WSAEINPROGRESS:
printf("请重新启动");
break;
case WSAEPROCLIM:
printf("请尝试关掉不必要的软件,以为当前网络运行提供充足资源");
break;
}
return 0;
}
//校验版本
if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))
{
//说明版本不对
//清理网络库
WSACleanup();
return 0;
}
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//int a = WSAGetLastError();
if (INVALID_SOCKET == socketServer)
{
int a = WSAGetLastError();
//清理网络库
WSACleanup();
return 0;
}
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12345);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//int a = ~0;
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr *)&si, sizeof(si)))
{
//出错了
int a = WSAGetLastError();
//释放
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
//出错了
int a = WSAGetLastError();
//释放
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}
//清零
FD_ZERO(&allSockets);
//服务器装进去
FD_SET(socketServer, &allSockets);
while (1)
{
fd_set readSockets = allSockets;
fd_set writeSockets = allSockets;
//可以有也可以没有
FD_CLR(socketServer, &writeSockets);
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);
}
}
}
}
}
}
else
{
//发生错误了
break;
}
}
一定要close
//FD_CLR(socketServer, &clientSockets);
closesocket(socketServer);
//int a = FD_ISSET(socketServer, &clientSockets);
//
//释放所有socket
for (u_int i = 0; i < allSockets.fd_count; i++)
{
closesocket(allSockets.fd_array[i]);
}
//清理网络库
WSACleanup();
system("pause");
return 0;
}