Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1292514
  • 博文数量: 79
  • 博客积分: 1959
  • 博客等级: 上尉
  • 技术积分: 2719
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-19 12:07
个人简介

樽中酒不空

文章分类

全部博文(79)

文章存档

2024年(3)

2020年(4)

2019年(1)

2017年(2)

2016年(2)

2015年(7)

2014年(11)

2013年(13)

2012年(18)

2011年(2)

2010年(16)

分类: C/C++

2012-02-03 10:55:01

libUV是一个网络接口,在Linux下集成了libev,在Windows下按linux风格封装了一下IOCP,不过使用起来方便多了。
用来写TCP Server,极大地简化了代码,比直接用libev还要简单,详细例子可以见test目录下的例子,以其中blackhole-server为例,稍修改一下使之成为独立程序:

#include "uv.h"
#include

#define ASSERT(expr)                                      \
 do {                                                     \
  if (!(expr)) {                                          \
    fprintf(stderr,                                       \
            "Assertion failed in %s on line %d: %s\n",    \
            __FILE__,                                     \
            __LINE__,                                     \
            #expr);                                       \
    abort();                                              \
  }                                                       \
 } while (0)

#define container_of(ptr, type, member) \
  ((type *) ((char *) (ptr) - offsetof(type, member)))


typedef struct {
  uv_tcp_t handle;
  uv_shutdown_t shutdown_req;
} conn_rec;
static uv_tcp_t tcp_server;

static void connection_cb(uv_stream_t* stream, int status);
static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size);
static void read_cb(uv_stream_t* stream, ssize_t nread, uv_buf_t buf);
static void shutdown_cb(uv_shutdown_t* req, int status);
static void close_cb(uv_handle_t* handle);


static void connection_cb(uv_stream_t* stream, int status) {
  conn_rec* conn;
  int r;

  ASSERT(status == 0);
  ASSERT(stream == (uv_stream_t*)&tcp_server);

  conn = (conn_rec*)malloc(sizeof *conn);
  ASSERT(conn != NULL);

  r = uv_tcp_init(stream->loop, &conn->handle);
  ASSERT(r == 0);

  r = uv_accept(stream, (uv_stream_t*)&conn->handle);
  ASSERT(r == 0);

  r = uv_read_start((uv_stream_t*)&conn->handle, alloc_cb, read_cb);
  ASSERT(r == 0);
}


static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) {
  static char buf[65536];
  return uv_buf_init(buf, sizeof buf);
}


static void read_cb(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) {
  conn_rec* conn;
  int r;

  if (nread >= 0)
    return;

  ASSERT(uv_last_error(stream->loop).code == UV_EOF);

  conn = container_of(stream, conn_rec, handle);

  r = uv_shutdown(&conn->shutdown_req, stream, shutdown_cb);
  ASSERT(r == 0);
}


static void shutdown_cb(uv_shutdown_t* req, int status) {
  conn_rec* conn = container_of(req, conn_rec, shutdown_req);
  uv_close((uv_handle_t*)&conn->handle, close_cb);
}


static void close_cb(uv_handle_t* handle) {
  conn_rec* conn = container_of(handle, conn_rec, handle);
  free(conn);
}

int main()
{
  struct sockaddr_in addr;
  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();
  addr = uv_ip4_addr("127.0.0.1", 1234);

  r = uv_tcp_init(loop, &tcp_server);
  ASSERT(r == 0);

  r = uv_tcp_bind(&tcp_server, addr);
  ASSERT(r == 0);

  r = uv_listen((uv_stream_t*)&tcp_server, 128, connection_cb);
  ASSERT(r == 0);

  r = uv_run(loop);
  ASSERT(0 && "Blackhole server dropped out of event loop.");

  return 0;
}

简单说明:
当客户端有连接时,响应connection_cb回调,在这里进行accept操作。
然后uv_read_start是设置读事件回调read_cb。
socket有读事件时,触发read_cb,在里面读数据。

当关掉socket时,先uv_shutdown,设置关闭的回调shutdown_cb,在这里再设置close回调close_cb。这样是完成一个动作后再去下一个动作,避免了while(1) 去检查socket状态。
代码里可能稍难理解的是container_of,不过如果接触过list_entry就很容易理解了。在这里充分看出指针的强大作用,基本相当于陆小凤的灵犀指,李探花的飞刀,替没有小李飞刀的大众化编程默哀两分钟,再替c/c++鼓鼓掌。

这个demo里没有写怎么发,从其他例子中copy一个:

 for (i = 0; i < write_sockets; i++) {
      do_write(type == TCP ? (uv_stream_t*)&tcp_write_handles[i] : (uv_stream_t*)&pipe_write_handles[i]);
    }
选个合适的位置,比如定时器,向所有连接来的client socket发点消息,发送方法如下:
 static void do_write(uv_stream_t* stream) {
  uv_write_t* req;
  uv_buf_t buf;
  int r;

  buf.base = (char*) &write_buffer;
  buf.len = sizeof write_buffer;

  while (stream->write_queue_size == 0) {
    req = (uv_write_t*) req_alloc();
    r = uv_write(req, stream, &buf, 1, write_cb);
    ASSERT(r == 0);
  }
}

static void write_cb(uv_write_t* req, int status) {
  ASSERT(status == 0);

  req_free((uv_req_t*) req);

  nsent += sizeof write_buffer;
  nsent_total += sizeof write_buffer;

  do_write((uv_stream_t*) req->handle);
}

老规矩,还是回调方式,发送函数调用uv_write,libUV帮你发,然后触发回调write_cb,看一下,如果还有数据没发完,继续do_write。传统的方法伪代码一般是:
while(1)
{
 leng = send();
 sendlen += leng;
 if (sendlen == total)
{
break;
}
}


看的出来,libUV使用起来的确很方便。开发tcp 的朋友可以大大松一口气,连 libev,libevent都可以替换掉了。至于boost.asio和ace,如果只是单线做网络模块,自己写线程池,内存池,应该没必要去考虑了。再说内存池,线程池也不麻烦,比如 char * p是事先分配好的一块内存,在上面再给obj分配一下:
obj* pobj = new(p)  obj;
只是注意一下,释放的时候需要显示调用obj的析构。
线程池呢
for (i = 0; i < nthreads; i++) {
        create_worker(worker_libevent, &threads[i]);
    }
然后协调好就行了。
不要以为线程池内存池是普通人无法实现的技术,认真研究一下,都能掌握的不错,然后在工作中不断完善优化。没必要神化ace和boost,当然其代码写的很好,可以学习,但不是离开就活不了的。


阅读(16046) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~