网络编程 select模型

       

目录

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱笑的蛐蛐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值