三-select模型
select模型是对简单C/S模型的优化,他解决了accept函数阻塞等待连接的问题。并且允许应用程序同时监视多个套接字,从而实现简单的并发请求。通过调用select函数确认一个或多个套接字当前的状态,并根据当前状态进行相应操作。在select模型模型中,select函数是最关键的。
select模型工作原理
select模型维护了一个Socket数组,通过遍历该数组检查当前是否存在就绪socket,并将所有就绪的socket返回。我们遍历该数组提供相应服务。工作原理大致如下:
- 将服务端Socket添加至socket数组中。
- 调用select()函数遍历socket数组。
- 返回就绪socket数组,对该数组集中处理。
- 如果是服务端Socket,调用accept()函数接收连接请求,并将该客户端数组添加至socket数组。
- 如果是客户端Socket,调用send()、recv()函数进行通信,当客户端下线时,从socket数组中移除该Socket。
- 重复执行2-6步。
select模型使用步骤
- 启动Socket服务
- 创建套接字
- 为套接字绑定端口信息
- 监听套接字
- 使用select模型监听套接字集合,处理数据
- 关闭套接字和网络服务
select()函数
该函数定义如下:
int select(int nfds, fd_set readfds, fd_set writefds,
fd_set exceptfds, const struct timeval timeout);
参数
- nfds:忽略,传入0。为了与Berkeley 套接字兼容。
- readfds:可读取套接字的集合,调用函数时传入要监视的套接字,函数返回时保存可读套接字。
- writefds:可写套接字的集合。只要建立连接,则任何时候都可写入,可以传NULL。
- exceptfds:异常套接字集合。
- timeout:指定超时时间。
如果是服务端socket套接字,可读表示当前有客户端进行连接。如果是客户端套接字,可读表示当前有数据发送至服务端。select()函数是一个阻塞函数,如果指定了超时时间且没有socket就绪,select()函数返回。
返回值
- 0:超时
- >0:当前就绪的套接字数量,就绪的套接字保存在readfds中。
- -1:出现错误,可以通过WSAGetLasterror()函数获取错误码。
fd_set结构体
fd_set结构体定义如下:
typedef struct fd_set {
u_int fd_count; //fd_array数组中当前元素个数
SOCKET fd_array[FD_SETSIZE]; //socket数组
} fd_set;
在给select函数传递参数时,我们将需要监听的socket封装进fd_set结构体。select函数会顺序遍历数组,当发现有socket就绪,select函数返回,就绪的socket集合通常保存在readfds中。
下面列出了几个关于fd_set结构体的操作宏:
示例
1 #define _WINSOCK_DEPRECATED_NO_WARNINGS
2
3 #include <iostream>
4 #include <WinSock2.h>
5 #pragma comment(lib, "ws2_32.lib")
6 using namespace std;
7 const int nMajorVersion = 2;
8 const int nMinorVersion = 2;
9
10 int main()
11 {
12 DWORD dwVersion = MAKEWORD(nMajorVersion, nMinorVersion);
13 WSADATA wsaData;
14 int nStartRet = WSAStartup(dwVersion, &wsaData);
15 if (nStartRet != 0)
16 {
17 cout << "WSAStartup failed with error :" << nStartRet << endl;
18 return 1;
19 }
20
21 if (LOBYTE(wsaData.wVersion) != nMajorVersion || HIBYTE(wsaData.wVersion) != nMinorVersion)
22 {
23 cout << "version failed" << endl;
24 WSACleanup();
25 return 1;
26 }
27
28 SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
29 if (sockServer == INVALID_SOCKET)
30 {
31 cout << "socket create failed with code: "<< WSAGetLastError() << endl;
32 WSACleanup();
33 return 1;
34 }
35
36 sockaddr_in addInfo;
37 addInfo.sin_family = AF_INET;
38 addInfo.sin_port = htons(12345);
39 addInfo.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
40 int nBindRet = bind(sockServer, reinterpret_cast<sockaddr*>(&addInfo), sizeof(addInfo));
41 if (SOCKET_ERROR == nBindRet)
42 {
43 cout << "bind failed with code: " << WSAGetLastError() << endl;
44 closesocket(sockServer);
45 WSACleanup();
46 return 1;
47 }
48
49 int nListenRet = listen(sockServer, SOMAXCONN);
50 if (SOCKET_ERROR == nBindRet)
51 {
52 cout << "bind failed with code: " << WSAGetLastError() << endl;
53 closesocket(sockServer);
54 WSACleanup();
55 return 1;
56 }
57
58 fd_set allSockets;
59 FD_ZERO(&allSockets);
60 FD_SET(sockServer, &allSockets);
61 struct timeval tv = { 3,0 };
62
63 while (1)
64 {
65 fd_set tmpAllSockets = allSockets;
66 int nSelectRet = select(0, &tmpAllSockets, NULL, NULL, &tv);
67 if(nSelectRet == 0) continue; //超时
68 else if (nSelectRet == -1) //出错
69 {
70 cout << "select failed with code: " << WSAGetLastError() << endl;
71 break;
72 }
73 else if (nSelectRet > 0)
74 {
75 for (int i = 0; i < tmpAllSockets.fd_count; i++)
76 {
77 SOCKET socketID = tmpAllSockets.fd_array[i];
78
79 if (socketID == sockServer)
80 {
81 SOCKET socketClient = accept(socketID, NULL, NULL);
82 if (socketClient == INVALID_SOCKET)
83 continue;
84
85 FD_SET(socketClient, &allSockets);
86 }
87 else
88 {
89 char buf[1024] = { 0 };
90 int nRecvRet = recv(socketID, buf, 1024, 0);
91 if (nRecvRet == 0) //客户端断开连接
92 {
93 FD_CLR(socketID, &allSockets);
94 closesocket(socketID);
95 continue;
96 }
97 else if (nRecvRet > 0)
98 {
99 cout << socketID << " : " << buf << endl;
100 send(socketID, "我收到了你发送的数据!", sizeof("我收到了你发送的数据!"), 0);
101 }
102 else if (nRecvRet == SOCKET_ERROR)
103 {
104 int nError = WSAGetLastError();
105 if (nError == 10054)
106 {
107 FD_CLR(socketID, &allSockets);
108 closesocket(socketID);
109 continue;
110 }
111 else
112 cout << "recv error." << endl;
113 }
114 }
115 }
116 }
117 }
118
119 for (int i = 0; i < allSockets.fd_count; i++)
120 {
121 closesocket(allSockets.fd_array[i]);
122 }
123 FD_ZERO(&allSockets);
124 WSACleanup();
125 return 0;
126 }
总结
select模型有以下优点:
- select允许同时监视多个套接字的读写状态,使得在单个线程中可以处理多个套接字的I/O操作,提高了系统的效率。
- select模型使用简单,适合初学者设计简单的网络通信。
select模型有以下缺点:
- select()函数本身会造成阻塞。
- 由于需要遍历整个被监视的套接字集合,在大规模并发场景下,select模型性能低下。
- 受限于FD_SET集合有大小限制,后续的拓展性差,无法适用于高并发场景。
- 无法处理大量数据,如果有大量数据需要处理,会导致阻塞其他套接字的读写。
三-select模型的更多相关文章
- windows socket编程select模型使用
int select( int nfds, //忽略 fd_ser* readfds, //指向一个套接字集合,用来检测其可读性 ...
- socket编程的select模型
在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的 ...
- linux下多路复用模型之Select模型
Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模 ...
- 比较一下Linux下的Epoll模型和select模型的区别
一. select 模型(apache的常用) 1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Sel ...
- 基于select模型的udp客户端实现超时机制
参考:http://www.cnblogs.com/chenshuyi/p/3539949.html 多路选择I/O — select模型 其思想在于使用一个集合,该集合中包含需要进行读写的fd,通过 ...
- Winsock IO模型之select模型
之所以称其为select模型是因为它主要是使用select函数来管理I/O的.这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字. int sele ...
- Windows I/O模型之一:Select模型
1.概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock) 四种调用模式: 同步:所谓同步,就是在发出一个功能调用时,在没有得到结果 ...
- socket select模型
由于socket recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他连接不能继续. 如果想改变这种一直等下去的焦急状态,可以多线程来实现(不 ...
- socket select()模型
转载:http://www.cnblogs.com/xiangshancuizhu/archive/2012/10/05/2711882.html 由于socket recv()方法是阻塞式的,当有多 ...
- select模型
在Windows中所有的socket函数都是阻塞类型的,也就是说只有网络中有特定的事件发生时才会返回,在没有发生事件时会一直等待,虽说我们将它们设置为非阻塞状态,但是在对于服务器段而言,肯定会一直等待 ...
随机推荐
- Thymeleaf判断集合是否为空
Thymeleaf判断集合是否为空最近项目使用的是thymeleaf.项目架构是Springboot+Thymeleaf. 在判断集合是否为空的时候踩了坑与大家分享. 以下代码是判断集合是否为空的,m ...
- redis - [07] 数据类型
redis是一个开源(BSD许可)的,内存中的数据结构存储系统,可以用作数据库.缓存和消息中间件MQ.它支持多种类型的数据结构,如字符串(String).散列(Hash).列表(List).集合( ...
- 2. 在Linux 当中安装 Nginx(13步) 下载&安装&启动(详细说明+附加详细截图说明)
2. 在Linux 当中安装 Nginx(13步) 下载&安装&启动(详细说明+附加详细截图说明) @ 目录 2. 在Linux 当中安装 Nginx(13步) 下载&安装&a ...
- 远程debug
1. 在idea中添加远程服务器信息 打开应用配置 填写配置 1. 在 + 选择 Remote JVM Debug 2. 在 2 处填写名称,任意 3. 在 3 填写服务器ip 4. 在 4 填写de ...
- 面试题10- I. 斐波那契数列
地址:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/ <?php /** 写一个函数,输入 n ,求斐波那契(Fibona ...
- netcore后台服务慎用BackgroundService
在 .NET Core 开发中,BackgroundService 是一个非常方便的后台任务运行方式,但它并不适用于所有场景. BackgroundService 一时爽,并发火葬场. Backgro ...
- 分布式锁—6.Redisson的同步器组件
大纲 1.Redisson的分布式锁简单总结 2.Redisson的Semaphore简介 3.Redisson的Semaphore源码剖析 4.Redisson的CountDownLatch简介 5 ...
- 附035.Kubernetes_v1.25.3高可用部署架构二
目录 部署组件 kubeadm介绍 kubelet介绍 kubectl介绍 方案概述 方案介绍 部署规划 节点规划 主机名配置 变量准备 互信配置 环境初始化 部署高可用组件 HAProxy安装 Ke ...
- Python设置递归最大深度
博客地址:https://www.cnblogs.com/zylyehuo/ import sys sys.setrecursionlimit(100000) # 设置最大递归深度,默认是3000
- centos7下扩展根分区(图文详解)
df -h 查看当前系统磁盘使用状况 fdisk -l 可以看见,我新添加了一块硬盘,大小为10G,新磁盘/dev/sdb fdisk /dev/sdb 对新的磁盘进行分区 在交互模 ...