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