C++扫盲系列--第一个服务器程序


转自:http://blog.chinaunix.net/uid-22273897-id-393650.html

经过了近一个星期的反复纠结,今天要对于我的第一个服务器程序做一个小节。区区三百多行的程序,写了将近一个星期,整个程序不知重写了多少遍(应该在10遍左右)。作为第一个项目,它是失败的,下面是我的一些思考与总结。

关于需求

         进行程序开发,对于需求的把握是至关重要的。可以说,我之前没有任何开发服务器程序的经验,因此首先在对于需求的把握上出现了问题。

本程序的功能:

Linux环境下实现一个服务器程序,通过管道,从本地的客户端读取数据,然后进行解析、组包,之后发送POST给远程的服务器程序。最后,读取远程服务器发送回来的响应,并打印在屏幕上。

我的第一个程序就是仅仅考虑了上述的基本需求,然后编写了一个单线程同步程序实现。但是,这样的程序并不能够满足一个服务器程序的需求。

主要问题出现在如下方面:

1.       服务器程序代码要保证绝对的健壮,不能因为程序或数据的异常而退出。

2.       程序要保证尽量减少数据包的丢失。

3.       程序对于管道的监听应该采用阻塞的模式。

4.       对于管道传递过来的大量数据进行快速的处理。

5.       在保证整个服务器程序安全性的基础上,要尽量增大整个系统的吞吐量。

如果同时的去考虑上述的问题,那么原先的程序结构就要进行调整了。

管道通信

         我的程序当中采用了FIFO进行本地程序之间的通信。对于FIFO的应用,我们需要清楚如下事实:

1.       open()read()都可以阻塞程序的运行。到服务器端调用open函数时,如何此时没有客户端连接则程序会发生阻塞,直到第一个客户端程序与服务器程序建立管道连接为止。在建立连接之后,如果管道当中没有数据可读,则read函数会发生阻塞直到管道当中有数据可读为止。如果无客户端连接,read不会发生阻塞,并且其返回值为0

上面这一条要特别的注意,因为当read返回值为0的时候,我们就不能以read阻塞的方式来进行监听了,倘若程序当中不存在其它的阻塞方式,那么整个服务器程序就会陷入循环调用,他会严重的浪费处理器资源。具体情景如下:

server_fifo_fd = open(SERVER_FIFO_NAME,O_RDONLY);

while(1)

{

     read_res = read(server_fifo_fd, &my_data, sizeof(my_data));

cout << read_res << endl;

if(read_res > 0)

{

          ...

     }

}

一旦出现无客户端连接的状态,这个程序将无法进行进入“空转”状态,其表现为不断的打印0.这样,我们就会因为一个“什么也不做”的循环而白白浪费处理器资源。

 

那么,我们又应该如何解决上述问题呢?请看下面代码:

server_fifo_fd = open(SERVER_FIFO_NAME,O_RDONLY);

while(1)

{

     read_res = read(server_fifo_fd, &my_data, sizeof(my_data));

     cout << read_res << endl;

     if(read_res > 0)

     {

         ...

     }

     else

     {

         close(server_fifo_fd);

         server_fifo_fd = open(SERVER_FIFO_NAME,O_RDONLY);

     }

}

 

如何发现没有客户端连接在管道上,我们就关闭管道,然后再打开,通过open来再次让程序进入阻塞状态。

io_service

根据我现在的理解,io_service就是一个任务调度机。我们将任务交给io_service,他负责调度现有的系统资源,把这些任务消费掉。对于不同种类的任务,可以将它们分配给不同种类的调度机来分别执行,这样即便于管理,又有利于增加程序的吞吐量。

         在我的程序当中,大体存在两个独立的任务:

1.       从管道读取数据。

2.       与远程服务器之间的通信。

这样,可以建立两个调度机来管理这两个相对独立任务。为了运行这两个调度机,我们首先需要将它们分别绑定在两个线程上。这里还不得不提到一个问题:boost库所提供的io_service在没有任务执行的时候会自动的退出。而boost库当中标准的线程绑定方式如下:

     boost::thread *t_read = new boost::thread(boost::bind(

              &boost::asio::io_service::run, io_service_read));

此时,相当于开始运行了io_service。也就是说,如果在它之前,没有对io_service进行初始的绑定,那么程序就会自行的退出。再有就是如果在运行的过程当中,io_service处理完了其本身的所有任务,而服务器程序又不会新建一个调度机,那么该程序也将死掉。为了解决上述问题,我们需要对于io_service绑定一个资源消耗低而且会永远执行下去的程序。

boost::asio为我们提供的定时器可以满足上述的需求,我们可以创建一个循环定时器作为io_service的初始化任务。代码如下:

class io_clock

{

private:

     boost::asio::deadline_timer timer;

public:

     io_clock(boost::asio::io_service &io):

         timer(io, boost::posix_time::hours(24))

         {

              timer.async_wait(boost::bind(&io_clock::no_dead,this));

         }

     void no_dead()

     {

         timer.expires_at(timer.expires_at()+boost::posix_time::hours(24));

         timer.async_wait(boost::bind(&io_clock::no_dead, this));

     }

};

 

    这段代码是一段经典的定时器异步程序。关于异步程序的问题,过一会再讨论。

    下面继续讨论io_service。现在,我们已经知道什么是调度机了,并且计划在系统当中运用两个调度机,一个处理管道读取,另外一个运行远程通信。大致流程是,从管道读取数据,之后进行解析,将解析后的数据传入另外一个调度机当中实现数据包的组成、发送以及接收等操作。

io_service2

处理远程通信

io_service1

处理管道读取

    现在一个新的问题产生了:如何实现两个调度机之间的数据通信呢?

 

 

解析后的数据

 

 

 

 


    这里就涉及到io_service当中的post方法了。它可以实现将一个函数绑定到一个正在运行的io_service之上。这样,只要实现每当io_service1产生了一个数据就可以通过post的方式传递给io_service2来进行继续的执行。

请看下面这段代码:

         while(1)

         {

              //读取数据

              read_res = read(server_fifo_fd, &my_data, sizeof(my_data));

              cout << read_res << endl;

              if (read_res > 0)

              {

                   //对于读取后的数据进行解析

for (int i = 0; i < my_data.number; i++)

                   {

                       std::string Furl;


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值