TCP网络编程模型

  TCP网络编程有三个例子最值得学习研究,分别是echo、chat、proxy。

  echo的作用:熟悉服务端被动接受新连接、收发数据、被动处理连接断开。每个连接是独立服务的,连接之间没有关联。在消息内容方面echo有一些变种:比如做成一问一答得方式,收到的请求和发送响应的内容不一样,这时候要考虑打包与拆包格式的设计,进一步还可以写成简单的HTTP服务。

  chat的作用:连接之间的数据有交流,从a收到的数据要发给b。这样对连接管理提出了更高的要求:如何用一个程序同时处理多个连接?fork()-per-connection似乎是不行的。如何防止串话?b有可能随时断开连接,而新建立的连接c可能恰好复用了b的文件描述符,那么a会不会错误地把消息发给c?

  proxy的作用:连接的管理更加复杂:既要被动接受连接,也要主动发起连接;既要主动关闭连接,也要被动关闭连接。还要考虑两边速度不匹配。

  这三个例子功能简单,突出了TCP网络编程中的重点问题,挨着做一遍基本就能到基础层次。chat例子如腾讯的qq,今天发现一个很好的范强软件lantern,就是proxy的应用。另外推荐两个学习socketAPI的神器:IPython、netcat。IDE用CLion,不要太好用。

 

  依个人水平,目前把Linux服务端编程模型分为三个层次:

  第一层次——简单的多进程、多线程、线程池。缺点:一个线程阻塞一个IO操作,耗资源

  第二层次——多路IO复用。特点:阻塞IO,把accept()、read()等的阻塞放在select()/poll()/epoll_wait()上

  第三层次——reactor proactor模型。优势:高性能、高并发

注:服务器程序一般不会频繁地启动和终止线程。甚至create_thread只在程序启动时调用,在服务运行期间是不调用的。

参考:https://www.zhihu.com/question/21516827

http://blog.csdn.net/caiwenfeng_for_23/article/details/8458299

 

 一、TCP服务端多线程模型:

void *worker(void *arg)
{
    int client = *((int *)arg);
    while (read(client) > 0)
    {
        write(client);
    }
    close(client);
}

int main()
{
    socket();
    bind();
    listen();
    while (1)
    {
        int client = accept();
        if (client > 0)
        {
            pthread_t pt;
            pthread_create(&pt, NULL, worker, &client);  //one thread per connect. bad model!
        }
    }
}

 

二、TCP服务端epoll模型:

int main()
{
    int listener = socket();
    bind();
    listen();
    
    int epfd = epoll_create();
    epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev);
    while (1)
    {
        int n = epoll_wait(epfd, events, MAX_EVENT, -1);
        for (int i=0; i<n; ++i)
        {
            if (events[i].data.fd == listener)
            {
                int client = accept();
                setnonblocking(client);
                epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev);
            }
            else
            {
                handle();
                if (done())
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, client, &ev);
                    close(client);
                }
            }
        }
    }
}

epoll的两种触发方式:水平触发LT、边缘触发ET。

Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!

阻塞IO:当你去读一个阻塞的文件描述符时,如果在该文件描述符上没有数据可读,那么它会一直阻塞(通俗一点就是一直卡在调用函数那里),直到有数据可读。当你去写一个阻塞的文件描述符时,如果在该文件描述符上没有空间(通常是缓冲区)可写,那么它会一直阻塞,直到有空间可写。以上的读和写我们统一指在某个文件描述符进行的操作,不单单指真正的读数据,写数据,还包括接收连接accept(),发起连接connect()等操作...

非阻塞IO:当你去读写一个非阻塞的文件描述符时,不管可不可以读写,它都会立即返回,返回成功说明读写操作完成了,返回失败会设置相应errno状态码,根据这个errno可以进一步执行其他处理。它不会像阻塞IO那样,卡在那里不动!!!                    

 select(),poll()模型都是水平触发模式,信号驱动IO是边缘触发模式,epoll()模型即支持水平触发,也支持边缘触发,默认是水平触发。

  

posted on 2017-04-08 12:45  xuelei56  阅读(1013)  评论(0编辑  收藏  举报

导航