您的位置:首页 > 理论基础 > 计算机网络

网络开发库从libuv说到epoll

2016-03-20 14:28 381 查看
引言

  这篇博文可能有点水,主要将自己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 基础开发, 请参照的我的 博文资料 /article/5735847.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基础案例能够让你至少听过

,有了装逼的方向.

后记

  错误是难免的,有问题再交流....
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: