在大家常看到的计网五层模型里,可能找不到socket的位置,因为socket在运输层以上,应用层以下。
这里同情一下计网7层模型。表示层和会话层被阉割掉了。表示层我感觉倒是无所谓,就是格式转换。
会话层还是有点东西的。值得拿出来讲。
socket其实在第五层,会话层,英文叫session。
看到session是不是很容易联想到web服务端的session,浏览器的session,此session非彼session。但此间隐含的深意是类似的。
我们熟悉的session,是用来标识一个链接的用户属性的,就是用来表明,谁是谁。
session层取这个名字,我觉得也是有点异曲同工之妙的。
我们学习TCP族协议,是不是学到,TCP比IP多了端口号对吧,这个TCP协议吭哧吭哧的,辛辛苦苦的把数据运到了计算机的端口这里,然后呢?
总得有人来接一下这个数据吧,人家大老远跑来的。
TCP咚咚咚,敲门,“你好,这是端口号,80的数据,麻烦签收一下”。
我愿意称socket为管道连接器。
不管是什么类型的IO,还是什么语言,都少不了bind()这一个操作,意思是让socket监听xx端口的数据流。
数据有很多,管道接口就一个。
但是不同链接来到socket这边可不是混为一谈的,对每个链接accept成功之后,返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接,这样,就把不同的TCP链接分隔开来了,知道谁是谁。
如果一个TCP的服务完成之后,这个描述字就被关闭。
我们也应该知道,操作系统里,与网络的联通工作,是内核在做的,用户应用是在应用层这边了。
netIO必然有个数据转移的过程。
究竟是应用不停的问socket,“嗨,有我的信息吗?”
还是说等socket来通知应用呢?
应用问了之后,如果socket没有回答,是要一直等待下去吗,还是继续询问呢?socket当时没有数据是直接回答还是等有数据了再回答呢?
这些都是问题,这也引出了IO的同步、异步,阻塞,非阻塞这几种分类。
同步与异步的差异就是,同步是做一件事,必须得到一个结果。而异步,做了一件事之后,可能暂时是没有结果的。应用可以在这个没有结果的时间里,去干别的事情。等到结果来了,再继续下一阶段。
但是不管同步还是异步,目标都是要把事情给做完。
同步阻塞
同步阻塞,我们由上图可以看出,一切是从recvfrom这个调用开始的。
我们看下这个调用什么意思。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:标识一个已连接套接口的描述字
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。是以下一个或者多个标志的组合体,具体可选内容,可以参考百度百科
from:(可选)指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向from缓冲区长度值。
这个函数,是接收一个描述字的数据的内容的。flags可以操控阻塞或者不阻塞,以及数据返回的结果等等设置。
描述字我们上面说过了,是scoket的accept被触发后,内核为这个新的tcp链接生成的标识。
我们都知道TCP有3次握手,4次挥手,在这期间,也有数据在不断的传递的,标识这个TCP链接的描述字,就一直在内核里存着,直到链接彻底结束为止。
那这个描述字代表的链接,有没有数据?这是个问题。也是应用迫切需要知道的。
同步阻塞:
recvfrom拿着描述字问内核要数据,内核没有数据,那就两边都干等着,直到内核有数据,返回过去,才进行下一步。
同步非阻塞
不断的拿着描述字,去询问内核现在该tcp有没有数据传来,没有的话直接返回没有。
如果有数据,这个数据复制期间,也是阻塞的。
上面这两种,同一时间只能处理单个用户,这肯定不是我们想要看到的,主要的限制点在哪里,因为我们每次recvfrom都是一个socket描述字。
多路复用
这个名词听上去有点高大上,redis就是因为多路复用,才可以单线程也能有这么高的效率。
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
maxfdp : 需要监视的最大文件描述符加1
readfds:需要检测的可读文件描述符的集合
writefds:可写文件描述符的集合
errorfds:异常文件描述符的集合
timeout:等待时间,如果是null,则表示阻塞式等待,直到有事件就绪,才会返回。0表示非阻塞式等待,没有事件就立即返回,大于0表示等待的时间。
返回值:大于0表示就绪的文件描述符的个数,0表示等待时间到了,小于0表示调用失败
多路复用select版本,不是一开始就去recvfrom的,而是先用select去查询,所有scoket描述字,瞅瞅它里面有没有内容(就绪),可写描述符,就被存到writefds里,返回就绪的数目。
多路复用还有poll版,epoll版,最强的还是epoll。
epoll没有,需要监视的最大文件描述符加1(select里的maxfdp),这个限制
“epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的,IO效率不随FD数目增加而线性下降。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。”
非阻塞异步
aio_read调用异步读取,直接返回,等待有数据返回之后,才会进行后续的步骤。
信号驱动式(Signal-Driven) I/O
感觉跟异步IO差不多,对于TCP而言,信号驱动的I/O方式近乎无用,因为导致这种通知的条件为数众多,每一个来进行判别会消耗很大资源,与前几种方式相比优势尽失