目录
一、socket
TCP的通信过程可以大致分为建立连接、数据传输、释放连接。建立连接、释放连接中的三次握手、四次挥手实现是比较麻烦的,而作为应用开发者只想关注数据传输的过程,所以操作系统给开发者提供了socket。
socket的一大功能就是作为TCP网络连接的抽象,在底层socket就是一个已经完成了三次握手的TCP连接,Linux中socket以文件描述符FD作为标识
socket通信过程
- server端会调用Linux的socket(),Linux会新建一个socket。
- server端会操作这个socket把它绑定在一个端口上,这样就可以在这个端口上对外提供服务。
- server端会调用listen()监听新连接。
- 如果client端想要连接server端,那么client端也会新建socket。
- client端随后调用connect()连接server端的端口。
- 此时server端处于监听状态,如果同意客户端连接那么会调用accept(),那么server端就会生成一个新的socket用来与client端通信。
- 待通信完毕后client端会调用close(),进行四次挥手。
操作系统通过socket屏蔽了底层的握手和挥手的操作,使得应用开发者使用更加便捷。如果server端在服务两个client端,那么会有三个socket,其中两个socket用于服务client端,一个socket用于监听新连接,如下图:
server端要同时处理多个socket,这时就出现了IO模型。
二、IO模型
所谓的IO模型指的就是同时操作socket的方案。IO模型有三种,分别是阻塞IO、非阻塞IO、多路复用IO。
阻塞IO(BIO)
阻塞IO就是阻塞调用socket,每个线程服务一个客户端,当调用socket的时候,如果没有数据就一直阻塞,直到有数据为止,server端根据socket的数据进行处理,再将结果写入socket。
阻塞IO的特点:
- 同步读写socket时,线程陷入内核态,每个线程服务一个客户端。
- 当读写成功后,切换回用户态继续执行。
- 开发难度小,代码简单,读取socket,没有数据就阻塞,有数据时再返回,然后处理数据,但是涉及到线程用户态与内核态的切换,开销大。
非阻塞IO(NIO)
非阻塞IO就是非阻塞调用socket,与阻塞IO对比,当调用socket时,不会阻塞到有数据才返回,而是不管有没有数据都返回。然后会一直轮询所有的socket,发现有数据就进行处理,处理结束后再把结果返回给对应的socket。
非阻塞IO的特点:
- 如果暂时无法收发数据,会返回错误。
- 应用会不断的轮询,直到socket可读写。
- 不会陷入内核态,但是需要轮询,非阻塞IO中一个线程负责多个client端。
多路复用
当我们新建socket时,不想自己轮询监控socket是否有数据,让系统来完成这件工作,这时就出现了多路复用。其中一个比较典型的就是epoll。
epoll全称event poll(事件池),epoll里面的事件是各个socket的可读事件,由操作系统来监控socket是否可以操作,我们会非阻塞的调用epoll去询问事件池中哪个事件发生了,epoll会返回发生的事件列表,那么业务就会根据事件列表去调用对应的socket,处理socket对应客户端的请求,最后将结果返回。也就是说多路复用就是将监听socket的工作从业务转移到了操作系统。
多路复用的特点:
- 需要在epoll中注册多个socket。
- 调用epoll,当有事件发生时,返回发生的事件。
- 提供了事件列表,不需要轮询各个socket,但是开发难度大,逻辑复杂,每当有新的连接,就新建socket,把socket事件注册到epoll中,询问epoll有哪些事件发生,根据发生事件处理对应的socket。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了IO模型与socket。如果文中有不对或者不足的地方还希望各位大佬给出指点意见。