windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型

windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第1张图片

执行阻塞:
默认情况下socket是blocking的,即函数accept(), recv/recvfrom, send/sendto,connect等,需等待函数执行结束之后才能够返回(此时操作系统切换到其他进程执行)。accpet()等待到有client连接请求并接受成功之后,recv/recvfrom需要读取完client发送的数据之后才能够返回。
阻塞与非阻塞套接字
sockets_tutorial

这几种网络模型就是解决阻塞问题的,简单的模型解决傻等阻塞,复杂的模型解决执行阻塞。
本文思维导图

1 select 模型原理介绍

模型用于服务器
解决服务器无法有多个客户端连接的情况。
windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第2张图片
代码逻辑:
windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第3张图片
select处理逻辑
windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第4张图片

2 select 处理步骤

2.1 第1步 定义一个装socket的结构体

windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第5张图片

2.1.1 fd_set结构的意义和应用

各种 Windows 套接字函数和服务提供程序(如 select 函数)使用fd_set结构将套接字放入"set"中,用于各种目的,例如使用 select 函数的 readfds 参数测试给定套接字的可读性。
windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第6张图片
本质就是一个数组

注意

  • FD_SETSIZE 宏定义要放在之前,否则无效
    windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第7张图片
    windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第8张图片

2.1.2 四个操作fd_set的宏函数

	fd_set fd_set_socks;//装服务端、多个客户端的集合
	
	FD_ZERO(&fd_set_socks);//只将count清0 -> 高效
	
	FD_SET(socket_server, &fd_set_socks);//添加一个元素
	
	int res_set = FD_ISSET(socket_server, &fd_set_socks);//判断一个socket是否在集合中
	if (0 == res_set)
		printf("不在集合中, res_set = %d\n", res_set);
	else
		printf("在集合中, res_set = %d\n", res_set);

	FD_CLR(socket_server, &fd_set_socks);//删除一个元素
	res_set = FD_ISSET(socket_server, &fd_set_socks);//判断一个socket是否在集合中
	if (0 == res_set)
		printf("不在集合中, res_set = %d\n", res_set);
	else
		printf("在集合中, res_set = %d\n", res_set);

	closesocket(socket_server);//从集合中删除后要手动释放socket,否则会造成内存泄漏

windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第9张图片

2.2 第2步 select ()函数

windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第10张图片

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

2.2.1 [in] int nfds

忽视。包含 nfds 参数只是为了与 Berkeley 套接字兼容。

2.2.2 [in, out] fd_set *readfds

指向一组要检查可读性的套接字的可选指针。
in -> 最初的fd_set结构体 如:1,2,3,4,5
out -> 返回有响应的客户端,如1,2响应,就readfds 就是1 和 2

	fd_set fd_set_socks;//装服务端和多个客户端的集合 1,2,3,4,5
	select(0, &fd_set_socks, &fd_set_socks, &fd_set_socks, &time_val);//此时,fd_set_socks为1,2

检查 处理 accept 和 recv

测试参数2的demo
select_server.c

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#pragma comment(lib,"Ws2_32.lib")


int main(int argc, char* argv[])
{
	/*打开网络库*/
	WORD ws_verson = MAKEWORD(2, 2);
	WSADATA wd_sock_msg;
	int ret = WSAStartup(ws_verson, &wd_sock_msg);
	if (0 != ret)
	{
		switch (ret)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库!");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库!");
			break;
		case WSAEPROCLIM:
			printf("请重新启动!");
			break;
		case WSAEINPROGRESS:
			printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
			break;
		case WSAEFAULT:
			printf("参数错误!");
			break;
		default:
			break;
		}
		return 0;
	}
	printf("网络库打开成功!\n");

	/*检查版本*/
	if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
		2 != HIBYTE(wd_sock_msg.wVersion))   //高位-》得到副版本
	{
		//说明版本不对,要关闭网络库
		WSACleanup();
		return -1;
	}
	printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));

	/*创建套接字*/
	SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socket_server)
	{
		printf("socket created failed,error_code:%d\n", WSAGetLastError());
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("socket created success,return code:%d\n", socket_server);

	/*绑定地址*/
	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 res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
	if (SOCKET_ERROR == res_bind)
	{
		printf("bind failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("bind success,return code:%d\n", res_bind);

	/*监听listen*/
	int res_listen = listen(socket_server, SOMAXCONN);
	if (SOCKET_ERROR == res_listen)
	{
		printf("listen failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("listen success,return code:%d\n", res_listen);

	/*select模型*/
	/*第1步*/
	fd_set fd_set_socks;//装服务端和多个客户端的集合
	FD_ZERO(&fd_set_socks);//只将count清0 -> 高效

	FD_SET(socket_server, &fd_set_socks);//将服务端装进去

	while (1)
	{
		fd_set fd_set_socks_temp = fd_set_socks;
		struct timeval time_val;
		time_val.tv_sec = 3;
		time_val.tv_usec = 0;
		//第1种情况,检查accept 和 recv
		int res_select = select(0, &fd_set_socks_temp, NULL, NULL, &time_val);
		if (0 == res_select)//无响应
		{
			continue;
		}
		else if (res_select > 0)//有响应
		{
			/*	判断响应是服务端产生的还是客户端产生的
				响应的是服务端,说明有客户端连接进来 -> accept
				响应的是客户端,说明有客户端 下线/向服务端发送消息/错误 -> 具体处理
			*/
			for (u_int i = 0; i < fd_set_socks_temp.fd_count; i++)
			{
				if (fd_set_socks_temp.fd_array[i] == socket_server)//是服务端响应
				{
					//accept
					SOCKET socket_client = accept(socket_server, NULL, NULL);
					if (INVALID_SOCKET == socket_client)//连接出错
						continue;

					FD_SET(socket_client, &fd_set_socks);//链接客户端成功,放进fd_set_socks中
					//send
					//...
				}
				else//是客户端响应
				{
					char str_buf[1500] = { 0 };
					int res_recv = recv(fd_set_socks_temp.fd_array[i], str_buf, 1499, 0);
					if (0 == res_recv)//客户端下线了
					{
						printf("socket_id: %d 下线\n", fd_set_socks.fd_array[i]);

						//1.从fd_set_socks中删掉
						//(先用临时变量接收,再释放),不然有可能会将后面的删掉
						SOCKET socket_temp = fd_set_socks.fd_array[i];
						FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
						//2.手动释放
						closesocket(socket_temp);
					}
					else if (res_recv > 0)//接收到了消息
					{
						printf("服务器socket%d接收到了客户端的消息:%s\n", fd_set_socks.fd_array[i], str_buf);
					}
					else//SOCK_ERROR
					{
						int err_code = WSAGetLastError();
						//客户端强行关闭 出错 error_code 10054
						switch (err_code)
						{
						case 10054:
						{
							{
								printf("client offline,error_code:%d\n", err_code);
								//1.从fd_set_socks中删掉
								//(先用临时变量接收,再释放),不然有可能会将后面的删掉
								SOCKET socket_temp = fd_set_socks.fd_array[i];
								FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
								//2.手动释放
								closesocket(socket_temp);
							}
						}
							break;
						}
					}
				}
			}

		}
		else//发生错误了
		{
			break;
		}
	}


	//int res_set = FD_ISSET(socket_server, &fd_set_socks);//判断一个socket是否在集合中
	//if (0 == res_set)
	//	printf("不在集合中, res_set = %d\n", res_set);
	//else
	//	printf("在集合中, res_set = %d\n", res_set);

	//FD_CLR(socket_server, &fd_set_socks);//删除一个元素
	//res_set = FD_ISSET(socket_server, &fd_set_socks);//判断一个socket是否在集合中
	//if (0 == res_set)
	//	printf("不在集合中, res_set = %d\n", res_set);
	//else
	//	printf("在集合中, res_set = %d\n", res_set);

	/*释放所有的socket*/
	/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
	for (u_int i = 0; i < fd_set_socks.fd_count; i++)
	{
		closesocket(fd_set_socks.fd_array[i]);//从集合中删除后要手动释放socket,否则会造成内存泄漏
	}

	/*关闭网络库*/
	WSACleanup();//关闭网络库

	printf("\n");
	system("pause");
	return 0;
}

client.c

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#pragma comment(lib, "Ws2_32.lib")

int main(void)
{
	WORD wdVersion = MAKEWORD(2, 2); 
	WSADATA wdScokMsg;
	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
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socketServer)
	{
		int a = WSAGetLastError();
		//清理网络库
		WSACleanup();
		return 0;
	}

	//链接服务器
	struct sockaddr_in serverMsg;
	serverMsg.sin_family = AF_INET;
	serverMsg.sin_port = htons(12345);
	serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	if (SOCKET_ERROR == connect(socketServer, (struct sockaddr*)&serverMsg, sizeof(serverMsg)))
	{
		int a = WSAGetLastError();
		closesocket(socketServer);
		//清理网络库
		WSACleanup();
		return 0;
	}

	while (1)
	{
		char buf[1500] = { 0 };
		scanf("%s", buf);
		if ('q' == buf[0])//当输入q的时候退出循环,关闭程序,正常下线
		{
			break;
		}
		if (SOCKET_ERROR == send(socketServer, buf, strlen(buf), 0))
		{
			//出错了
			int a = WSAGetLastError();
			//根据实际情况处理
			printf("%d\n", a);
		}
	}

	/*释放所有的socket*/
	/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
	for (u_int i = 0; i < fd_set_socks.fd_count; i++)
	{
		closesocket(fd_set_socks.fd_array[i]);//从集合中删除后要手动释放socket,否则会造成内存泄漏
	}

	/*关闭网络库*/
	WSACleanup();//关闭网络库

	system("pause");
	return 0;
}

2.2.3 [in, out] fd_set *writefds

指向要检查可写性的一组套接字的可选指针。
检查 处理 send

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#pragma comment(lib,"Ws2_32.lib")


int main(int argc, char* argv[])
{
	/*打开网络库*/
	WORD ws_verson = MAKEWORD(2, 2);
	WSADATA wd_sock_msg;
	int ret = WSAStartup(ws_verson, &wd_sock_msg);
	if (0 != ret)
	{
		switch (ret)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库!");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库!");
			break;
		case WSAEPROCLIM:
			printf("请重新启动!");
			break;
		case WSAEINPROGRESS:
			printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
			break;
		case WSAEFAULT:
			printf("参数错误!");
			break;
		default:
			break;
		}
		return 0;
	}
	printf("网络库打开成功!\n");

	/*检查版本*/
	if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
		2 != HIBYTE(wd_sock_msg.wVersion))   //高位-》得到副版本
	{
		//说明版本不对,要关闭网络库
		WSACleanup();
		return -1;
	}
	printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));

	/*创建套接字*/
	SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socket_server)
	{
		printf("socket created failed,error_code:%d\n", WSAGetLastError());
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("socket created success,return code:%d\n", socket_server);

	/*绑定地址*/
	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 res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
	if (SOCKET_ERROR == res_bind)
	{
		printf("bind failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("bind success,return code:%d\n", res_bind);

	/*监听listen*/
	int res_listen = listen(socket_server, SOMAXCONN);
	if (SOCKET_ERROR == res_listen)
	{
		printf("listen failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("listen success,return code:%d\n", res_listen);

	/*select模型*/
	/*第1步*/
	fd_set fd_set_socks;//装服务端和多个客户端的集合
	FD_ZERO(&fd_set_socks);//只将count清0 -> 高效

	FD_SET(socket_server, &fd_set_socks);//将服务端装进去

	while (1)
	{
		fd_set fd_set_read_socks = fd_set_socks;
		fd_set fd_set_write_socks = fd_set_socks;//里面包含了server
		FD_CLR(socket_server, &fd_set_write_socks);//先把服务器删掉

		struct timeval time_val;
		time_val.tv_sec = 3;
		time_val.tv_usec = 0;
		//第1种情况,检查accept 和 recv
		int res_select = select(0, &fd_set_read_socks, &fd_set_write_socks, NULL, &time_val);
		if (0 == res_select)//无响应
		{
			continue;
		}
		else if (res_select > 0)//有响应
		{
			/*判断客户端是否可写*/
			for (u_int i = 0; i < fd_set_write_socks.fd_count; i++)
			{
				//printf("服务器:%d, socket id %d 可写\n", socket_server, fd_set_write_socks.fd_array[i]);
				//send
				int res_send = send(fd_set_write_socks.fd_array[i], "ok", 2, 0);
				if (SOCKET_ERROR == res_send)
				{
					printf("send error error_code:%d\n", WSAGetLastError());
				}
				else if (2 == res_send)
				{
					//printf("发送成功");
				}
			}

			/*	判断响应是服务端产生的还是客户端产生的
				响应的是服务端,说明有客户端连接进来 -> accept
				响应的是客户端,说明有客户端 下线/向服务端发送消息/错误 -> 具体处理
			*/
			for (u_int i = 0; i < fd_set_read_socks.fd_count; i++)
			{
				if (fd_set_read_socks.fd_array[i] == socket_server)//是服务端响应
				{
					//accept
					SOCKET socket_client = accept(socket_server, NULL, NULL);
					if (INVALID_SOCKET == socket_client)//连接出错
						continue;

					FD_SET(socket_client, &fd_set_socks);//链接客户端成功,放进fd_set_socks中
					//send
					//...
				}
				else//是客户端响应
				{
					char str_buf[1500] = { 0 };
					int res_recv = recv(fd_set_read_socks.fd_array[i], str_buf, 1499, 0);
					if (0 == res_recv)//客户端下线了
					{
						printf("socket_id: %d 下线\n", fd_set_socks.fd_array[i]);

						//1.从fd_set_socks中删掉
						//(先用临时变量接收,再释放),不然有可能会将后面的删掉
						SOCKET socket_temp = fd_set_socks.fd_array[i];
						FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
						//2.手动释放
						closesocket(socket_temp);
					}
					else if (res_recv > 0)//接收到了消息
					{
						printf("服务器socket%d %d接收到了客户端的消息:%s\n",
							   socket_server, fd_set_socks.fd_array[i], str_buf);
					}
					else//SOCK_ERROR
					{
						int err_code = WSAGetLastError();
						//客户端强行关闭 出错 error_code 10054
						switch (err_code)
						{
						case 10054:
						{
							{
								printf("client offline,error_code:%d\n", err_code);
								//1.从fd_set_socks中删掉
								//(先用临时变量接收,再释放),不然有可能会将后面的删掉
								SOCKET socket_temp = fd_set_socks.fd_array[i];
								FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
								//2.手动释放
								closesocket(socket_temp);
							}
						}
							break;
						}
					}
				}
			}

		}
		else//发生错误了
		{
			break;
		}
	}

	/*释放所有的socket*/
	/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
	for (u_int i = 0; i < fd_set_socks.fd_count; i++)
	{
		closesocket(fd_set_socks.fd_array[i]);//从集合中删除后要手动释放socket,否则会造成内存泄漏
	}

	/*关闭网络库*/
	WSACleanup();//关闭网络库

	printf("\n");
	system("pause");
	return 0;
}

2.2.4 [in, out] fd_set *exceptfds

指向一组要检查错误的套接字的可选指针。
检查 处理 error

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#pragma comment(lib,"Ws2_32.lib")


int main(int argc, char* argv[])
{
	/*打开网络库*/
	WORD ws_verson = MAKEWORD(2, 2);
	WSADATA wd_sock_msg;
	int ret = WSAStartup(ws_verson, &wd_sock_msg);
	if (0 != ret)
	{
		switch (ret)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库!");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库!");
			break;
		case WSAEPROCLIM:
			printf("请重新启动!");
			break;
		case WSAEINPROGRESS:
			printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
			break;
		case WSAEFAULT:
			printf("参数错误!");
			break;
		default:
			break;
		}
		return 0;
	}
	printf("网络库打开成功!\n");

	/*检查版本*/
	if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
		2 != HIBYTE(wd_sock_msg.wVersion))   //高位-》得到副版本
	{
		//说明版本不对,要关闭网络库
		WSACleanup();
		return -1;
	}
	printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));

	/*创建套接字*/
	SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socket_server)
	{
		printf("socket created failed,error_code:%d\n", WSAGetLastError());
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("socket created success,return code:%d\n", socket_server);

	/*绑定地址*/
	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 res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
	if (SOCKET_ERROR == res_bind)
	{
		printf("bind failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("bind success,return code:%d\n", res_bind);

	/*监听listen*/
	int res_listen = listen(socket_server, SOMAXCONN);
	if (SOCKET_ERROR == res_listen)
	{
		printf("listen failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("listen success,return code:%d\n", res_listen);

	/*select模型*/
	/*第1步*/
	fd_set fd_set_socks;//装服务端和多个客户端的集合
	FD_ZERO(&fd_set_socks);//只将count清0 -> 高效

	FD_SET(socket_server, &fd_set_socks);//将服务端装进去

	while (1)
	{
		fd_set fd_set_read_socks = fd_set_socks;
		fd_set fd_set_write_socks = fd_set_socks;//里面包含了server
		FD_CLR(socket_server, &fd_set_write_socks);//先把服务器删掉
		fd_set fd_set_err_socks = fd_set_socks;

		struct timeval time_val;
		time_val.tv_sec = 3;
		time_val.tv_usec = 0;
		//第1种情况,检查accept 和 recv
		int res_select = select(0, &fd_set_read_socks, &fd_set_write_socks, &fd_set_err_socks, &time_val);
		if (0 == res_select)//无响应
		{
			continue;
		}
		else if (res_select > 0)//有响应
		{
			/*处理错误*/
			for (u_int i = 0; i < fd_set_err_socks.fd_count; i++)
			{
				char err_buf[100] = { 0 };
				int len = 99;
				int res_err = getsockopt(fd_set_err_socks.fd_array[i], SOL_SOCKET, SO_ERROR, err_buf, &len);
				if (SOCKET_ERROR == res_err)
				{
					printf("getsockopt failed, err_code %d\n", WSAGetLastError());
				}
				else if (0 == res_err)
				{
					printf("no error happens\n");
				}
			}

			/*判断客户端是否可写*/
			for (u_int i = 0; i < fd_set_write_socks.fd_count; i++)
			{
				//printf("服务器:%d, socket id %d 可写\n", socket_server, fd_set_write_socks.fd_array[i]);
				//send
				int res_send = send(fd_set_write_socks.fd_array[i], "ok", 2, 0);
				if (SOCKET_ERROR == res_send)
				{
					printf("send error error_code:%d\n", WSAGetLastError());
				}
				else if (2 == res_send)
				{
					//printf("发送成功");
				}
			}

			/*	判断响应是服务端产生的还是客户端产生的
				响应的是服务端,说明有客户端连接进来 -> accept
				响应的是客户端,说明有客户端 下线/向服务端发送消息/错误 -> 具体处理
			*/
			for (u_int i = 0; i < fd_set_read_socks.fd_count; i++)
			{
				if (fd_set_read_socks.fd_array[i] == socket_server)//是服务端响应
				{
					//accept
					SOCKET socket_client = accept(socket_server, NULL, NULL);
					if (INVALID_SOCKET == socket_client)//连接出错
						continue;

					FD_SET(socket_client, &fd_set_socks);//链接客户端成功,放进fd_set_socks中
					//send
					//...
				}
				else//是客户端响应
				{
					char str_buf[1500] = { 0 };
					int res_recv = recv(fd_set_read_socks.fd_array[i], str_buf, 1499, 0);
					if (0 == res_recv)//客户端下线了
					{
						printf("socket_id: %d 下线\n", fd_set_socks.fd_array[i]);

						//1.从fd_set_socks中删掉
						//(先用临时变量接收,再释放),不然有可能会将后面的删掉
						SOCKET socket_temp = fd_set_socks.fd_array[i];
						FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
						//2.手动释放
						closesocket(socket_temp);
					}
					else if (res_recv > 0)//接收到了消息
					{
						printf("服务器socket%d %d接收到了客户端的消息:%s\n",
							   socket_server, fd_set_socks.fd_array[i], str_buf);
					}
					else//SOCK_ERROR
					{
						int err_code = WSAGetLastError();
						//客户端强行关闭 出错 error_code 10054
						switch (err_code)
						{
						case 10054:
						{
							{
								printf("client offline,error_code:%d\n", err_code);
								//1.从fd_set_socks中删掉
								//(先用临时变量接收,再释放),不然有可能会将后面的删掉
								SOCKET socket_temp = fd_set_socks.fd_array[i];
								FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
								//2.手动释放
								closesocket(socket_temp);
							}
						}
							break;
						}
					}
				}
			}

		}
		else//发生错误了
		{
			break;
		}
	}

	/*释放所有的socket*/
	/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
	for (u_int i = 0; i < fd_set_socks.fd_count; i++)
	{
		closesocket(fd_set_socks.fd_array[i]);//从集合中删除后要手动释放socket,否则会造成内存泄漏
	}

	/*关闭网络库*/
	WSACleanup();//关闭网络库

	printf("\n");
	system("pause");
	return 0;
}

2.2.5 [in] const timeval *timeout

选择等待的最长时间,以 TIMEVAL 结构的形式提供。对于阻止操作,将超时参数设置为 null。

struct timeval time_val;
time_val.tv_sec = 3;
time_val.tv_usec = 0;
//select()函数会处理3秒的时间,执行阻塞
int res_select = select(0, &fd_set_read_socks, &fd_set_write_socks, &fd_set_err_socks, &time_val);

2.3 控制台关闭处理事件

windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第11张图片

fd_set fd_set_socks;//装服务端和多个客户端的集合
BOOL WINAPI fun(DWORD dw_ctrl_type)
{
	switch (dw_ctrl_type)
	{
	case CTRL_CLOSE_EVENT:
	{
		{
			/*释放所有的socket*/
			/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
			for (u_int i = 0; i < fd_set_socks.fd_count; i++)
			{
				closesocket(fd_set_socks.fd_array[i]);//从集合中删除后要手动释放socket,否则会造成内存泄漏
			}

			/*关闭网络库*/
			WSACleanup();//关闭网络库
		}
	}
		break;
	default:
		break;
	}
	return TRUE;
}


SetConsoleCtrlHandler(fun, TRUE);

selece_server.c

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#pragma comment(lib,"Ws2_32.lib")


fd_set fd_set_socks;//装服务端和多个客户端的集合
BOOL WINAPI fun(DWORD dw_ctrl_type)
{
	switch (dw_ctrl_type)
	{
	case CTRL_CLOSE_EVENT:
	{
		{
			/*释放所有的socket*/
			/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
			for (u_int i = 0; i < fd_set_socks.fd_count; i++)
			{
				closesocket(fd_set_socks.fd_array[i]);//从集合中删除后要手动释放socket,否则会造成内存泄漏
			}

			/*关闭网络库*/
			WSACleanup();//关闭网络库
		}
	}
		break;
	default:
		break;
	}
	return TRUE;
}

int main(int argc, char* argv[])
{
	SetConsoleCtrlHandler(fun, TRUE);

	/*打开网络库*/
	WORD ws_verson = MAKEWORD(2, 2);
	WSADATA wd_sock_msg;
	int ret = WSAStartup(ws_verson, &wd_sock_msg);
	if (0 != ret)
	{
		switch (ret)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库!");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库!");
			break;
		case WSAEPROCLIM:
			printf("请重新启动!");
			break;
		case WSAEINPROGRESS:
			printf("请尝试关掉不必要的软件,为当前网络运行提供充足资源!");
			break;
		case WSAEFAULT:
			printf("参数错误!");
			break;
		default:
			break;
		}
		return 0;
	}
	printf("网络库打开成功!\n");

	/*检查版本*/
	if (2 != LOBYTE(wd_sock_msg.wVersion) || //地位-》得到主版本
		2 != HIBYTE(wd_sock_msg.wVersion))   //高位-》得到副版本
	{
		//说明版本不对,要关闭网络库
		WSACleanup();
		return -1;
	}
	printf("网络库版本号:(%d,%d)\n", LOBYTE(wd_sock_msg.wVersion), HIBYTE(wd_sock_msg.wVersion));

	/*创建套接字*/
	SOCKET socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socket_server)
	{
		printf("socket created failed,error_code:%d\n", WSAGetLastError());
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("socket created success,return code:%d\n", socket_server);

	/*绑定地址*/
	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 res_bind = bind(socket_server, (const struct sockaddr *) &si, sizeof(si));
	if (SOCKET_ERROR == res_bind)
	{
		printf("bind failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("bind success,return code:%d\n", res_bind);

	/*监听listen*/
	int res_listen = listen(socket_server, SOMAXCONN);
	if (SOCKET_ERROR == res_listen)
	{
		printf("listen failed,error_code:%d\n", WSAGetLastError());
		closesocket(socket_server);
		WSACleanup();//关闭网络库
		return -1;
	}
	else
		printf("listen success,return code:%d\n", res_listen);

	/*select模型*/
	/*第1步*/
	//fd_set fd_set_socks;//装服务端和多个客户端的集合
	FD_ZERO(&fd_set_socks);//只将count清0 -> 高效

	FD_SET(socket_server, &fd_set_socks);//将服务端装进去

	while (1)
	{
		fd_set fd_set_read_socks = fd_set_socks;
		fd_set fd_set_write_socks = fd_set_socks;//里面包含了server
		FD_CLR(socket_server, &fd_set_write_socks);//先把服务器删掉
		fd_set fd_set_err_socks = fd_set_socks;

		struct timeval time_val;
		time_val.tv_sec = 3;
		time_val.tv_usec = 0;
		//第1种情况,检查accept 和 recv
		int res_select = select(0, &fd_set_read_socks, &fd_set_write_socks, &fd_set_err_socks, &time_val);
		if (0 == res_select)//无响应
		{
			continue;
		}
		else if (res_select > 0)//有响应
		{
			/*处理错误*/
			for (u_int i = 0; i < fd_set_err_socks.fd_count; i++)
			{
				char err_buf[100] = { 0 };
				int len = 99;
				int res_err = getsockopt(fd_set_err_socks.fd_array[i], SOL_SOCKET, SO_ERROR, err_buf, &len);
				if (SOCKET_ERROR == res_err)
				{
					printf("getsockopt failed, err_code %d\n", WSAGetLastError());
				}
				else if (0 == res_err)
				{
					printf("no error happens\n");
				}
			}

			/*判断客户端是否可写*/
			for (u_int i = 0; i < fd_set_write_socks.fd_count; i++)
			{
				//printf("服务器:%d, socket id %d 可写\n", socket_server, fd_set_write_socks.fd_array[i]);
				//send
				int res_send = send(fd_set_write_socks.fd_array[i], "ok", 2, 0);
				if (SOCKET_ERROR == res_send)
				{
					printf("send error error_code:%d\n", WSAGetLastError());
				}
				else if (2 == res_send)
				{
					//printf("发送成功");
				}
			}

			/*	判断响应是服务端产生的还是客户端产生的
				响应的是服务端,说明有客户端连接进来 -> accept
				响应的是客户端,说明有客户端 下线/向服务端发送消息/错误 -> 具体处理
			*/
			for (u_int i = 0; i < fd_set_read_socks.fd_count; i++)
			{
				if (fd_set_read_socks.fd_array[i] == socket_server)//是服务端响应
				{
					//accept
					SOCKET socket_client = accept(socket_server, NULL, NULL);
					if (INVALID_SOCKET == socket_client)//连接出错
						continue;

					FD_SET(socket_client, &fd_set_socks);//链接客户端成功,放进fd_set_socks中
					//send
					//...
				}
				else//是客户端响应
				{
					char str_buf[1500] = { 0 };
					int res_recv = recv(fd_set_read_socks.fd_array[i], str_buf, 1499, 0);
					if (0 == res_recv)//客户端下线了
					{
						printf("socket_id: %d 下线\n", fd_set_socks.fd_array[i]);

						//1.从fd_set_socks中删掉
						//(先用临时变量接收,再释放),不然有可能会将后面的删掉
						SOCKET socket_temp = fd_set_socks.fd_array[i];
						FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
						//2.手动释放
						closesocket(socket_temp);
					}
					else if (res_recv > 0)//接收到了消息
					{
						printf("服务器socket%d %d接收到了客户端的消息:%s\n",
							   socket_server, fd_set_socks.fd_array[i], str_buf);
					}
					else//SOCK_ERROR
					{
						int err_code = WSAGetLastError();
						//客户端强行关闭 出错 error_code 10054
						switch (err_code)
						{
						case 10054:
						{
							{
								printf("client offline,error_code:%d\n", err_code);
								//1.从fd_set_socks中删掉
								//(先用临时变量接收,再释放),不然有可能会将后面的删掉
								SOCKET socket_temp = fd_set_socks.fd_array[i];
								FD_CLR(fd_set_socks.fd_array[i], &fd_set_socks);
								//2.手动释放
								closesocket(socket_temp);
							}
						}
							break;
						}
					}
				}
			}

		}
		else//发生错误了
		{
			break;
		}
	}

	/*释放所有的socket*/
	/*程序结束先关闭socket,再关闭网络库;因为socket是网络库里面的函数*/
	for (u_int i = 0; i < fd_set_socks.fd_count; i++)
	{
		closesocket(fd_set_socks.fd_array[i]);//从集合中删除后要手动释放socket,否则会造成内存泄漏
	}

	/*关闭网络库*/
	WSACleanup();//关闭网络库

	printf("\n");
	system("pause");
	return 0;
}

select模型服务端DEMO

4 select总结

windows TCP/IP 网络编程(二)5种windows网络模型(1) select模型_第12张图片

你可能感兴趣的:(#,Windows网络编程,c++,c语言,visualstudio,socket)