目录
select模型详解
select函数解释
整体代码
select模型在代码上和c/s模型的前面一部分是一样的,可以去看 这个https://blog.csdn.net/weixin_62859191/article/details/128397927?spm=1001.2014.3001.5501,相同的代码如下
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
//第一步 打开网络库/校验版本
WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdSockMsg;
int nRes = WSAStartup(wdVersion, &wdSockMsg);
if (nRes != 0)
{
printf("打开网络库失败\n");
}
if (wdSockMsg.wVersion != HIBYTE(2) || wdSockMsg.wVersion != LOBYTE(2))
{
WSACleanup();
printf("版本不对\n");
return 0;
}
//第二步 创建socket
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketServer == INVALID_SOCKET)
{
int a = WSAGetLastError();//获取错误码
printf("创建socket出错\n");
WSACleanup();
return 0;
}
//第三步 绑定ip地址和端口号
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12332);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if(SOCKET_ERROR == bind(socketServer, (const sockaddr*)&si, sizeof(si)))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
printf("创建socket出错\n");
return 0;
}
//第四步 开始监听
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
printf("监听出错\n");
return 0;
}
closesocket(socketServer);
WSACleanup();
return 0;
}
select模型详解
作用:select模型使用来解决c/s模型中收发消息时阻塞的问题,用在服务器上
需要用到 fd_set 网络中定义好的结构体,结构体原型
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set, FD_SET, *PFD_SET, *LPFD_SET;
系统也给 fd_set 结构体提供了4个操作宏
FD_ZERO:把fd_set结构体中的所有数据清零
FD_SET:在fd_set结构体中添加一个参数
FD_CLR:删除指定的socket变量
FD_ISSET:判断fd_set结构体中是否有指定的socket变量,有返回非0,没有返回0
##代码样例
//创建一个存放socket的结构体
fd_set clientSockets;
FD_ZERO(&clientSockets);//把集合中的所有数据清零
FD_SET(socketServer, &clientSockets);//添加元素进集合
//删除的时候一定要close
FD_CLR(socketServer, &clientSockets);//删除集合中指定的元素
//closesocket(socketServer);
int a = FD_ISSET(socketServer, &clientSockets);//判断集合中是否有指定的元素 存在返回非0,不在返回0
select函数解释
该函数确定一个或多个套接字的状态(如有必要)等待执行同步 I/O。函数原型
int WSAAPI select(
[in] int nfds,
[in, out] fd_set *readfds,
[in, out] fd_set *writefds,
[in, out] fd_set *exceptfds,
[in] const timeval *timeout
);
参数1 nfds:填0,表示可以兼容旧版本
参数2 readfds:检查是否有可读的socket,即客户端发来消息,对应的socket就会被设置
参数3 writefds:检查是否有可写的socket,就是可以给哪些客户端套接字发消息,只要链接成功建立起来了,那该客户端套接字就是可写的。
参数4 exceptfds:检查套接字上的异常错误,用法跟2,3一样
参数5 timeout:这是一个结构体对象,结构体原型如下,该参数可以设置最大等待时间,结构体中的两个成员,tv_sec表示秒,tv_usec表示微妙,设置为00时,非阻塞状态,立刻返回,设置为1,1表示在无客户端响应下等待1秒1微妙,如果填入NULL,会完全阻塞,直到客户端有响应
typedef struct timeval {
long tv_sec;
long tv_usec;
} TIMEVAL, *PTIMEVAL, *LPTIMEVAL;
返回值:该函数如果返回0,表示在客户端等待时间内没有反应,如果返回大于0的数,表示有客户端请求交流,如果返回 SOCKET_ERROR,表示发生了错误,使用WSAGetLastError()可以获取错误码
在使用参数4时,需要用到函数 getsockopt ,该函数可以获取到socket发生错误时的错误信息,函数原型,及使用样例
int WSAAPI getsockopt(
[in] SOCKET s,
[in] int level,
[in] int optname,
[out] char *optval,
[in, out] int *optlen
);
//使用样例
char str[100] = {0};
int len = 99;
getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len)
错误信息返回会给到字符数组str,如果可以找到错误,返回0,否则返回SOCKET_ERROR
整体代码
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
printf("**********服务器***********\n");
//第一步 打开网络库/校验版本
WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdSockMsg;
int nRes = WSAStartup(wdVersion, &wdSockMsg);
if (nRes != 0)
{
printf("打开网络库失败\n");
}
if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion))
{
WSACleanup();
printf("版本不对\n");
return 0;
}
//第二步 创建socket
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketServer == INVALID_SOCKET)
{
int a = WSAGetLastError();//获取错误码
printf("创建socket出错\n");
WSACleanup();
return 0;
}
//第三步 绑定ip地址和端口号
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12332);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if(SOCKET_ERROR == bind(socketServer, (const sockaddr*)&si, sizeof(si)))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
printf("创建socket出错\n");
return 0;
}
//第四步 开始监听
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
printf("监听出错\n");
return 0;
}
//创建一个存放socket的结构体
fd_set allSockets;
FD_ZERO(&allSockets);//把集合中的所有数据清零
//把服务器装进去
FD_SET(socketServer, &allSockets);//添加元素进集合
删除的时候一定要close
//FD_CLR(socketServer, &clientSockets);//删除集合中指定的元素
closesocket(socketServer);
//int a = FD_ISSET(socketServer, &clientSockets);//判断集合中是否有指定的元素 存在返回非0,不在返回0
while (1)
{
fd_set readSockets = allSockets;//第二个参数的中间变量
fd_set writeSockets = allSockets;//第三个参数的中间变量
fd_set errorSockets = allSockets;//第四个参数的中间变量
FD_CLR(socketServer, &writeSockets);
FD_CLR(socketServer, &errorSockets);
struct timeval st;
st.tv_sec = 3;
st.tv_usec = 0;
int sRes = select(0, &readSockets, &writeSockets, NULL, &st);//该函数会改变tempSockets中的元素
if (sRes == 0)
{
//客户端没有响应
continue;
}
else if (sRes > 0)
{
//错误socket处理
for (u_int i = 0; i < errorSockets.fd_count; i++)
{
char str[100] = { 0 };
int len = 99;
//参数4:通过参数2进行参数传址获取错误码
if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len))
{
printf("无法得到错误信息\n");
}
}
//服务器发送消息
for (u_int i = 0; i < writeSockets.fd_count; i++)
{
if (SOCKET_ERROR == send(writeSockets.fd_array[i], "OK", 2, 0))//客户端正常关闭也时返回SOCKET_ERROR
{
printf("发送出错\n");
int a = WSAGetLastError();//获取错误码
}
}
//客户端有发消息
for (u_int i = 0; i < readSockets.fd_count; i++)
{
if (readSockets.fd_array[i] == socketServer)//表示有客户端请求连接
{
//创建客户端socket
SOCKET socketClient = accept(socketServer, NULL, NULL);
if (socketClient == INVALID_SOCKET)
{
//链接出错
continue;
}
//否则把该socket放入tempSocket数组中
FD_SET(socketClient, &allSockets);
printf("客户端连接成功\n");
}
else//否则就是有客户端发送消息,需要接收消息
{
char buf[1500] = { 0 };
int nRecv = recv(readSockets.fd_array[i], buf, 1499, 0);
if (nRecv == 0)//表示客户端下线
{
SOCKET socketTemp = readSockets.fd_array[i];
printf("客户端下线\n");
FD_CLR(readSockets.fd_array[i], &allSockets);
closesocket(socketTemp);
}
else if (nRecv > 0)//接收到客户端发来的消息
{
printf("%d %s\n", nRecv, buf);
}
else//SOCKET_ERROR 非正常关闭
{
SOCKET socketTemp = readSockets.fd_array[i];
printf("客户端被迫退出\n");
int a = WSAGetLastError();//获取错误码
FD_CLR(readSockets.fd_array[i], &allSockets);
closesocket(socketTemp);
}
}
}
}
else
{
//发生错误
break;
}
}
//释放所有的socket
for (u_int i = 0; i < allSockets.fd_count; i++)
{
closesocket(allSockets.fd_array[i]);
}
closesocket(socketServer);
WSACleanup();
return 0;
}