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

在这里插入图片描述

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

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

1 select 模型原理介绍

模型用于服务器
解决服务器无法有多个客户端连接的情况。
在这里插入图片描述
代码逻辑:
在这里插入图片描述
select处理逻辑
在这里插入图片描述

2 select 处理步骤

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

在这里插入图片描述

2.1.1 fd_set结构的意义和应用

各种 Windows 套接字函数和服务提供程序(如 select 函数)使用fd_set结构将套接字放入"set"中,用于各种目的,例如使用 select 函数的 readfds 参数测试给定套接字的可读性。
在这里插入图片描述
本质就是一个数组

注意

  • FD_SETSIZE 宏定义要放在<WinSock2.h>之前,否则无效
    在这里插入图片描述
    在这里插入图片描述

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,否则会造成内存泄漏

在这里插入图片描述

2.2 第2步 select ()函数

在这里插入图片描述

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 <WinSock2.h>
#include <stdio.h>
#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 <stdio.h>
#include <stdlib.h>
#include <Winsock2.h>
#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 <WinSock2.h>
#include <stdio.h>
#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 <WinSock2.h>
#include <stdio.h>
#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 控制台关闭处理事件

在这里插入图片描述

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 <WinSock2.h>
#include <stdio.h>
#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总结

在这里插入图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux网络编程TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础() 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五I/O模型 selectselect改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三方式 IPC对象的持续性 24进程间通信介绍() 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列() msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量() 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程() 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现
Windows Sockets网络编程》是WindowsSockets网络编程领域公认的经典著作,由Windows Sockets2.0规范解释小组负责人亲自执笔,权威性毋庸置疑。它结合大量示例,对WindowsSockets规范进行了深刻地解读,系统讲解了WindowsSockets网络编程及其相关的概念、原理、主要命令、操作模式,以及开发技巧和可能的陷阱,从程序员的角度给出了大量的建议和最佳实践,是学习WindowsSockets网络编程不可多得的参考书。   全书分为三部分:第一部分(第1~6章),提供了翔实的背景知识和框架方面的概念,借助于此框架,读者可理解WinSock的具体细节,包括WindowsSockets概述、OSI网络参考模型TCP/IP协议簇中的协议和可用的服务、WinSock网络应用程序的框架及其工作机制、WinSock的三操作模式、socket通信机制等;第部分(第7~12章),以FTP客户端实例为基础介绍了函数实例库,还介绍了客户端程序、服务器程序和DLL中间构件及它们的相应函数,并涵盖socket命令和选项及移植BSDSockets相关事项等;第三部分(第13~17章),介绍了应用程序调试技术和工具,针对应用编程中的陷阱的建议和措施,WinSockAPI的多操作系统平台,WinSock规范的可选功能和WinSock规范2.0中的所有新功能。 译者序 序 前言 第1章 Windows Sockets概述 1.1 什么是Windows Sockets 1.2 Windows Sockets的发展历史 1.3 Windows Sockets的优势 1.3.1 Windows Sockets是一个开放的标准 1.3.2 Windows Sockets提供源代码可移植性 1.3.3 Windows Sockets支持动态链接 1.3.4 Windows Sockets的优点 1.4 Windows Sockets的前景 1.5 结论 第2章 Windows Sockets的概念 2.1 OSI网络模型 2.2 WinSock网络模型 2.2.1 信息与数据 2.2.2 应用协议 2.3 WinSock中的OSI层次 2.3.1 应用层 2.3.2 表示层 2.3.3 会话层 2.3.4 传输层 2.3.5 网络层 2.3.6 数据链路层 2.3.7 物理层 2.4 模块化的层次框 2.5 服务和协议 2.6 协议和API 第3章 TCP/IP协议服务 3.1 什么是TCP/IP 3.2 TCP/IP的发展历史 3.3 传输服务 3.3.1 无连接的服务:UDP 3.3.2 面向连接的服务:TCP 3.3.3 传输协议的选择:UDP与TCP的对比 3.4 网络服务 3.4.1 IP服务 3.4.2 ICMP服务 3.5 支持协议和服务 3.5.1 域名服务 3.5.2 地址解析协议 3.5.3 其他支持协议 3.6 TCP/IP的发展前景 第4章 网络应用程序工作机制 4.1 客户端-服务器模型 4.2 网络程序概览 4.3 socket的打开 4.4 socket的命名 4.4.1 sockaddr结构 4.4.2 sockaddr_in结构 4.4.3 端口号 4.4.4 本地IP地址 4.4.5 什么是socket名称 4.4.6 客户端socket名称是可选的 4.5 与另一个socket建立关联 4.5.1 服务器如何准备建立关联 4.5.2 客户端如何发起一个关联 4.5.3 服务器如何完成一个关联 4.6 socket之间的发送与接收 4.6.1 在“已连接的”socket上发送数据 4.6.2 在“无连接的”socket上发送数据 4.6.3 接收数据 4.6.4 socket解复用器中的关联 4.7 socket的关闭 4.7.1 closesocket 4.7.2 shutdown 4.8 客户端和服务器概览 第5章 操作模式 5.1 什么是操作模式 5.1.1 不挂机,等待:阻塞 5.1.2 挂机后再拨:非阻塞 5.1.3 请求对方回拨:异步 5.2 阻塞模式 5.2.1 阻塞socket 5.2.2 阻塞函数 5.2.3 伪阻塞的问题 5.2.4 阻塞钩子函数 5.2.5 阻塞情境 5.2.6 撤销阻塞操作 5.2.7 阻塞操作中的超时 5.2.8 无最少接收限制值 5.2.9 代码示例 5.3 非阻塞模式 5.3.1 怎样使socket成为非阻塞的 5.3.2 成功与失败不是绝对的 5.3.3 探询而非阻塞 5.3.4 显式地避让 5.3.5 代码示例 5.4 异步模式 5.4.1 认识异步函数 5.4.2 撤销异步操作 5.4.3 代码示例 5.4.4 AU_T

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值