Author : Kevin Lynx
从开始接触网络编程这个东西开始,我就不间断地阅读一些网络库(模块)的源代码,主要目的是为了获取别
人在这方面的经验,编程这东西,还是要多实践啊。
基本上,Etwork是一个很小巧的网络库。Etwork基于select模型,采用我之前说的技巧,理论上可以处理很
多连接(先不说效率)。
先看看下这个库的结构:
如同很多网络库一样,总会有一个类似于ISocketManager的类,用于管理所有网络连接(当用户服务器时)。
而ISocket则用于代表一个网络连接。在其他库中,ISocketManager对应的可能就是Server,而ISocket对应
的则是Session。
在接口设计上,尽管Etwork写了很多接口类(看看那些IClass),但是事实上它抽象得并不彻底。只是暴露给
客户端的代码很简洁,而库本身依然臃肿。不知道为什么,现在我比较喜欢纯C这种简洁的东西,对于OO以及
template,渐渐地有点心累。
在功能实现上,我以TCP服务器为例,CreateEtwork根据传来的参数建立服务器,在SocketManager::open中
是很常规的socket, bind, listen。当建立了服务器之后,需要在程序主循环里不断地轮询状态,这里主要
调用poll函数完成。
poll函数主体就是调用select。当select成功返回活动的套接字数量后,Etwork依次轮询读、写、错误fdset,
将保存的所有网络连接(就是那些ISocket对象)对应的套接字与fdset中当前的套接字做比较。大致逻辑为:
fd_count = select( 0, readset, writeset, exceptset, &timeout );
for( each fd in readset )
if( fd is listening fd )
accept new connection
else
for( each socket in all connections )
if( fd == socket )
can read data on this socket
for( each fd in writeset )
for( each fd in exceptset )
没什么特别让人注意的地方(别觉得别人垃圾,耐心读别人的代码不是什么坏事)。每一次,当Etwork检测到
新的连接时,会创建新的ISocket对象,并关联对应的套接字,然后保存此对象到一个列表中。当poll结束
后,客户端程序通常会调用accept函数(Etwork中提供的接口),该函数主要是将poll中保存的新的ISocket
对象全部拷贝出去。
在接收、发送网络数据上,Etwork如同几乎所有的网络库(模块)一样,采用了缓冲机制。这里所说的缓冲机
制是,网络模块接收到网络数据时,将数据保存起来,客户端程序想获取数据时,实际上就是从这个缓冲中
直接取,而不是从网络上获取;同理,发送数据时,客户端程序将数据提供给网络模块,网络模块将数据保
存起来,网络模块会在另一个时候发送这个缓冲中的数据(对于异步IO的处理毕竟不一样)。
Etwork关于这个缓冲机制的相关代码,主要集中在Buffer这个类。与Buffer相关的是一个Message机制。Buffer
维护了一个Message的队列(deque)。一个Message实际上是一个非常简单的结构体:
struct Message
{
unsigned short offset_;
unsigned short size_;
};
这其实是消息头,在消息头后全部是数据。在创建消息时(new_message),Etwork根据客户端提供的数据创建
足够大的缓存保存:
Message * m = (Message *)::operator new( size + sizeof( Message ) );
这其实是一个很危险的做法,但是从Etwokr的源码可以看出来,作者很喜欢玩弄这个技巧。与Buffer具体相
关的接口包括:get_data, put_data, get_message, put_message。Buffer内部维护的数据都是以Message
的形式组织。但是,对于外部而言,却依然是raw data,也就是诸如char*之类的数据。几个相关函数大致
上的操作为:获取指定尺寸的消息(可能包含多个消息),将一段数据加入Buffer并以消息的形式组织(可能会
创建多个消息),将一个消息以raw data的形式输出,将raw data以一个消息的形式加入到Buffer。
一般情况下,Etwork的poll操作,会将套接字上的数据接收并put_data到缓冲中;发送数据时则get_data。
客户端要从缓冲中获取数据时,就调用get_message;发送数据时就put_message。
Etwork中还有一个比较有趣的东西:marshaller。这个东西主要就是提供将C++中各种数据类型的变量进行字
节编码,也就是将int long struct之类的东西转换为unsigned char,从而方便直接往网络上发送。
基本上,Buffer和marshaller可以说是一个网络库(模块)的必要部件,你可以在不同的网络库中看到类似的
东西。
Etwork在网络事件的处理上,除了上面的轮询外,还支持回调机制。这主要是通过INotify,以及给各个ISocket
注册Notify对象实现。没什么难度,基本上就是observer模式的简单实现。
其他东西就没什么好说的了,纵观一下,Etwork实现得还是比较典型的,可以作为开发网络库的一个简单例子。