socket网络编程进阶篇--------如何写一个并发的TCP服务器(基于IO复用)

本文深入探讨了如何使用IO复用(包括select、poll和epoll)技术来构建并发TCP服务器。对比了多线程和IO复用的优缺点,并详细解释了IO复用的工作原理,通过实例代码展示了基于select和poll函数的服务器端实现。
摘要由CSDN通过智能技术生成

Table of Contents

预备知识

 1. 什么是IO复用?

2.多线程和IO复用对比

具体过程

1.概述 

2.select函数

3.poll函数

4.epoll函数

实例代码

1.基于select函数的服务器端

​​2.基于poll实现的服务器端

参考:



在基础篇介绍了怎么从零开始写一个简单的tcp udp服务器,
在上一篇写的简单的tcp服务器中,他也叫循环服务器
对多个客户端的请求,同一时刻只能处理一个请求.但是每个请求处理的时候会稍微快一些.
在这一篇讲一讲并发tcp服务器如何实现,什么叫并发服务器呢?
对多个客户端的请求,同一时刻能处理多个请求,但是每个请求处理的时候会稍微慢一些
并发TCP服务器的实现可以有三种方式:多进程方式,多线程方式,多路复用方式
用多进程的方法也可以实现并发服务器,但是存在两个缺点
1.太占用cpu和内存资源,因为没来一个客户端请求就得创建一个进程.
2.实现进程间的通信(IPC)比较麻烦,提高了编程难度.
所以引入了第二种 ------基于IO复用方式实现的并发服务器.
再看下面的内容之前可先看前一篇博客 <socket网络编程基础篇>作为基础

预备知识

 1. 什么是IO复用?

io复用是五种网络IO模型中的一种,那这五种网络IO模型又具体指哪些呢?

https://blog.csdn.net/ocean_fan/article/details/79622956

2.多线程和IO复用对比

多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,假定有10000个连接,开这么多个线程需要80G的内存空间!即使调整每个线程的栈空间,也很难满足更多的需求。攻击者可以利用这一点发动DDoS,只要一个连接连上服务器什么也不做,就能吃掉服务器几M的内存,这不同于多进程模型,线程间内存无法共享,因为所有线程处在同一个地址空间中。内存是多线程模型的软肋。
在UNIX平台下多进程模型擅长处理并发长连接,但却不适用于连接频繁产生和关闭的情形。Windows平台忽略此项。 同样的连接需要的内存数量并不比多线程模型少,但是得益于操作系统虚拟内存的Copy on  Write机制,fork产生的进程和父进程共享了很大一部分物理内存。但是多进程模型在执行效率上太低,接受一个连接需要几百个时钟周期,产生一个进程 可能消耗几万个CPU时钟周期,两者的开销不成比例。而且由于每个进程的地址空间是独立的,如果需要进行进程间通信的话,只能使用IPC进行进程间通 信,而不能直接对内存进行访问。在CPU能力不足的情况下同样容易遭受DDos,攻击者只需要连上服务器,然后立刻关闭连接,服务端则需要打开一个进程再关闭。
同时需要保持很多的长连接,而且连接的开关很频繁,最高效的模型是非阻塞、异步IO模型。而且不要用select/poll,这两个API的有着O(N)的时间复杂度。在Linux用epoll,BSD用kqueue,Windows用IOCP,或者用libevent封装的统一接口(对于不同平台libevent实现时采用各个平台特有的API),这些平台特有的API时间复杂度为O(1)。然而在非阻塞,异步I/O模型下的编程是非常痛苦的。由于I/O操作不再阻塞,报文的解析需要小心翼翼,并且需要亲自管理维护每个链接的状态。并且为了充分利用CPU,还应结合线程池,避免在轮询线程中处理业务逻辑。 

具体过程

1.概述 

 上一篇介绍了简单TCP服务器实现的过程,咱们先来回顾一下整个服务器客户端的工作过程.
--->服务器用socket创建监听套接字(比如该套接字的文件描述符号为4)
-->调用listen函数监听该套接字
-->新来一个客户端调用connect请求连接
--->服务器调用accpet函数接受客户端请求并创建了针对这个客户端的数据传输套接字(比如文件描述符号为5) 
--->在这个套接字5上调用read函数读客户端发过来的数据
-->新来一个客户端调用connect请求连接
--->服务器调用accpet函数接受客户端请求并创建了针对这个客户端的数据传输套接字(比如文件描述符号为6) 
--->在这个套接字6上调用read函数读客户端发过来的数据
-->新来一个客户端调用connect请求连接
--->服务器调用accpet函数接受客户端请求并创建了针对这个客户端的数据传输套接字(比如文件描述符号为7) 
--->在这个套接字7上调用read函数读客户端发过来的数据
........(多个客户端请求的时候就不断延续上述过程) 

 上述的这样一个过程存在一个严重的问题
accpet函数在没有客户端连接时会阻塞,同样read/write函数在客户端没有数据传过来的也会阻塞
所以在上述多个客户端请求的过程中,
只要排队在上面的客户端没有传数据过来,调用read函数就会阻塞在那,后面继续到来的客户端请求就会响应不了
同样只要没有新的客户端连接过来,调用accept就会阻塞在那,前面发数据过来的客户端的请求也响应不了

这样根本就不能处理多个客户端的服务请求.
那怎么办呢?所以就引入了多路io复用,他有三种实现机制----select函数,poll函数,epoll函数

 

 

  • 对于select函数工作过程的理解

select函数是怎么工作的呢?我是这么理解的
上面这个服务器客户端交互过程本质上是文件io函数(accept/read)对文件描述符(4,5,6,7)的操作过程.
出现问题的罪魁祸首就是,
accep/read函数"自作主张"就直接去连接/读取文件描述符对应的套接字,
丝毫不管对应文件描述符的套接字是否可以连接/读取
万一对应文件描述符的套接字没有连接/数据请求传输过来,这两个函数自然就会阻塞.


理想的情况应该是有一个"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值