SELECT通信模型

155 阅读3分钟

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

服务器端:

1、包含网络头文件网络库


/*

* 基于Windows的网络编程协议库

* 并链接到Ws2_32.lib库

*/

#include<WinSock2.h>

#pragma comment(lib,"Ws2_32.lib")

2、打开网络库


/*

* 1. 打开网络库

*/

WSADATA wdScoket;

int nRes = WSAStartup(wdVersion, &wdScoket);

if (nRes) {

switch (nRes) {

case WSASYSNOTREADY:

{

printf("错误代码:%d\n", WSASYSNOTREADY);

printf("原因:底层网络子系统尚未准备好进行网络通信。\n");

printf("解决方案:重启电脑,并检查库文件是否存在\n");

break;

}

case WSAVERNOTSUPPORTED:

{

printf("错误代码:%d\n", WSAVERNOTSUPPORTED);

printf("原因:此特定Windows套接字实现不提供所请求的Windows套接字支持的版本。\n");

printf("解决方案:指定版本不支持,切换换低版本\n");

break;

}

case WSAEINPROGRESS:

{

printf("错误代码:%d\n", WSAEINPROGRESS);

printf("原因:正在阻止Windows Sockets 1.1操作。\n");

printf("解决方案:重启电脑。\n");

break;

}

case WSAEPROCLIM:

{

printf("错误代码:%d\n", WSAEPROCLIM);

printf("原因:已达到对Windows套接字实现支持的任务数量的限制。\n");

printf("解决方案:网络连接达到上限或阻塞,关闭不必要软件。\n");

break;

}

case WSAEFAULT:

{

printf("错误代码:%d\n", WSAEFAULT);

printf("原因:IpWSAData参数不是有效指针。\n");

printf("解决方案:检查参数错误\n");

break;

}

return 0;

}

}

printf("打开网络库成功!\n");

3、校验版本

/*

* 3. 校验版本

*/

if (HIBYTE(wdScoket.wVersion) != 2 || LOBYTE(wdScoket.wVersion) != 2) {

printf("版本有问题!\n");

WSACleanup();

return 0;

}

printf("版本校验成功!\n");

4、创建SOCKET

/*

* 4. 创建socket

*/

SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (socketServer == INVALID_SOCKET) {

int err = WSAGetLastError();

printf("服务器创建socket失败!错误码为: %d\n",err);

WSACleanup();

return 0;

}

printf("服务器创建socket成功!\n");

5、绑定地址与端口

 

/*

* 5. 绑定地址与端口

*/

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");

if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*) & si, sizeof(si))) {

int err = WSAGetLastError();

printf("服务器bind失败,错误码为: %d\n", err);

closesocket(socketServer);

WSACleanup();

return 0;

}

printf("服务器bind成功!\n");

6、开始监听

/*

* 6. 开始监听

*/

if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) {

int err = WSAGetLastError();

printf("服务器liten失败,错误码为: %d\n");

closesocket(socketServer);

WSACleanup();

return 0;

}

printf("服务器listen成功!\n");

7、SELECT(重点)

7.1将服务器句柄装入fd_set

//把集合清零

FD_ZERO(&allSockets);

//装入服务器端

FD_SET(socketServer,&allSockets);

7.2开始轮询循环

//防止轮询数组被传址引用清空

fd_set recvSockets = allSockets;

fd_set sendSockets = allSockets;

fd_set errorSockets = allSockets;

//服务器不会给自己发消息,把服务器从sensockets中移除

FD_CLR(socketServer, &sendSockets);

//设置每次等待时间

struct timeval st;

st.tv_sec = 3;//3秒

st.tv_usec = 3;//3微秒

7.2.1使用select查询有信号的socket句柄

//开始select

int nRes = select(0, &recvSockets, &sendSockets, &errorSockets, &st);

7.2.2没有socket句柄由信号则重新查询

if (nRes == 0) {//没有socket响应

continue;

}

7.2.3select出错则进行错误处理

else {//出现异常

int err = WSAGetLastError();

printf("服务器select失败!错误码为: %d\n", err);

break;

}

7.2.4处理有信号的socket句柄

7.2.4.1处理有读请求的句柄

如果是服务器端:accept后加入fd_set

//循环处理有读请求的句柄

for (u_int i = 0; i < recvSockets.fd_count; i++) {

//如果是服务器句柄有请求,表明有新的客户端要进行accept

if (recvSockets.fd_array[i] == socketServer) {

SOCKET socketClient = accept(socketServer, NULL, NULL);

//accept新客户端失败

if (socketClient == INVALID_SOCKET) {

int err = WSAGetLastError();

printf("accept新的客户端失败,错误码为: %d\n", err);

continue;

}

//accept新客户端成功
FD_SET(socketClient, &allSockets);

printf("服务器获取客户端句柄成功,fd_set中共有%d个句柄!\n", allSockets.fd_count);

}

如果是客户端:recv信息,并根据结果判断是否有recv异常

//如果是客户端,就进行recv

else {

char strBuff[1500] = { 0 };

int nRecv = recv(recvSockets.fd_array[i],strBuff,1500,0);

if (nRecv == 0) {

//客户端正常掉线

SOCKET socketTemp = recvSockets.fd_array[i];

FD_CLR(recvSockets.fd_array[i], &allSockets);

closesocket(socketTemp);

 

printf("客户端已掉线,fd_set中共有%d个句柄!\n", allSockets.fd_count);

}

//接收到消息就打印出来

else if(nRecv > 0){

printf("客户端的消息是:%s\n", strBuff);

}

//如果遇到错误

else {

int err = WSAGetLastError();

switch (err) {

case 10053:

case 10054:

SOCKET socketTemp = recvSockets.fd_array[i];

FD_CLR(recvSockets.fd_array[i], &allSockets);

closesocket(socketTemp);

 

printf("客户端已掉线,fd_set中共有%d个句柄!\n", allSockets.fd_count);

}

}
7.2.4.2处理有写请求的句柄

//循环处理有写请求的句柄

for (u_int i = 0; i < sendSockets.fd_count; i++) {

if (send(sendSockets.fd_array[i], "hello", 5, 0) == SOCKET_ERROR) {

int err = WSAGetLastError();

}

}

7.2.4.3处理有异常请求的句柄

//循环处理有错误的句柄

for (u_int i = 0; i < errorSockets.fd_count; i++) {

char str[100] = { 0 };

int len = 0;

if (getsockopt(errorSockets.fd_array[i],SOL_SOCKET,SO_ERROR,str,&len) == SOCKET_ERROR) {

printf("getsocketopt无法获取相关信息!\n");

int getopterr = WSAGetLastError();

printf("服务器getsocketopt失败,错误码为: %d\n", getopterr);

}

//打印getsockopt错误信息

printf("%s\n", str);

}

运行情况

image.png

image.png

image.png