Linux服务端编程经验

学习linux服务端编程的过程中跟一个拥有多年linux服务端编程经验的前辈请教了一番,有的地方理解的或许没有那么透彻,

在这里记录一下,后期内功修炼的差不多了再回来看看是否有新的理解。

主流服务器模式

做过很多服务器项目,看过很多源码,现在主流服务器都是Reactor模式,下面是一个简单的epoll实现Reactor模式的例子
http://blog.csdn.net/rankun1/article/details/69938640
先用select或者poll、epoll分离出io事件  然后处理io事件(发送或者读取网络数据) 最后对收到的数据进行解包
然后进行相应的处理,复杂一点的会将解好的数据包丢到一个任务队列里面去

阻塞和非阻塞

阻塞和非阻塞强调的是是否在程序处理耗时操作上等待
这两个概念一般是对于IO或其它可能耗时等待的操作而言,我们这里就io操作来分析:
无论是网络io还是文件io,io描述符都可以设置为阻塞或者非阻塞的,
拿recv来举例,io操作可以分为两个步骤:第一个步骤是recv时判断是否有数据,第二个步骤接收数据
阻塞io和非阻塞io的区别在于第一个步骤时 没有数据的情况下(如果确定了有数据了,你再去recv,阻塞和非阻塞效果是一样的)
对于阻塞的io:如果recv时有数据,则立即进行第二个步骤去接收完数据返回;发现没有数据则recv函数阻塞等待,直到数据到来,接收数据recv函数返回
对于非阻塞的io:如果recv时有数据,则立即进行第二个步骤去接收完数据返回;发现没有数据则recv直接返回-1 报错,并返回一个错误码码WSAEWOULDBLOCK,表明不是真正的出错,而是当前操作不能完成,也就是说没有数据了。
总结: 在我们执行recv这个操作可能是需要耗时的(这个耗时主要指从没有数据到数据到来这段时间,接收数据使用的时间这里不考虑在内),在这段时间你希望程序在处理过程中等待吗?没有数据就阻塞在这里等待,不执行其它操作,这是阻塞(对于connect来说,就是我不想阻塞等待直到connect完成,对于accept就是我不想阻塞等待直到accept完成)。我不想让程序在这里等待,我继续处理别的操作,我过一会再来看看有没有数据到来,这就是非阻塞。

select poll

在阻塞和非阻塞中提到一句话:如果确定了有数据了,你再去recv,阻塞和非阻塞效果是一样的(很显然啊,我到了肯定有数据,肯定不用在等数据到来上花时间),那么如何确定有数据了呢?select poll就是干这个的,监听你的io描述符上是否有数据,有数据了就返回,还有一个好处是它们可以同时监听多个io描述符(内部轮询查看),
这就是io多路复用的概念吗?注意一点,select耗时与否在于你设置的超时时间, 与你的io描述符是否阻塞没有任何关系,不要混淆。
这里是select poll详细讲解 http://blog.csdn.net/rankun1/article/details/69691950

select确定有数据后,一次接收多少合适?

首先 不一定要while收,也就是说 不一定要循环收,因为select出已经有数据了 
所以调用recv时 肯定能收到数据,至于一次性收多少 根据你个人喜欢 
假如服务器发来100个字节,你要收取200,没关系,这次这能收到100个,recv返回值是100
相反,如果100的字节,你选择收取50个字节也没关系,下一次调用select时因为socket上还有数据,还是会显示该socket可读
你再继续收取。
select通知你一次后,如果你要循环recv,或者多次recv你要注意了,阻塞socket和非阻塞socket是有区别的,
因为select通知了你一次有数据之后,你多次recv的过程中,不知道哪次把数据收完了,就不确定下次有没有数据可以recv了,
对于 没有数据的情况当然阻塞和非阻塞有区别了,上面已经讲了。

有多少数据收多少数据

继续上面的话题,我可不可以提前知道当前socket可以recv多少,然后我就去recv多少呢?答案是可以的。
int ioctlsocket(
  _In_    SOCKET s,
  _In_    long   cmd,
  _Inout_ u_long *argp
);
可以通过这个函数,预先知道要收多少字节。不过不常用

同步和异步

我一开始对同步和异步的理解是这样的:在io操作上,有回调通知的是异步,没有回调通知需要等待的是同步,这样的理解太狭隘了,
这里的同步和异步是一个宽泛的概念,设置回调是异步的主流方式。(和多线程编程中的同步不是一个概念,不要混为一谈)。
(以下同步异步还没有理解,此处标记,后面回头再看看理解了再重新整理)
同步是代码可能阻塞在某个流程上
举例:
数据接收举例:
设置收数据和实际收数据在流程上是顺序的,这是同步
设置收数据和实际收数据是两个不同的地方,这是异步
connect举例:
connect一般是阻塞的,等待它返回结果,就是同步
设置socket非阻塞模式 然后connect会立即返回,不用管了,
后面用select检测socket是否可写 如果可写则连接上了,这是异步
不阻塞等于异步,

C++11 里面的std::async 和std::future都是设置异步的好方法
总之,使用同步还是异步根据自己的需求来,同步在流程上具有一致性,方便调试;异步的代码具有跨越性,流程上可能不好理解,不方便调试
20171209更新:
同步和异步在I/O模型中的概念和在并发模式中的概念是不同的:
在I/O模型中同步和异步体现在收发数据是由谁(应用层还是内核)来做的,或者说IO事件发生时是通知的就绪事件还是完成事件:
同步IO:io读写事件触发时,需要应用层自己去调用recv/send来收发数据,也就是说io事件发生时,是说明数据就绪了,可以去读写了
异步IO:io读写事件触发时,表明数据收发操作已经完成了,由内核来完成的,可以直接处理数据了
并发模式中的同步和异步体现在程序的执行顺序上:
同步指的是程序完全按照代码序列的顺序执行
异步指的是程序执行需要由系统事件来驱动,常见系统事件包括中断,信号等。

水平触发和边缘触发

level trigger和edge trigger,这是linux 2.6内核引入的概念,因为是随epoll来的,windows上没有这个概念
首先看一下select和epoll的区别 http://blog.csdn.net/rankun1/article/details/70227010
重复一下select和epoll区别:
1. select有句柄数目限制,windows平台就64个 ,而且是遍历这些socket,算法是O(n),集合越大效率越低, epoll的数量是系统最大句柄数目,这个只要系统内存够用,基本上认为很多很多
2. select是主动轮询,而epoll是操作系统通知你,如果没有的话 epoll就挂在那里,一个相当于线程空转,一个相当于线程休眠
再说lt和et模式
比如一个socket只要可读,lt模式就会触发
对于et模式,一个socket上从没有数据到有数据会触发一次,如果没收干净,下次不会再触发了
解释:
lt模式是只要fd上有信号就会触发,et是信号改变才会触发
这是电工学上的概念,高电平有信号,低电平无信号
lt是只要有信号就触发,et是在从低电平到高电平触发一次

所以et模式下一定要把socket数据收干净,不然后面再也不会触发数据可读了 (同理,如果你这个socket是服务端监听socket,那么et模式下你要在循环里accept处理完所有连接,要不然后面不会触发客户端连接了),也就是说放到一个循环里面去,这就是本质区别

网络io处理和数据逻辑处理(解协议等)要放在不同的线程里吗?

处理网络io的线程称为网络线程,一般是循环阻塞在epoll_wait上的,等epoll_wait触发后去接收数据或者接收客户端连接,
收到的数据可以放在单独的线程里去解包,也可以让空闲的网络线程去做。因为有的时候,大多数网络线程是空闲的。当然这要根据你的服务器应用场景
来决定你用哪种方式去解包。有可能你的数据很小,但是客户端很多,有可能你客户端不多,但是数据交互很频繁等情况。看看你是注重哪种业务逻辑。
这里说一下让空闲网络io线程去做解包工作的方法,核心是让网络io线程epoll_wait时指定一个本地socket,我们通过这个socket去唤醒网络线程的epoll_wait
伪代码:
详细说明 http://blog.csdn.net/analogous_love/article/details/53426777

定时器在服务器编程中的应用

主要有两种场景,一是定时发送心跳包。
另一个是流量控制,详细说一下流量控制:
举个例子,你去饭店吃饭,很多人都点了菜,饭店一般,给每个桌子都上点菜让大家都先吃上,而不是先给一个桌子上好其他人等着
同理,当一个服务器处理很多连接的时候,比如现在有三个连接A B C,一个工作线程,可能会这个工作线程一直在处理A的请求与应答,
那么B C 是不是要一直等着,这个时候如何做呢,每次处理A的数据请求是记下A的字节数,比如一次recv记下a1第二次a2 
a1 + a2 +... +an >= LIMIT
当A的数据已经超过LIMIT了,这个时候即使A有新数据我也不去收取了,腾下时间给B和C,B 和 C也一样
给A,B,C分别设置一个定时器Ta,Tb,Tc,比如1秒后将Ta将A的LIMIT限制取消,也就是将A流量清0,这样又能继续处理A了。
这就是流量控制的一个方法,好处是避免某些人饿死,和饭店上菜一个道理
所以 定时器 也是网络编程的一个重要方面

流量控制可以搞一个服务器优先处理策略
所有的定时器按优先级顺序排好
判断第一个定时器的时间到了没有,如果到了清空对应连接的流量,没到就不用判断了,接着判断第二个,以此类推
保存定时器可以用stl的优先队列,让它根据优先级来排序,
这个会自动排序,小的排在前面,大的排在后面
有些抢票软件的服务器就有优先处理策略,充了钱的VIP服务器优先处理你的请求

你可能感兴趣的:(Linux服务器编程)