前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【网络编程】select模型

【网络编程】select模型

作者头像
半生瓜的blog
发布2023-05-12 21:38:18
1950
发布2023-05-12 21:38:18
举报
文章被收录于专栏:半生瓜のblog半生瓜のblog


select模型

特点

  1. 解决基本c/s模型中,accept,rcev傻等的问题。
    • 傻等阻塞
    • 执行阻塞 send recv accept 在执行的复制粘贴的过程中都是阻塞的。

    (网络模型就是解决阻塞问题的)

  2. 实现多个客户端链接,与多个客户端分别通信。
  3. 用于服务器,因为客户端就一个socket。

服务器端

代码语言:javascript
复制
网络头文件 网络库
打开网络库
校验版本
创建socket
绑定地址与端口
开始监听
 
select    

逻辑

  1. 每个客户端都有socket,服务器也有自己的socket,将所有的socket装进一个数据结构里,即数组。
  2. 通过select函数,遍历1中的socket数组,当某个socket有相应,select就会通过其参数/返回值反馈出来。
  3. 处理。如果见得到的是服务器socket,那就有客户端链接,调用accept。如果检测到客户端socket,那就是客户端请求通信,调用send或者recv。

定义一个装客户端的socket结构体

fd_set

是网络库中定义好的类型。

代码语言:javascript
复制
typedef struct fd_set {
    	//几个有效的
        u_int fd_count;               /* how many are SET? */
        //数组
    	SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

默认FD_SERSIZE 是64,重新宏定义要写在网络库前。
尽量不要太大,大用户量应该用更高级的网络模型。
select模型应用就是小用户量访问,几十几百,简单方便。
    
    fd_set socketClient;
四个参数宏
代码语言:javascript
复制
FD_ZERO
    #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
    将定义好的集合清零
    
    FD_ZERO(&socketClient);
代码语言:javascript
复制
FD_SET 
    #define FD_SET(fd, set) do { \
    u_int __i; \
    for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
        if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \
            break; \
        } \
    } \
    if (__i == ((fd_set FAR *)(set))->fd_count) { \
        if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \
            ((fd_set FAR *)(set))->fd_array[__i] = (fd); \
            ((fd_set FAR *)(set))->fd_count++; \
        } \
    } \
} while(0, 0)
   向集合中添加socket  
代码语言:javascript
复制
FD_CLR
   #define FD_CLR(fd, set) do { \
    u_int __i; \
    for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \
        if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \
            while (__i < ((fd_set FAR *)(set))->fd_count-1) { \
                ((fd_set FAR *)(set))->fd_array[__i] = \
                    ((fd_set FAR *)(set))->fd_array[__i+1]; \
                __i++; \
            } \
            ((fd_set FAR *)(set))->fd_count--; \
            break; \
        } \
    } \
} while(0, 0)
    从集合中删除某个元素,要手动释放,closesocket(socketServer)
    同链表删除。
代码语言:javascript
复制
FD_ISSET
    #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
    判断集合中是否有某个元素
    有-返回非0
    没有-返回0

select

代码语言:javascript
复制
int WSAAPI select(
	int nfds,
    fd_set *readfds,
    fd_set *writefds,
    fd_set *exceptfds,
);
作用

监视socket集合,如果某个socket发生事件,(链接或者收发数据),通过返回值以及参数告诉我们。

参数1

Ignored忽略,填0,仅为了兼容(向下兼容性)Berkeley sockets。

参数2

检查是否有可读的scoket。(是否有消息recv/accept/)

即客户端发来消息了,该socket就会被设置。

初始化所有的socket,通过select投放给系统,系统将有事件发生的socket再复制回来,调用后,这个参数就只剩下有请求的socket。

返回有响应的socket。用个中间变量接收。

参数3

检查是否有可写的socket。

从头到尾遍历出来。

即,使可以给哪些客户端套接字发消息,即send,只要链接成功建立起来了,该客户端套接字就是可写的。

初始化所有的socket,通过select投放给系统,系统将可以写的socket在复制回来,调用后,这个参数就是装着可以被send数据的客户端socket。

参数4

检查套接字上的异常错误,用法同参数23。将所有的socket投放进去。

得到异常套接字上的具体错误码。

getsockopt(socket,SOL_SOCKET,SO_ERROR,buf,buflen);

参数5

最大等待时间,比如当客户端没有请求时,那么select函数可以等一会儿,一段时间过后,还没有,就继续执行select下面的语句,如果有了,就立刻执行下面的语句。

代码语言:javascript
复制
TIMEVAL
    tv_sec 秒
    tv_usec 微秒
    0 0非阻塞状态,立刻返回
    3 4那就再无客户端相应的情况下等待3秒4微秒
NULL
    select完全阻塞,知道客户端有反应,我才继续
返回值
代码语言:javascript
复制
0 客户端在等待时间内没有反应  处理——continue>0 有客户端请求交流了SOCKET_ERROR 发生了错误    	得到错误码WSAGetLaseError()

流程总结

代码语言:javascript
复制
socket集合    socket判断有没有相应的    	返回0,没有,继续挑    	返回>0,有相应    				可读的accept    					  recv    				可写的send    				异常的getsockopt    	SOCK_ERROR

select是阻塞的。

不等待——执行阻塞

半等待——执行阻塞+软阻塞

全等待——执行阻塞+硬阻塞 死等

完整代码

(仅熟悉流程)

代码语言:javascript
复制
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<WinSock2.h> 
#pragma comment(lib,"Ws2_32.lib")



//装所有的socket
fd_set allSocket;

BOOL WINAPI fun(DWORD dwCtrlType)
{
	switch (dwCtrlType)
	{

	case CTRL_CLOSE_EVENT:
		for (u_int i = 0; i < allSocket.fd_count; i++)
		{
			closesocket(allSocket.fd_array[i]);
		}
		WSACleanup();
	}
	return TRUE;
}

int main(void)
{
	//投递一个监视
	//关闭事件
	//控制台点叉退出
	SetConsoleCtrlHandler(fun, TRUE);	


	WORD wdVersion = MAKEWORD(2, 2);
	WSADATA wdSockMsg;
	int nRes = WSAStartup(wdVersion, &wdSockMsg);

	if (nRes != 0)
	{
		printf("网络库打开失败");
		return 0;
	}

	if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
	{
		printf("版本不对");
		WSACleanup();
		return 0;
	}

	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketServer == INVALID_SOCKET)
	{
		printf("创建服务器socket失败");
		int a = WSAGetLastError();
		WSACleanup();
		return 0;
	}
	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)))
	{
		printf("绑定错误");
		int a = WSAGetLastError();
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
	{
		int a = WSAGetLastError();
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}


	//清零
	FD_ZERO(&allSocket);
	//把服务器装进去
	FD_SET(socketServer, &allSocket);
	
	while (1)
	{
		//可读
		fd_set readSocket = allSocket;
		//可写
		fd_set writeSocket = allSocket;
		FD_CLR(socketServer, &writeSocket);
		fd_set errorSocket = allSocket;



		//时间段
		struct timeval st;
		st.tv_sec = 3;
		st.tv_usec = 0;
		//不用哪个哪个位置就写NULL
		int nRes = select(0, &readSocket, &writeSocket, &errorSocket, &st);
		if (nRes == 0)//没有响应的socket
		{
			continue;
		}
		else if (nRes > 0)
		{

			for (u_int i = 0; i < errorSocket.fd_count; i++)
			{
				char str[100] = { 0 };
				int len = 99;
				if (SOCKET_ERROR == getsockopt(errorSocket.fd_array[i], SOL_SOCKET, SO_ERROR,str,&len))
				{
					printf("无法得到错误信息\n");
				}
				printf("%s\n", str);
				
			}

			for(u_int i = 0;i<writeSocket.fd_count;i++)
			{
				//printf("服务器%d %d:可写\n", socketServer, writeSocket.fd_array[i]);
				if (SOCKET_ERROR == send(writeSocket.fd_array[i], "ok", 2, 0))
				{
					//正常 大于0 socket_error 下线0
					int a = WSAGetLastError();
				}
			}

			//有响应
			//遍历socket
			for (u_int i = 0; i < readSocket.fd_count; i++)
			{
				if (readSocket.fd_array[i] == socketServer)
				{
					//有链接(响应)-accept
					SOCKET socketClient = accept(socketServer, NULL, NULL);
					if (socketClient == SOCKET_ERROR)
					{
						//链接出错
						continue;
					}
					FD_SET(socketClient, &allSocket);
					//SEND
					send(readSocket.fd_array[i], "服务器链接成功!", sizeof("服务器链接成功"),0);
				}
				else
				{
					char strBuf[1500] = { 0 };
					//客户端socket
					int nRecv = recv(readSocket.fd_array[i], strBuf, 1500, 0);
					if (nRecv == 0)
					{
						//客户端下线了
						//从集合中去掉		
						SOCKET socketTemp = readSocket.fd_array[i];
						FD_CLR(readSocket.fd_array[i],&allSocket);
						//释放
						closesocket(socketTemp);

					}
					else if(nRecv > 0)
					{
						//接收到了消息
						printf(strBuf);
					}
					else
					{
						//强制下线10054
						
						//出错了SOCK_ERROR
						int a = WSAGetLastError();
						switch (a)
						{
							case 10054:
							{
							SOCKET socketTemp = readSocket.fd_array[i];
							FD_CLR(readSocket.fd_array[i], &allSocket);
							closesocket(socketTemp);
							}
						}
						printf("%d", a);
					}
				}
			}
		}
		else
		{
			//发生错误了
			break;
		}
	}




	//释放socket集合
	for (u_int i = 0; i < allSocket.fd_count; i++)
	{
		closesocket(allSocket.fd_array[i]);
	}
	
	WSACleanup();//正常关闭
	system("pause");
	return 0;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • select模型
    • 特点
      • 服务器端
        • 逻辑
        • 定义一个装客户端的socket结构体
        • select
        • 流程总结
        • 完整代码
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档