一、网络编程的开发平台
一般来说,目前开发者面对的主流开发平台就是Windows和类Unix,或者干脆就是Windows和Linux平台。至于如IBM或其它等公司的相关平台,可能对绝大多数开发者来说,可能一生都遇不到。至于嵌入式平台,除了专有的网络通信外,一般情况下,不会有太复杂的网络编程场景。基本都是借助网络进行通信,而且大多基本也都是客户端。
从上面的说明可以大概定义出,网络编程的平台,粗略的可以认为,针对绝大多数开发者,或者干脆就是Windows和Linux平台。
而在网络编程中,一般来讲,客户端编程是比较简单的。大多数情况下基本都是一对一的通信,即使有异步等复杂情况,处理起来也相对简单(不是绝对)。所以谓网络编程一谈起来复杂的原因,其实指的服务端。也就是说,在上述的两个平台上,如何进行高并发的服务端编程,这才是网络编程的复杂度和难度的体现。
二、网络服务端编程的特点
网络服务端编程的难点和复杂度在哪儿?有如下几点:
1、异步编程
即网络数据的接收与发送的及时处理和非同步话的底层机制(即指排除简单的网络编程外)
2、内存管理
网络IO与上层应用,上层应用与上层应用中大数据的交换时,内存的安全管理
3、多线程或者说线程池(协程)
高并发的网络天然的需要多线程加快数据的吞吐,同时CPU核数的增加也要求更好的将多线程程与并发(并行)有机的结合起来
4、非阻塞IO
非阻塞IO等同于你做你的,我做我的。有事儿打招呼,没事儿别找事儿。它的特点是不管有没有IO可操作(读写),都会立即返回,只是返回值不同。即通过返回值可以判断是否需要再次进行IO操作。所以,基本非阻塞IO都需要一个轮询的过程。
5、异步IO
这里需要明白的是异步IO与非阻塞IO的不同,虽然二者可能在某些情况下被表述人混淆为一谈,但其实是两回事。异步IO强调的是对IO的异步操作,即更倾向于上层的应用。这个上层指的是IO的上层,可能是内核级也可能是用户空间级。而非阻塞IO则指的IO操作本身,是否需要等待。异步IO其实是异步与IO操作的结合,当然开发者谈起的异步IO,大多都是指异步和非阻塞IO的结合。
6、上述的结合
如果觉得上面的可能都不是多难,那么就一起吧。解决高并发的网络服务时,把上述的技术融合到一起,如果还觉得得简单,那么你就成为了一个真正的大牛。
三、网络编程IO模型
所谓模型,可以理解为处理IO的标准流程或者说动作。而网络IO模型就是处理网络IO数据的流程。为了应对网络IO的处理,无论哪个平台,都可以抽象成几个大类的模型:
1、阻塞I/O (blocking I/O)
这个非常容易理解,程序发起IO请求,如果内核没有准备好,则阻塞,所谓阻塞就是等待,等待着内核准备好再把数据传递回来时,再进行动作。举个例子,朝老婆要钱买东西,老婆去拿钱,你就得一直在原地等待,什么你不等待走了。好吧。钱也没有了,当然东西也别买了。
2、非阻塞I/O (nonblocking I/O)
这个就有点意思,其实就是在用户发起IO请求后,内核返回一个值(error,这个值大多数情况下不是一个真正的错误)告诉应用程序我开始准备数据了,你先去搞别的吧。回头我把数据准备好你再来操作。所以上层应用会不断的通过轮询机制来查看数据是否准备成功。当然,也有可能已经准备好了,直接就把数据读过来了,不阻塞。这就比如向老婆要钱买东西,老婆说,我去拿钱,你先忙别的,回头我把钱放你书桌上。那你就可以去先洗碗,洗一个碗就去书桌看看有没有钱,没钱接着洗。直到发现有钱了,高高兴兴的出去买东西。
3、I/O多路复用(I/O multiplexing)
轮询肯定不是一种好的机制,所以就出现这种多路复用技术,即内核提供一种机制,允许上层应用监控多个IO,一旦某个IO有动作,就通知上层来发起相关的IO请求。
4、信号机制驱动I/O (signal driven I/O (SIGIO))
这种机制就比较容易理解了,在上层应用进行IO请求发起时,只是向内核发送信号通知相关操作,然后应用就可以做其它任务了,当内核当相关的IO数据准备好后,向上层应用发送一个信号,上层应用得到信号后就会继续进行IO操作,从而达到IO请求的一个整体流程。
5、异步I/O(asynchronous I/O (the POSIX aio_functions))
异步IO其实就上层应用在发起IO请求后,内核不会阻塞IO,会立刻返回一个error,通过其判断内核的准备状态,在内核未准备好的情况下,上层应用可以进行其它任务(准备好则继续进行IO请求,这种情况比较简单,不讨论。当然也可能是真正的错误,直接返回即可)。当内核把数据准备完成后,通过通知机制(一般是信号),通知上层应用继续操作IO。上层应用接到通知后,即再次发起IO请求即可得到相关数据。
这个有点类似于银行的叫号机制,大家拿了一个号,然后坐在一排椅子上等着窗口叫号,叫到的前去办理即可。
需要注意的是,对内核来说,IO操作是针对所有IO的,包括网口、串口等等。在网络编程中,一般的描述中,一般不会明显的去区分是网络IO还是其它IO,毕竟大家在这个环境中,应该知道指的就是网络IO。但是,在网络应用上支持的一些IO模型未必在其它模型上可用,所以如果严格的讨论一些具体的问题时,请注意表述的严谨性。
在此处还要谈一个老生常谈的问题,同步、异步与阻塞、非阻塞的关系。同步和异步一般是指多线(进)程间的协调一致或不一致;而阻塞一般是指IO的处理是否等待并交出时间片。二者间没有必然的联系。虽然在开发过程中二者联系非常紧密,但其实它们之间确实是没有必然的联系,完全可以不搭界。比如一个IO可以直接连接另外一个IO,根本不过上层。同样,线程和进程可以完全不访问IO。
如果二者有关系呢?一般来说,分为以下几种情况:
1、同步阻塞IO
这种一般用于非常简单的场景,比如只是一个基本的数据交换或者命令传递。没有并发量。这种情况下,整个线程会被阻塞,然后直到有IO响应。
2、同步非阻塞IO
这种一般用于处理多个IO,比如有三个网口,需要不断的查询一些命令数据之类的。所以不能在查询某一个网络IO时被阻塞住,谁也不知道另外两个是不是已经有数据传递过来了。但它需要不断的轮询导致消耗较多的CPU时间片。
3、异步阻塞IO
这个应用景比较少,一般异步和IO结合后,都是指异步非阻塞IO。异步阻塞IO其实划分到更细的程度后,其实和同步阻塞IO是一致的,只是逻辑上不同罢了。这种IO模型应用场景非常少。
4、异步非阻塞IO
这个就是常见的异步IO。一般来说,异步操作IO会拿到一个返回值,表示IO已经接收了处理动作。上层应用可以继续处理其它任务,当此IO动作处理完成后,内核会以一定的机制(信号、回调或者事件等)通知应用进程处理相关数据,从而避免了轮询的操作。
基本上目前高并发的各种网络框架、库及程序等都是基于这种情况进行开发的。
四、总结
网络服务端编程是复杂的,要先从宏观上把网络通信的相关机制搞清楚。这样才能更好的在开发过程中明白一些细节的掌控,而不至于迷失在细节当中。这个系列会从顶层一点点的抽丝剥茧的把相关背景和技术栈分析展开,并对它们的相对关系进行分析说明。某些具体的细节如果不必要就不展开了,请大家自行查阅相关书籍或者资料。