Windows网络编程~TCP/IP~select(四)

1、select模型特点:

  1. 解决基本模型中accept以及recv阻塞的问题
  2. 实现多个客户端连接,与多个客户端分别通信
  3. 用于服务器

2、应用范围:

  • 小用户访问量

3、select模型流程

  1. 打开网络库
  2. 校验版本
  3. 创建socket
  4. 绑定地址与端口
  5. 开始监听
  6. 调用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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值