程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 網絡開發庫從libuv說到epoll,開發libuv說到epoll

網絡開發庫從libuv說到epoll,開發libuv說到epoll

編輯:關於C語言

網絡開發庫從libuv說到epoll,開發libuv說到epoll


引言

  這篇博文可能有點水,主要將自己libuv的學習過程和理解. 簡單談方法. 有點雜. 那我們開始吧.

首先介紹 githup . 這個工具特別好用. 代碼托管. 如果不FQ可能有點卡. 但是應該試試. 這裡扯一點, githup

對代碼水平提高 太重要了.還有一個解決疑難問題的論壇 stackoverflow  http://stackoverflow.com/.

真的屌的不行.

  附贈

  githup 簡易教程, 不用謝   http://www.nowcoder.com/courses/2

  國內還有一個 逼格特別高的論壇, 哪天你nb了, 也可以上去裝逼, 以其中一個帖子為例

  知乎epoll討論   http://www.zhihu.com/question/21516827

到這裡關於 引言就結束了.

 

前言

  現在我們開始說libuv, 這是個網絡跨平台的庫,是C庫.比其它同類的網絡庫, 多了個高效編程.不需要考慮太多細節.

是node.js的底層. 自己學習了一兩周,發現, 功能挺強大的.通用性好. 但總覺得有點惡心.後面有時間說. 總的而言很優秀,很好,

但不喜歡.

  下面我來分享怎麼學習libuv 首先 你要去 官網下載libuv 代碼.

     libuv githup 源碼   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, "\033[2J\033[H\033[%dB\033[%luC\033[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\n", 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] = '\0';
                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 "\r\n",\
             __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\r\nContent-type: text/plain\r\nI am here, heoo...\r\n\r\n"

// 線程執行的函數
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 ) {
        // 給客戶端發送信息, 多個'\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基礎案例能夠讓你至少聽過

,有了裝逼的方向.

 

後記

  錯誤是難免的,有問題再交流....

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved