之前写过一个socket的服务程序,只使用了accept进行处理,缺点是程序是阻塞的。
总结当时的思路是:
用listen监听请求,循环accept得到客户端句柄。为了实现多客户端共享该服务,我开辟了一个用于保存客户端句柄的数组,并为每个请求建立一个线程去处理。值得注意的是需要考虑多线程的同步与互斥。主线程A用于填充数组,由主线程开启的线程B负责循环读取数组,当数组有元素时会为客户端句柄创建处理线程C(x),它们之间的同步互斥关系可参加上篇文章。
这样做的好处是服务可以并发处理多个请求了,但是程序仍然是阻塞的,为了避免阻塞我不得不另外开启了线程B和C。但是linux中select函数,完全能让我的主线程避开完全阻塞,即使一段时间没有请求,主线程也能避开等待,而处理其它事情。这是非常重要的,线程的空等一定带来资源的浪费,最大化地让利用资源才能更好地提高程序执行效率。
因此,我又重新设计我的程序,思路如下:
(1)服务分两块,一块是处理client请求,另一块是处理非client请求(如外界的RPC请求),这里只说明client请求;
(2)暂时不考虑多client请求的情况,先跑通一个client再说。
(3)服务有三个重要的全局变量:一个fd_set, 一个存放句柄相关信息的watch table, 一个pending list。
主线程会开启一个loop线程,去感知fd_set,若有可读写的fd存在,则从watch_table中查出该fd相关的详细信息,最后放入到pending list中等待处理。
主线程或其它线程会组合请求句柄相关信息到watch table中,并将fd加入fd_set。
(4)这样
当客户端调用connect时,服务端的server_socket_fd是可读的,会触发一个处理函数,在处理函数中自然会得到客户端句柄client_fd,然后又可以封装该fd相关的信息,放入到watch table和fd_set中。
当客户端调用send时,服务端的client_fd是可读的,触发一个处理函数,这样就能得到客户端发来的请求信息了。
设计仍然存在许多改进提升之处:
(1)客户端与服务端没有协议的设计;
(2)不支持多客户端
(3)服务只能处理socket类型的请求。服务也有可能作为客户端请求其它服务,这时服务得到的响应也应该会有处理。
对此,我先考虑了不足(1)的设计,如下:
数据准备采用这种形式(ID, size, data)
ID表示请求标识;
size标识请求数据的长度,如几个int, 几个char等;
data是具体网络字节数据。
解析时,先解出ID,然后在列表中查找出该ID带有分发函数dispatchXXX,这里讲分发函数分成以下几种:
dispatchInt() ---- 代表处理的请求data是int类型的
dispathChar() ---- 代表处理的请求data是char类型的
dispathString() ---- 代表处理的请求data是string类型的
dispathVoid() ---- 代表处理的请求data是NULL的
……
在dispatchXXX中,解析出size和data,根据具体类型还原出具体的数据。