引言
這篇博文可能有點水,主要將自己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基礎案例能夠讓你至少聽過
,有了裝逼的方向.
後記
錯誤是難免的,有問題再交流....