zoukankan      html  css  js  c++  java
  • 网络开发库从libuv说到epoll

    引言

      这篇博文可能有点水,主要将自己libuv的学习过程和理解. 简单谈方法. 有点杂. 那我们开始吧.

    首先介绍 github . 这个工具特别好用. 代码托管. 如果不翻墙可能有点卡. 但是应该试试. 这里扯一点, github

    对代码水平提高 太重要了.还有一个解决疑难问题的论坛 stackoverflow  http://stackoverflow.com/.

    真的屌的不行.

      附赠

      github 简易教程, 不用谢   http://www.nowcoder.com/courses/2

      国内还有一个 逼格特别高的论坛, 哪天你nb了, 也可以上去装逼, 以其中一个帖子为例

      知乎epoll讨论   http://www.zhihu.com/question/21516827

    到这里关于 引言就结束了.

    前言

      现在我们开始说libuv, 这是个网络跨平台的库,是C库.比其它同类的网络库, 多了个高效编程.不需要考虑太多细节.

    是node.js的底层. 自己学习了一两周,发现, 功能挺强大的.通用性好. 但总觉得有点恶心.后面有时间说. 总的而言很优秀,很好,

    但不喜欢.

      下面我来分享怎么学习libuv 首先 你要去 官网下载libuv 代码.

         libuv github 源码   https://github.com/libuv/libuv  这时候你需要在你的linux上编译安装.

    参照步骤就是 readme.md

    这时候你肯定会出故障. 怎么做呢. 去 stackoverflow 上 找答案. google搜一下,都能解决. 我当时遇到一个问题是网关超时. 修改网关就可以了. 自己尝试,提高最快.

    安装折腾你半天. 那我们 测试一下. 按照 libuv 中文版最后一个demo 为例

    #include <stdio.h>
    #include <string.h>
    #include <uv.h>
    
    uv_tty_t g_tty;
    uv_timer_t g_tick;
    int g_width, g_height, g_pos;
    
    static void __update(uv_timer_t* req)
    {
        uv_write_t wreq;
        char data[64];
        const char* msg = "    Hello TTY    ";
        uv_buf_t buf;   
        buf.base = data;
        buf.len = sprintf(data, "33[2J33[H33[%dB33[%luC33[42;37m%s",
                                g_pos, (g_width - strlen(msg))/2, msg); 
        uv_write(&wreq, (uv_stream_t*)&g_tty, &buf, 1, NULL);
        
        if(++g_pos > g_height){
            uv_tty_reset_mode();
            uv_timer_stop(&g_tick);
        }   
    }
    
    // 主函数检测
    int main(void)
    {
        uv_loop_t* loop = uv_default_loop();
        
        uv_tty_init(loop, &g_tty, 1, 0); 
        uv_tty_set_mode(&g_tty, 0); 
        
        if(uv_tty_get_winsize(&g_tty, &g_width, &g_height)){
            puts("Could not get TTY information");
            uv_tty_reset_mode();
            return 1;
        }   
        
        printf("Width %d, height %d
    ", g_width, g_height);
        uv_timer_init(loop, &g_tick);
        uv_timer_start(&g_tick, __update, 200, 200);    
    
        return uv_run(loop, UV_RUN_DEFAULT);
    }

     测试的时候,运行会看见动画. 控制台动画

    gcc -g -Wall -o uvtty.c uvtty.c -luv

    运行截图是

    运行看出来Hello TTY 会一直向下移动知道移动到底了.

    好到这里,表示libuv 基本环境是好了,是可以开发了. 来上大头戏.国人有几个人翻译了一本 libuv 开发的书籍 ,

    地址

      libuv中文编程 拿走不谢    http://www.nowx.org/uvbook/

    这里再扯一点, 对于别人的劳动成果, 还是表示感谢.没有他们我们只能是干等着 闭门造车. 外国技术至少领先国内5年.

    你看上面书的时候需要对照下面代码看

      libuv中文编程 演示代码    https://github.com/nikhilm/uvbook/tree/master/code

    你至少需要看完那本书, 有问题翻libuv 源码, 对于书中的 demo code都需要敲一遍. 后面至少遇到libuv不在陌生.

    上面能练习code都敲了一遍,临摹并且优化修改了.

    到这里关于libuv 的学习思路基本就确定了. 就是 写代码.

    好了简单提一下对libuv的理解.

      1. libuv 最好的学习方法 看懂源码. ........

        (源码能看懂的似懂非懂,目前还是写不出来.)

      2.libuv 网络开发确实简单, 网络层 100-200行代码就可以了, 但是它提供了 例如线程池, 定时器揉在一起源码看起来就难一点了, 跨平台的终端控制.

      3.libuv 开发全局变量 和 隐含的包头技术 太泛滥不好.....

    总而言之C开发中没有一劳永逸的轮子. 否则就成为标准库了. 都有优缺点. 看自己应用领域. 喜欢看网络库的 强烈推荐libuv 比libevent和libuv要

    封装的好写. 好久没用也都忘记了. .......

      这里也快结束了. 最好的 还是 思想和 设计......

    正文

      到这里我想了一下,网络库看了有一些了, 但是还是封装不出来. 感觉基础还是不好. 说的太玄乎还是从基础开始吧. 这里就相当了epoll. 还是epoll做起吧.

    对于socket 基础开发, 请参照的我的 博文资料 http://www.cnblogs.com/life2refuel/p/5240175.html

    简单讲解socket开发 最后还举了个epoll的案例.

      对于epoll 其实就 4个函数 man epoll_create 在linux系统上查看就可以了. 对于它怎么入门. 搜索10篇比较不错的epoll博文,看完写完.基本上

    就会开发了.其它的就慢慢提升了. 这里我们 不细说epoll 是什么. 就举个简单例子帮助我和大家入门. epoll 本质就是操作系统轮询检测通知上层可以用了.

    第一个例子监测 stdin输入

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/epoll.h>
    
    #define _INT_BUF (255)
    
    // epoll helloworld 练习
    int main(void)
    {
        int epfd, nfds, i, len;
        char buf[_INT_BUF];
        struct epoll_event ev;
        
        epfd = epoll_create(1); //监听一个描述符与. stdin
        ev.data.fd = STDIN_FILENO;
        ev.events = EPOLLIN; //使用默认的LT条件触发
        epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
        
        // 10 表示等待 30s,过了直接退出
        for(;;){
            nfds = epoll_wait(epfd, &ev, 1, -1);
            for(i=0; i<nfds; ++i){
                if(ev.data.fd == STDIN_FILENO){
                    len = read(STDIN_FILENO, buf, sizeof buf - 1);
                    buf[len] = '';
                    printf("%s" ,buf);
                }
            }
    
            //强加一个结束条件吧
            if(random() % 100 >= 90)
                break;
        }
        
        puts("Epoll Hello world is end!");
        // 只要是文件描述符都要释放
        close(epfd);
        return 0;
    }
    // 编译
    gcc -g -Wall -o epoll_stdin.out epoll_stdin.c

    运行结果是

     当用户输入的时候,再读取输出一次.

    这里再扯一点,关于 我们使用的 类vi 配置

     在根目录, touch .vimrc写入下面信息

    "设定默认解码
    set fenc=utf-8
    "设置默认字符集
    set fencs=utf-8,usc-bom,euc-jp,gb18030,gbk,gb2312,cp936 
    " 用于关闭VI的兼容模式, 采用纯VIM, vi还是比较难搞
    set nocompatible
    "显示行号
    set number
    "vim使用自动对齐,也就是把当前行的对齐格式应用到下一行
    set autoindent
    "依据上面的对齐格式,智能的选择对齐方式
    set smartindent
    "设置tab键为4个空格
    set tabstop=4
    "设置当行之间交错时使用4个空格
    set shiftwidth=4
    "设置在编辑过程中,于右下角显示光标位置的状态行
    set ruler
    "设置增量搜索,这样的查询比较smart 
    set incsearch 
    "高亮显示匹配的括号
    set showmatch
    "匹配括号高亮时间(单位为 1/10 s) set ignorecase  "在搜索的时候忽略大小写
    set matchtime=1
    "高亮语法
    syntax on

    还是比较好用的配合.

    最后我们举一个简单的 epoll + pthread 案例, 有时候觉得 从底层做起, 一辈子就是水比. 太难搞了.上代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/resource.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    
    //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
    #define CERR(fmt, ...) 
        fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "
    ",
                 __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)
    
    //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
    #define CERR_EXIT(fmt,...) 
            CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
    //4.2 检查一行代码,测试结果
    #define IF_CHECK(code) 
        if((code) < 0) 
            CERR_EXIT(#code)
    
    // 监听队列要比监听文件描述符epoll少一倍
    #define _INT_EPL (8192)
    #define _INT_BUF (1024)
    #define _INT_PORT (8088)
    #define _STR_IP "127.0.0.1"
    // 待发送的数据
    #define _STR_MSG "HTTP/1.0 200 OK
    Content-type: text/plain
    I am here, heoo...
    
    "
    
    // 线程执行的函数
    void* message(void* arg);
    // 设置文件描述符为非阻塞的, 设置成功返回0
    extern inline int setnonblocking(int fd);
    // 开启服务器监听
    int openserver(const char* ip, unsigned short port);
    
    // 主逻辑,开启线程和epoll 监听
    int main(int argc, char* argv[])
    {
        int nfds, i, cfd;
        struct sockaddr_in caddr;
        socklen_t clen = sizeof caddr;
        pthread_t tid;
        struct epoll_event ev, evs[_INT_EPL];
        int sfd = openserver(_STR_IP, _INT_PORT);    
        int efd = epoll_create(_INT_EPL);
        if(efd < 0) {
            close(sfd);
            CERR_EXIT("epoll_create %d is error!", _INT_EPL);
        }
    
        ev.events = EPOLLIN | EPOLLET;
        ev.data.fd = sfd;
        if(epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &ev) < 0){
            close(efd);
            close(sfd);
            CERR_EXIT("epoll_ctl is error!");
        }
        
        // 这里开始等待操作系统通知文件描述符是否可以了
    __startloop:
        if((nfds = epoll_wait(efd, evs, _INT_EPL, -1)) <= 0){
            if(nfds == 0 || errno == EINTR)
                goto __startloop;
            // 这里出现错误,直接返回
            CERR("epoll_wait is error nfds = %d.", nfds);
            goto __endloop;
        }
        // 这里是事件正确
        for(i=0; i<nfds; ++i) {
            if(evs[i].data.fd == sfd) { // 新连接过来
                // clen做输入和输出参数
                cfd = accept(sfd, (struct sockaddr*)&caddr, &clen);        
                if(cfd < 0) {
                    CERR("accept is error sfd = %d.", sfd);
                    goto __startloop; //继续其它服务
                }
                CERR("[%s:%d] happy connected here.", inet_ntoa(caddr.sin_addr), htons(caddr.sin_port));
                // 这里开始注册新的文件描述符过来
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = cfd;
                setnonblocking(cfd);
                if(epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &ev) < 0) {
                    CERR("epoll_ctl add cfd : %d error.", cfd);
                    // 这里存在一个cfd没有释放问题, 指望 exit之后帮我们释放吧
                    goto __endloop;    
                }
            }    
            else { // 这里是处理数据可读可写
                // 速度太快,也存在数据异常问题
                if(pthread_create(&tid, NULL, message, &evs[i].data.fd) < 0) {
                    CERR("pthread_create is error!");                
                    goto __endloop;
                }
            }
        }
    
        goto __startloop;
    __endloop:
    
        CERR("epoll server is error, to exit...");
        close(efd);
        close(sfd);
        return 0;
    }
    
    // 线程执行的函数
    void* 
    message(void* arg)
    {
        char buf[_INT_BUF];
        int cfd = *(int*)arg, rt; //得到文件描述符
        
        // 设置线程分离属性,自己回收
        pthread_detach(pthread_self());
    
        // 数据循环读取, 非阻塞随便搞
        for(;;) {
            rt = read(cfd, buf, _INT_BUF - 1);
            if(rt < 0){
                if(errno == EINTR) //信号中断继续
                    continue;
                // 由于非阻塞模式,当缓冲区已无数据可以读写的时候,触发EAGAIN信号
                if(errno == EAGAIN){
                    rt = 1; //标志客户端连接没有断
                    break;
                }
                // 下面就是错误现象
                CERR("read cfd = %d, is rt = %d.", cfd, rt);
                break;
            }
            // 需要继续读取客户端数据
            if(rt == _INT_BUF - 1) 
                continue;
    
            // 下面表示客户端已经关闭
            CERR("read end cfd = %d.", cfd);
            break;
        }
    
        // 给客户端 发送数据
        if( rt > 0 ) {
            // 给客户端发送信息, 多个''吧
            write(cfd, _STR_MSG, strlen(_STR_MSG) + 1);
        }    
    
        // 这里是处理完业务,关闭和服务器连接
        close(cfd);
        return NULL;
    }
    
    // 设置文件描述符为非阻塞的, 设置成功返回0
    inline int 
    setnonblocking(int fd)
    {
        int zfd = fcntl(fd, F_GETFD, 0);
        if(fcntl(fd, F_SETFL, zfd | O_NONBLOCK) < 0){
            CERR("fcntl F_SETFL fd:%d, zfd:%d.", fd, zfd);    
            return -1;
        }
        return 0;    
    }
    
    // 开启服务器监听
    int 
    openserver(const char* ip, unsigned short port)
    {
        int sfd, opt = SO_REUSEADDR;
        struct sockaddr_in saddr = { AF_INET };
        struct rlimit rt = { _INT_EPL, _INT_EPL };
        
        //设置每个进程打开的最大文件数    
        IF_CHECK(setrlimit(RLIMIT_NOFILE, &rt));
        // 开启socket 监听
        IF_CHECK(sfd = socket(PF_INET, SOCK_STREAM, 0));
        //设置端口复用, opt 可以简写为1,只要不为0
        IF_CHECK(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt));
        // 设置bind绑定端口
        saddr.sin_addr.s_addr = inet_addr(ip);
        saddr.sin_port = htons(port);
        IF_CHECK(bind(sfd, (struct sockaddr*)&saddr, sizeof saddr));
        //开始监听
        IF_CHECK(listen(sfd, _INT_EPL >> 1));
        
        // 这时候服务就启动起来并且监听了
        return sfd;
    }

    这个服务器监测客户端连接发送报文给客户端

    编译的时候需要加上 -lpthread

    运行结果如下

    客户端

    上面的关于epoll案例,有机会一定要自己学学. 都挺耗时间的. 但是 不学也不见有什么更有意思的事. 到这里有机会继续分享那些开发中用到的基础

    模型.网络开发确实不好搞, 细节太多, 但也容易都是套路...到这里说再见了,希望本文提供一些关于libuv的学习方法和epoll基础案例能够让你至少听过

    ,有了装逼的方向.

    后记

      错误是难免的,有问题再交流....

  • 相关阅读:
    【C语言】判断学生成绩等级
    如何强制卸载软件,强制卸载的工具。
    网站添加左下角某易云音乐播放器代码
    ASCII码表
    C语言当中int,float,double,char这四个有什么区别?
    未来HTML5的发展前景如何?黑客专家是这样回答的
    2018年需要关注5个与黑客安全相关趋势
    2025年的技术:为第四次工业革命做准备
    量子计算可以给企业竞争带来的七种优势
    目前投资区块链三大风险
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5297620.html
Copyright © 2011-2022 走看看