socket网络编程

首先我来定流的概念,一个流可以是文件,socketpipe等等可以I/O操作的内核象。

不管是文件,是套接字,是管道,我都可以把他看作流。

之后我讨论I/O的操作,通read,我可以从流中入数据;通write,我可以往流写入数据。在假定一个情形,我需要从流中数据,但是流中没有数据,(典型的例子,客端要从socket如数据,但是服没有把数据回来),这时么办

阻塞:阻塞是个什概念呢?比如某个候你在等快,但是你不知道快么时来,而且你没有的事可以干(或者接下来的事要等快来了才能做);那你可以去睡了,因你知道快送来一定会你打个电话(假定一定能叫醒你)。

非阻塞忙轮询:接着上面等快的例子,如果用忙轮询的方法,那你需要知道快递员的手机号,然后钟给他挂个电话你到了没?

很明一般人不会用第二做法,不仅显很无,浪费话费占用了快递员大量的时间
大部分程序也不会用第二做法,因第一方法经济简单经济是指消耗很少CPU时间,如果线程睡眠了,就掉出了系列,暂时不会去瓜分CPU时间片了。

了了解阻塞是如何行的,我讨论缓冲区,以及内核冲区,最I/O事件解清楚。冲区的引入是了减少I/O操作而引起繁的系统调用(你知道它很慢的),当你操作一个流,更多的是以冲区为单行操作,是相于用而言。于内核来,也需要冲区。

有一个管道,A管道的写入方,B管道的出方。

始内核冲区是空的,B为读出方,被阻塞着。然后首先A往管道写入,这时候内核冲区由空的态变到非空状,内核就会生一个事件告醒来了,个事件姑且称之冲区非空
但是冲区非空事件通知B后,B没有出数据;且内核许诺了不能把写入管道中的数据候,A写入的数据会滞留在内核冲区中,如果内核也冲区了,B仍未数据,最内核冲区会被填候会生一个I/O事件,告诉进A,你等等(阻塞)了,我个事件定义为冲区

后来B数据了,于是内核的冲区空了出来,这时候内核会告A,内核冲区有空位了,你可以从眠中醒来了,继续写数据了,我个事件叫做冲区非
事件Y1通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核冲区空了。候内核就告B,你需要阻塞了!,我时间冲区空

四个情形涵盖了四个I/O事件,冲区冲区空,冲区非空,冲区非(注都是的内核冲区,且四个术语都是我生造的,仅为其原理而造)。四个I/O事件是行阻塞同的根本。(如果不能理解是什概念,操作系,信号量,条件量等任方面的相)。

然后我说说阻塞I/O的缺点。但是阻塞I/O模式下,一个线程只能理一个流的I/O事件。如果想要同时处理多个流,要(fork),要线(pthread_create),很不幸方法效率都不高。
于是再来考非阻塞忙轮询I/O方式,我们发现可以同时处理多个流了(把一个流从阻塞模式切到非阻塞模式再此不予讨论):
while true {
for i in stream[]; {
if i has data
read until unavailable
}
}
只要不停的把所有流从到尾一遍,又从头开始。这样可以理多个流了,但这样的做法然不好,因如果所有的流都没有数据,那只会白白浪CPU里要充一点,阻塞模式下,内核I/O事件的理是阻塞或者醒,而非阻塞模式下I/O事件交其他象(后文介select以及epoll理甚至直接忽略

了避免CPU,可以引了一个代理(一始有一位叫做select的代理,后来又有一位叫做poll的代理,不两者的本是一的)。个代理比较厉害,可以同时观多流的I/O事件,在空候,会把当前线程阻塞掉,当有一个或多个流有I/O事件,就从阻塞中醒来,于是我的程序就会轮询一遍所有的流(于是我可以把字去掉了)。代码长这样:
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
于是,如果没有I/O事件生,我的程序就会阻塞在select。但是依然有个问题,我select那里仅仅知道了,有I/O事件生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我只能无差别轮询所有流,找出能出数据,或者写入数据的流,们进行操作。
但是使用select,我O(n)的无差别轮询复杂度,同时处理的流越多,没一次无差别轮询时间就越。再次
这么多,于能好好解epoll
epoll可以理解event poll,不同于忙轮询和无差别轮询epoll之会把哪个流生了怎I/O事件通知我。此们对这些流的操作都是有意的。(复杂度降低到了O(1)
讨论epoll实现细节之前,先把epoll的相操作列出:

epoll_create 建一个epoll象,一般epollfd = epoll_create()

epoll_ctl epoll_add/epoll_del的合体),往epoll象中增加/除某一个流的某一个事件
比如
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册冲区非空事件,即有数据流入
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册冲区非事件,即流可以被写入
epoll_wait(epollfd,...)等待直到注册的事件
(注:当一个非阻塞流的冲区冲区空,write/read会返回-1,并errno=EAGAINepoll冲区非冲区非空事件)。

一个epoll模式的代大概的子是:
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till
}

}

需要了解底层设备访问的原理,所以用高层应言的我,需要了解一下Linux设备访问机制,尤其是理一非阻塞IO的原理方法,准的术语好像是叫多用。以下文章部分句子有引用之,恕没有一一指出出

于接触Linux内核或设备驱动开发者,一定清楚pollselect统调用,以及从2.5版本引入的epoll机制(epoll机制包含三个系统调用)。网上于它的文章,有用法的,甚为详细,更有分析源代的,又比深入,且枝节颇多。经过几篇文章的阅读,我把得比核心的西写下来吧。我的用意是尽可能以简单的概念,比三者的异同。

经查找我才确定下来,pollselect应该归类为这样的系统调用,它可以阻塞地同支持非阻塞的IO设备,是否有事件生(如可,可写,有高错误输出,出现错误等等),直至某一个设备了事件或者超了指定的等待时间——也就是它职责不是做IO,而是帮助用者找当前就设备。同型的WindowsIOCP,它也是理多路用,只是把IO和探封装在了一起了。

的知有两点:1fd2op->poll

Linux里面,设备都被抽象文件,一系列的设备文件就有自己独立的虚文件系,所以,设备在系统调用参数中的表示就是file descriptionfd就是一个整数(特地,入,出,错误输出分别对应fd012)。与内核打交道的候,传递整数的fd可以在自己的文件系中作检查是否合法,如果只是返回指就不能这样操作了,竟指是无差无意的。

fd访问file,通file可以访问fileOperator里面我心的一个fileOp就是poll。因统调pollselect,就是依靠个文件操作poll实现的。poll文件操作有两个参数,一个是文件本身,一个可以看做是当设备尚未就绪时调用的回函数,个函数是把设备自己特有的等待传给内核,内核把当前的程挂到其中(因设备绪时设备应该醒在自己特有等待列中的所有点,这样当前程就取了完成的信号了)。poll文件操作返回的必是一组标准的掩,其中的各个位指示当前的不同的就(全0没有任何事件触)。

谈谈早期多路用的版本pollselect

而言,pollselect的共同点就是,全部指定设备做一次poll,当然往往都是没有就的,那就会通函数把当前程注册到设备的等待列,如果所有设备返回的掩都没有示任何的事件触,就去掉回函数的函数指入有限的睡眠状,再恢和不断做poll,再作有限的睡眠,直到其中一个设备有事件触发为止。只要有事件触,系统调用返回,回到用户态,用就可以fd或者写操作了。当然,不是所有的设备都就的喔,那就得不断地poll或者select了,而做一次这样的系统调用都得轮询所有的设备,次数是设备*(睡眠次数-1),也就是时间复杂度是On),得做几次On)呢。可在普遍的服器程序,需要同发监听数千个接,并且接需要重使用的情况,pollselect就存在这样的性能瓶。另外,数千个设备fd,都需要将其从用间复制到内核空里的开销不可忽略。

pollselect放在一起,是因其机制一致,而参数和数据构就略有不同select一次性入三作用于不同信道的设备fd,分入,出和错误异常。各fd期待各所特有的,由代指定的一事件,如入信道期待入就入挂起和错误等事件。 然后,select就挑选调用者心的fdpoll文件操作,检测返回的掩,看看是否有fd所属信道感趣的事件,比如看看个属于出信道的fd有没有出就等一系列的事件生,一地,如果有一个fd生感趣事件就返回用了。select了同时处理三使用不同的事件判断规则fd,采用了位的方式表示,一一个位,位度是当中最大的fd,上限是1024,三就是3072,而且这还只是入的位有一大小的出的位。当fd数越来越多,所需的存储开销大。

既然,一fd理起来比粗放,那就各个fd自己准好了。poll()统调用是System V的多元I/O解决方案。它有三个参数,第一个是pollfd构的数,也就是指向一fd及其相信息的指,因为这构包含的除了fd有期待的事件掩和返回的事件掩实质上就是将select的中的fd入和出参数到一个构之下,也不再把fd,也不再硬fd趣的事件,用者自己定。这样,不使用位组织数据,也就不需要位的全部遍了。按照一般列地遍fdpoll文件操作,检查返回的掩是否有期待的事件,以及做是否有挂起和错误的必要性检查,如果有事件触,就可以返回用了

回到pollselect的共同点,面高并接的用情境,它们显现出原来没有考到的不足,poll比起select又有所改了。除了上述的用都需要做一次从用到内核空的拷这样问题,就是当这样用情境pollselect会不得不多次操作,并且次操作都很有可能需要多次入睡眠状,也就是多次全部轮询fd,我们应该么处理一些会出而无意的操作。

些重而无意的操作有:1、从用到内核空,既然监视这几个fd,甚至期待的事件也不会改,那拷疑就是重而无意的,我可以内核期保存所有需要监视fd甚至期待事件,或者可以再需要时对部分期待事件行修改;2、将当前线流加入到fd对应设备的等待列,这样做无非是哪一个设备绪时通知程退出用,明的开发者想到,那就找个代理的回函数,代替当前程加入fd的等待列好了(也是我后来才总结出来,Linux的等待列,实质上是回函数列吧,也可以使用宏来将当前加入等待列,其就是将醒当前程的回函数加入列)。这样,像poll统调用一,做poll文件操作发现尚未绪时,它就入的一个回函数,epoll指定的回函数,它不再像以前的poll统调用指定的回函数那,而是就将那个代理的回函数加入设备的等待列就好了,个代理的回函数就自己乖乖地等待设备绪时将它醒,然后它就把设备fd放到一个指定的地方,同时唤醒可能在等待的程,到个指定的地方取fd就好了。我12合起来就可以这样做了,只拷一次fd,一旦确定了fd就可以做poll文件操作,如果有事件当然好啦,上就把fd放到指定的地方,而通常都是没有的,那就给这fd的等待列加一个函数,有事件就自fd放到指定的地方,当前程不需要再一个个poll和睡眠等待了。

epoll机制就是这样的了。然,fd少的候,当前程一个个地等问题不大,可是在和尚多了,方丈就不好管了。以前设备事件触发时,只负责唤醒当前程就好了,而当前程也只能傻傻地在poll里面等待或者循,再来一次poll,也不知道个由设备提供的poll性能如何,能不能检查出当前程已在等待了就立即返回,当然,我也不明白做了一遍的poll之后,去掉回函数指了,得再做,不是好了会去程的

在就事件触函数多做一。本来设备还没就用一个回函数了,在再在个回函数里面做一个注册另一个回函数的操作,目的就是使得设备事件触多走一,不仅仅醒当前程,要把自己的fd放到指定的地方。就像收本子的班,以前得一个个学生地去有没有本子,如果没有,它得等待一段时间而后又继续问在好了,只走一次,如果没有本子,班就告大家去那里交本子,当班想起要取本子,就去那里看看或者等待一定时间后离,有本子到了就叫醒他,然后取走。个道理很简单,就是老和班干的,大家多做一点工作,我的工作就松很多了,尤其是需要管理的西越来越多

这种机制或者模式,我想在JavaFutureTask里面应该也会用到的,一堆在线程池里面跑着的线程(当然是任,不是线程,接口是Callable<V>,不是Runnable.run,是Callable.call,它是可以返回果的),先做好就应该理呀,可是道得一个个问吗?干脆就好了,就按照既定的操作暴露自己,这样FutureTaskget方法就可以上知道当前最先完成的线程了,就可以取此线程返回果了。

epoll由三个系统调成,分epoll_createepoll_ctlepoll_waitepoll_create用于建和初始化一些内部使用的数据构;epoll_ctl用于添加,除或者修改指定的fd及其期待的事件,epoll_wait就是用于等待任何先前指定的fd事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值