C高级 服务器内核分析和构建 (一)
引言
最经看cloud wind 的 skynet服务器设计. 觉得特别精妙. 想来个专题先剖析其通信层服务器内核
的设计原理. 最后再优化.本文是这个小专题的第一部分, 重点会讲解对于不同平台通信基础的接口封装.
linux是epoll, unix是 kqueue. 没有封装window上的iocp模型(了解过,没实际用过).
可能需要以下关于 linux epoll 基础. 请按个参照.
1. Epoll在LT和ET模式下的读写方式 http://www.ccvita.com/515.html
上面文字写的很好, 读的很受用. 代码外表很漂亮. 但是不对. 主要是 buf越界没考虑, errno == EINTR要继续读写等没处理.
可以适合初学观摩.
2. epoll 详解 http://blog.csdn.net/xiajun07061225/article/details/9250579
总结的很详细, 适合面试. 可以看看. 这个是csdn上的. 扯一点
最近在csdn上给一个大牛留言让其来博客园, 结果被csdn禁言发评论了. 感觉无辜. 内心很受伤, csdn太武断了.
3. epoll 中 EWOULDBLOCK = EAGAIN http://www.cnblogs.com/lovevivi/archive/2013/06/29/3162141.html
这个两个信号意义和区别.让其明白epoll的一些注意点.
4. epoll LT模式的例子 http://bbs.chinaunix.net/thread-1795307-1-1.html
网上都是ET模式, 其实LT不一定就比ET效率低,看使用方式和数量级.上面是个不错的LT例子.
到这里基本epoll就会使用了. epoll 还是挺容易的. 复杂在于 每个平台都有一套基础核心通信接口封装.统一封装还是麻烦的.
现在到重头戏了. ※skynet※ 主要看下面文件
再具体点可以看 一个cloud wind分离的 githup 项目
cloudwu/socket-server https://github.com/cloudwu/socket-server
引言基本都讲完了.
这里再扯一点, 对于服务器编程,个人认识. 开发基本断层了. NB的框架很成熟不需要再疯狂造轮子. 最主要的是 难,见效慢, 风险大, 待遇低.
前言
我们先看cloud wind的代码. 先分析一下其中一部分.
红线标注的是本文要分析优化的文件. 那开始吧.
Makefile
socket-server : socket_server.c test.c
gcc -g -Wall -o $@ $^ -lpthread clean:
rm socket-server
很基础很实在生成编译. 没的说.
socket_poll.h
#ifndef socket_poll_h
#define socket_poll_h #include <stdbool.h> typedef int poll_fd; struct event {
void * s;
bool read;
bool write;
}; static bool sp_invalid(poll_fd fd);
static poll_fd sp_create();
static void sp_release(poll_fd fd);
static int sp_add(poll_fd fd, int sock, void *ud);
static void sp_del(poll_fd fd, int sock);
static void sp_write(poll_fd, int sock, void *ud, bool enable);
static int sp_wait(poll_fd, struct event *e, int max);
static void sp_nonblocking(int sock); #ifdef __linux__
#include "socket_epoll.h"
#endif #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
#include "socket_kqueue.h"
#endif #endif
一眼看到这个头文件, 深深的为这个设计感到佩服. 这个跨平台设计的思路真巧妙. 设计统一的访问接口. 对于不同平台
采用不同设计. 非常的出彩. 这里说一下. 可能在 云风眼里, 跨平台就是linux 和 ios 能跑就可以了. window 是什么. 是M$吗.
这是玩笑话, 其实 window iocp是内核读取好了通知上层. epoll和kqueue是通知上层可以读了. 机制还是很大不一样.
老虎和秃鹫很难配对.window 网络编程自己很不好,目前封装不出来. 等有机会真的需要再window上设计再来个. (服务器linux和unix最强).
那我们开始吐槽云风的代码吧.
1). 代码太随意,约束不强
static void sp_del(poll_fd fd, int sock);
static void sp_write(poll_fd, int sock, void *ud, bool enable);
上面明显 第二个函数 少了 参数 ,应该也是 poll_fd fd.
2). 过于追求个人美感, 忽略了编译速度
#ifdef __linux__
#include "socket_epoll.h"
#endif #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
#include "socket_kqueue.h"
#endif
这个二者是 if else 的关系. 双if不会出错就是编译的时候多做一次if判断. c系列的语言本身编译就慢. 要注意
设计没的说. 好,真好. 多一份难受,少一份不完整.
socket_epoll.h
#ifndef poll_socket_epoll_h
#define poll_socket_epoll_h #include <netdb.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h> static bool
sp_invalid(int efd) {
return efd == -;
} static int
sp_create() {
return epoll_create();
} static void
sp_release(int efd) {
close(efd);
} static int
sp_add(int efd, int sock, void *ud) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = ud;
if (epoll_ctl(efd, EPOLL_CTL_ADD, sock, &ev) == -) {
return ;
}
return ;
} static void
sp_del(int efd, int sock) {
epoll_ctl(efd, EPOLL_CTL_DEL, sock , NULL);
} static void
sp_write(int efd, int sock, void *ud, bool enable) {
struct epoll_event ev;
ev.events = EPOLLIN | (enable ? EPOLLOUT : );
ev.data.ptr = ud;
epoll_ctl(efd, EPOLL_CTL_MOD, sock, &ev);
} static int
sp_wait(int efd, struct event *e, int max) {
struct epoll_event ev[max];
int n = epoll_wait(efd , ev, max, -);
int i;
for (i=;i<n;i++) {
e[i].s = ev[i].data.ptr;
unsigned flag = ev[i].events;
e[i].write = (flag & EPOLLOUT) != ;
e[i].read = (flag & EPOLLIN) != ;
} return n;
} static void
sp_nonblocking(int fd) {
int flag = fcntl(fd, F_GETFL, );
if ( - == flag ) {
return;
} fcntl(fd, F_SETFL, flag | O_NONBLOCK);
} #endif
这个代码没有什么问题, 除非鸡蛋里挑骨头. 就是前面接口层 socket_poll.h 中已经定义了变量名,就不要再换了.
fd -> efd. 例如最后一个将 sock 换成fd 不好.
static void
sp_nonblocking(int fd) {
可能都是大神手写的. 心随意动, ~~无所谓~~.
我后面会在正文部分开始全面优化. 保证有些变化. 毕竟他的代码都是临摹两遍之后才敢说话的.
socket_kqueue.h
#ifndef poll_socket_kqueue_h
#define poll_socket_kqueue_h #include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> static bool
sp_invalid(int kfd) {
return kfd == -;
} static int
sp_create() {
return kqueue();
} static void
sp_release(int kfd) {
close(kfd);
} static void
sp_del(int kfd, int sock) {
struct kevent ke;
EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, , , NULL);
kevent(kfd, &ke, , NULL, , NULL);
EV_SET(&ke, sock, EVFILT_WRITE, EV_DELETE, , , NULL);
kevent(kfd, &ke, , NULL, , NULL);
} static int
sp_add(int kfd, int sock, void *ud) {
struct kevent ke;
EV_SET(&ke, sock, EVFILT_READ, EV_ADD, , , ud);
if (kevent(kfd, &ke, , NULL, , NULL) == -) {
return ;
}
EV_SET(&ke, sock, EVFILT_WRITE, EV_ADD, , , ud);
if (kevent(kfd, &ke, , NULL, , NULL) == -) {
EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, , , NULL);
kevent(kfd, &ke, , NULL, , NULL);
return ;
}
EV_SET(&ke, sock, EVFILT_WRITE, EV_DISABLE, , , ud);
if (kevent(kfd, &ke, , NULL, , NULL) == -) {
sp_del(kfd, sock);
return ;
}
return ;
} static void
sp_write(int kfd, int sock, void *ud, bool enable) {
struct kevent ke;
EV_SET(&ke, sock, EVFILT_WRITE, enable ? EV_ENABLE : EV_DISABLE, , , ud);
if (kevent(kfd, &ke, , NULL, , NULL) == -) {
// todo: check error
}
} static int
sp_wait(int kfd, struct event *e, int max) {
struct kevent ev[max];
int n = kevent(kfd, NULL, , ev, max, NULL); int i;
for (i=;i<n;i++) {
e[i].s = ev[i].udata;
unsigned filter = ev[i].filter;
e[i].write = (filter == EVFILT_WRITE);
e[i].read = (filter == EVFILT_READ);
} return n;
} static void
sp_nonblocking(int fd) {
int flag = fcntl(fd, F_GETFL, );
if ( - == flag ) {
return;
} fcntl(fd, F_SETFL, flag | O_NONBLOCK);
} #endif
unix 一套机制. 个人觉得比 epoll好,不需要设置开启大小值. 真心话linux epoll 够用了. 估计服务器开发用它也就到头了.
上面代码还是很好懂得单独注册读写. 后面再单独删除.用法很相似.
前言总结. 对于大神的代码, 临摹的效果确实很好, 解决了很多开发中的难啃的问题. 而自己只需要临摹抄一抄就豁然开朗了.
他的还有一个, 设计上细节值得商榷, 条条大路通罗马. 对于 函数返回值
......
if (kevent(kfd, &ke, , NULL, , NULL) == -) {
sp_del(kfd, sock);
return ;
}
return ;
一般约定 返回0表示成功, 返回 -1表示失败公认的. 还有一个潜规则是返回 <0的表示错误, -1, -2, -3 各种错误状态.
返回 1, 2, 3 也表示成功, 并且有各种状态.
基于上面考虑,觉得它返回 1不好, 推荐返回-1.
还有
static int
sp_create() {
return epoll_create();
}
上面的代码, 菜鸟写也就算了. 对于大神只能理解为大巧若拙吧. 推荐用宏表示, 说不定哪天改了. 重新编译.
这里吐槽完了, 总的而言 云风的代码真的 很有感觉, 有一种细细而来的美感.
正文
到这里我们开始优化上面的代码.目前优化后结构是这样的.
说一下, sckpoll.h 是对外提供的接口文件. 后面 sckpoll-epoll.h 和 sckpoll-kqueue.h 是sckpoll 对应不同平台设计的接口补充.
中间的 '-' 标志表示这个文件是私有的不完整(部分)的. 不推荐不熟悉的实现细节的人使用.
这也是个潜规则. 好 先看 sckpoll.h
#ifndef _H_SCKPOLL
#define _H_SCKPOLL #include <stdbool.h> // 统一使用的句柄类型
typedef int poll_t; // 转存的内核通知的结构体
struct event {
void* s; // 通知的句柄
bool read; // true表示可读
bool write; // true表示可写
}; /*
* 统一的错误检测接口.
* fd : 检测的文件描述符(句柄)
* : 返回 true表示有错误
*/
static inline bool sp_invalid(poll_t fd); /*
* 句柄创建函数.可以通过sp_invalid 检测是否创建失败!
* : 返回创建好的句柄
*/
static inline poll_t sp_create(void); /*
* 句柄释放函数
* fd : 句柄
*/
static inline void sp_release(poll_t fd); /*
* 在轮序句柄fd中添加 sock文件描述符.来检测它
* fd : sp_create() 返回的句柄
* sock : 待处理的文件描述符, 一般为socket()返回结果
* ud : 自己使用的指针地址特殊处理
* : 返回0表示成功, -1表示失败
*/
static int sp_add(poll_t fd, int sock, void* ud); /*
* 在轮询句柄fd中删除注册过的sock描述符
* fd : sp_create()创建的句柄
* sock : socket()创建的句柄
*/
static inline void sp_del(poll_t fd, int sock); /*
* 在轮序句柄fd中修改sock注册类型
* fd : 轮询句柄
* sock : 待处理的句柄
* ud : 用户自定义数据地址
* enable : true表示开启写, false表示还是监听读
*/
static inline void sp_write(poll_t fd, int sock, void* ud, bool enable); /*
* 轮询句柄,等待有结果的时候构造当前用户层结构struct event 结构描述中
* fd : sp_create 创建的句柄
* es : 一段struct event内存的首地址
* max : es数组能够使用的最大值
* : 返回等待到的变动数, 相对于 es
*/
static int sp_wait(poll_t fd, struct event es[], int max); /*
* 为套接字描述符设置为非阻塞的
* sock : 文件描述符
*/
static inline void sp_nonblocking(int sock); // 当前支持linux的epoll和unix的kqueue, window会error. iocp机制和epoll机制好不一样呀
#if defined(__linux__)
# include "sckpoll-epoll.h"
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD) || defined(__NetBSD__)
# include "sckpoll-kqueue.h"
#else
# error Currently only supports the Linux and Unix
#endif #endif // !_H_SCKPOLL
参照原先总设计没有变化, 改变在于加了注释和统一了参数名,还有编译的判断流程.
继续看 epoll 优化后封装的代码 sckpoll-epoll.h
#ifndef _H_SCKPOLL_EPOLL
#define _H_SCKPOLL_EPOLL #include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h> // epoll 创建的时候创建的监测文件描述符最大数
#define _INT_MAXEPOLL (1024) /*
* 统一的错误检测接口.
* fd : 检测的文件描述符(句柄)
* : 返回 true表示有错误
*/
static inline bool
sp_invalid(poll_t fd) {
return fd < ;
} /*
* 句柄创建函数.可以通过sp_invalid 检测是否创建失败!
* : 返回创建好的句柄
*/
static inline poll_t
sp_create(void) {
return epoll_create(_INT_MAXEPOLL);
} /*
* 句柄释放函数
* fd : 句柄
*/
static inline
void sp_release(poll_t fd) {
close(fd);
} /*
* 在轮序句柄fd中添加 sock文件描述符.来检测它
* fd : sp_create() 返回的句柄
* sock : 待处理的文件描述符, 一般为socket()返回结果
* ud : 自己使用的指针地址特殊处理
* : 返回0表示成功, -1表示失败
*/
static int
sp_add(poll_t fd, int sock, void* ud) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = ud;
return epoll_ctl(fd, EPOLL_CTL_ADD, sock, &ev);
} /*
* 在轮询句柄fd中删除注册过的sock描述符
* fd : sp_create()创建的句柄
* sock : socket()创建的句柄
*/
static inline void
sp_del(poll_t fd, int sock) {
epoll_ctl(fd, sock, EPOLL_CTL_DEL, );
} /*
* 在轮序句柄fd中修改sock注册类型
* fd : 轮询句柄
* sock : 待处理的句柄
* ud : 用户自定义数据地址
* enable : true表示开启写, false表示还是监听读
*/
static inline void
sp_write(poll_t fd, int sock, void* ud, bool enable) {
struct epoll_event ev;
ev.events = EPOLLIN | (enable? EPOLLOUT : );
ev.data.ptr = ud;
epoll_ctl(fd, EPOLL_CTL_MOD, sock, &ev);
} /*
* 轮询句柄,等待有结果的时候构造当前用户层结构struct event 结构描述中
* fd : sp_create 创建的句柄
* es : 一段struct event内存的首地址
* max : es数组能够使用的最大值
* : 返回等待到的变动数, 相对于 es
*/
static int
sp_wait(poll_t fd, struct event es[], int max) {
struct epoll_event ev[max], *st = ev, *ed;
int n = epoll_wait(fd, ev, max, -);
// 用指针遍历速度快一些, 最后返回得到的变化量n
for(ed = st + n; st < ed; ++st) {
unsigned flag = st->events;
es->s = st->data.ptr;
es->read = flag & EPOLLIN;
es->write = flag & EPOLLOUT;
++es;
} return n;
} /*
* 为套接字描述符设置为非阻塞的
* sock : 文件描述符
*/
static inline void
sp_nonblocking(int sock) {
int flag = fcntl(sock, F_GETFL, );
if(flag < ) return;
fcntl(sock, F_SETFL, flag | O_NONBLOCK);
} #endif // !_H_SCKPOLL_EPOLL
还是有些变化的. 看人喜好了. 思路都是一样的. 这里用了C99 部分特性. 可变数组, 数组在栈上声明的 struct event ev[max]; 这样.
还有特殊语法糖 for(int i=0; i<.......) 等. 确实挺好用的. 要是目前编译器都支持C11(2011 年C指定标准)就更好了.
sckpoll-kqueue.h
#ifndef poll_socket_kqueue_h
#define poll_socket_kqueue_h #include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/event.h> /*
* 统一的错误检测接口.
* fd : 检测的文件描述符(句柄)
* : 返回 true表示有错误
*/
static inline bool
sp_invalid(poll_t fd) {
return fd < ;
} /*
* 句柄创建函数.可以通过sp_invalid 检测是否创建失败!
* : 返回创建好的句柄
*/
static inline poll_t
sp_create(void) {
return kqueue();
} /*
* 句柄释放函数
* fd : 句柄
*/
static inline
void sp_release(poll_t fd) {
close(fd);
} /*
* 在轮序句柄fd中添加 sock文件描述符.来检测它
* fd : sp_create() 返回的句柄
* sock : 待处理的文件描述符, 一般为socket()返回结果
* ud : 自己使用的指针地址特殊处理
* : 返回0表示成功, -1表示失败
*/
static int
sp_add(poll_t fd, int sock, void* ud) {
struct kevent ke;
EV_SET(&ke, sock, EVFILT_READ, EV_ADD, , , ud);
if (kevent(fd, &ke, , NULL, , NULL) == -) {
return -;
}
EV_SET(&ke, sock, EVFILT_WRITE, EV_ADD, , , ud);
if (kevent(fd, &ke, , NULL, , NULL) == -) {
EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, , , NULL);
kevent(fd, &ke, , NULL, , NULL);
return -;
}
EV_SET(&ke, sock, EVFILT_WRITE, EV_DISABLE, , , ud);
if (kevent(fd, &ke, , NULL, , NULL) == -) {
sp_del(fd, sock);
return -;
}
return ;
} /*
* 在轮询句柄fd中删除注册过的sock描述符
* fd : sp_create()创建的句柄
* sock : socket()创建的句柄
*/
static inline void
sp_del(poll_t fd, int sock) {
struct kevent ke;
EV_SET(&ke, sock, EVFILT_READ, EV_DELETE, , , NULL);
kevent(fd, &ke, , NULL, , NULL);
EV_SET(&ke, sock, EVFILT_WRITE, EV_DELETE, , , NULL);
kevent(fd, &ke, , NULL, , NULL);
} /*
* 在轮序句柄fd中修改sock注册类型
* fd : 轮询句柄
* sock : 待处理的句柄
* ud : 用户自定义数据地址
* enable : true表示开启写, false表示还是监听读
*/
static inline void
sp_write(poll_t fd, int sock, void* ud, bool enable) {
struct kevent ke;
EV_SET(&ke, sock, EVFILT_WRITE, enable ? EV_ENABLE : EV_DISABLE, , , ud);
kevent(fd, &ke, , NULL, , NULL);
} /*
* 轮询句柄,等待有结果的时候构造当前用户层结构struct event 结构描述中
* fd : sp_create 创建的句柄
* es : 一段struct event内存的首地址
* max : es数组能够使用的最大值
* : 返回等待到的变动数, 相对于 es
*/
static int
sp_wait(poll_t fd, struct event es[], int max) {
struct kevent ev[max], *st = ev, *ed;
int n = kevent(fd, NULL, , ev, max, NULL); for(ed = st + n; st < ed; ++st) {
unsigned filter = st->filter;
es->s = st->udata;
es->write = EVFILT_WRITE == filter;
es->read = EVFILT_READ == filter;
++es;
} return n;
} /*
* 为套接字描述符设置为非阻塞的
* sock : 文件描述符
*/
static inline void
sp_nonblocking(int sock) {
int flag = fcntl(sock, F_GETFL, );
if(flag < ) return;
fcntl(sock, F_SETFL, flag | O_NONBLOCK);
} #endif
这个没有使用, 感兴趣可以到unix上测试.
到这里 那我们开始 写测试文件了 首先是编译的文件Makefile
test.out : test.c
gcc -g -Wall -o $@ $^ clean:
rm *.out ; ls
测试的 demo test.c. 强烈推荐值得参考
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "sckpoll.h" // 目标端口和服务器监听的套接字个数
#define _INT_PORT (7088)
#define _INT_LIS (18)
// 一次处理事件个数
#define _INT_EVS (64) //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.3 if 的 代码检测
#define IF_CHECK(code) \
if((code) < ) \
CERR_EXIT(#code) /*
* 创建本地使用的服务器socket.
* ip : 待连接的ip地址, 默认使用NULL
* port : 使用的端口号
* : 返回创建好的服务器套接字
*/
static int _socket(const char* ip, unsigned short port) {
int sock, opt = SO_REUSEADDR;
struct sockaddr_in saddr = { AF_INET }; // 开启socket 监听
IF_CHECK(sock = socket(PF_INET, SOCK_STREAM, ));
//设置端口复用, opt 可以简写为1,只要不为0
IF_CHECK(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt));
// 设置bind绑定端口
saddr.sin_addr.s_addr = !ip || !*ip ? INADDR_ANY : inet_addr(ip);
saddr.sin_port = htons(port);
IF_CHECK(bind(sock, (struct sockaddr*)&saddr, sizeof saddr));
//开始监听
IF_CHECK(listen(sock, _INT_LIS)); // 这时候服务就启动起来并且监听了
return sock;
} /*
* 主逻辑, 测试sckpoll.h封装的简单读取发送 服务器
* 需要 C99或以上
*/
int main(int argc, char* argv[]) {
int i, n, csock, nr;
char buf[BUFSIZ];
struct sockaddr_in addr;
socklen_t clen = sizeof addr;
struct event es[_INT_EVS];
// 开始创建服务器套接字和my poll监听文件描述符
int sock = _socket(NULL, _INT_PORT);
poll_t fd = sp_create();
if(sp_invalid(fd)) {
close(sock);
CERR_EXIT("sp_create is error");
} // 开始设置非阻塞调节字后面注册监听
sp_nonblocking(sock);
// sock 值需要客户端下来, 这里会有警告没关系
if(sp_add(fd, sock, (void*)sock) < ) {
CERR("sp_add fd,sock:%d, %d.", fd, sock);
goto __exit;
} //开始监听
for(;;) {
n = sp_wait(fd, es, _INT_EVS);
if(n < ) {
if(errno == EINTR)
continue;
CERR("sp_wait is error");
break;
} //这里处理 各种状态
for(i=; i<n; ++i) {
struct event* e = es + i;
int nd = (int)e->s; // 有新的链接过来,开始注册链接
if(nd == sock) {
for(;;){
csock = accept(sock, (struct sockaddr*)&addr, &clen);
if(csock < ) {
if(errno == EINTR)
continue;
CERR("accept errno = %d.", errno);
}
break;
}
// 开始设置非阻塞调节字后面注册监听
sp_nonblocking(csock);
// sock 值需要客户端下来, 这里会有警告没关系
if(sp_add(fd, csock, (void*)csock) < ) {
close(csock);
CERR("sp_add fd,sock:%d, %d.", fd, csock);
}
continue;
} // 事件读取操作
if(e->read) {
for(;;){
nr = read(nd, buf, BUFSIZ-);
if(nr < && errno != EINTR && errno != EAGAIN) {
CERR("read buf error errno:%d.", errno);
break;
}
buf[nr] = '\0';
printf("%s", buf);
if(nr < BUFSIZ-) //读取完毕也直接返回
break;
}
//添加写事件, 方便给客户端回复信息
if(nr > )
sp_write(fd, nd,(void*)nd, true);
}
if(e->write) {
const char* html = "HTTP/1.1 500 Internal Server Error\r\n";
int nw = , sum = strlen(html);
while(nw < sum) {
nr = write(nd, buf + nw, sum - nw);
if(nr < ) {
if(errno == EINTR || errno == EAGAIN)
continue;
CERR("write is error sock:%d.", nd);
break;
}
nw += nr;
}
// 发送完毕关闭客户端句柄
close(nd);
}
}
} // 关闭打开的文件描述符
__exit:
sp_release(fd);
close(sock); return ;
}
一共才150行左右, 一般没有封装的epoll demo估计都250行. 上面可以再封装.等第二遍会来个更好的(继续临摹优化).
演示结果 先启动服务器
客户端测试结果
测试显示这个服务器处理收发数据都没问题. 到这里基本ok了. 上面 test.c 是采用 epoll LT触发模式, 但是用了 ET的读和写方式.
读 部分代码
for(;;){
nr = read(nd, buf, BUFSIZ-);
if(nr < && errno != EINTR && errno != EAGAIN) {
CERR("read buf error errno:%d.", errno);
break;
}
buf[nr] = '\0';
printf("%s", buf);
if(nr < BUFSIZ-) //读取完毕也直接返回
break;
}
//添加写事件, 方便给客户端回复信息
if(nr > )
sp_write(fd, nd,(void*)nd, true);
写的部分代码
const char* html = "HTTP/1.1 500 Internal Server Error\r\n";
int nw = , sum = strlen(html);
while(nw < sum) {
nr = write(nd, buf + nw, sum - nw);
if(nr < ) {
if(errno == EINTR || errno == EAGAIN)
continue;
CERR("write is error sock:%d.", nd);
break;
}
nw += nr;
}
// 发送完毕关闭客户端句柄
close(nd);
对于特殊信号基本都处理了. 到这里最后总结就是
熟能生巧,勤能补拙.
后记
错误是难免的, 交流会互相提高, 有机会继续分享这个专题. 想吐槽CSDN, 广告太多, 想封别人就封别人,坑, ╮(╯▽╰)╭. 拜~~
C高级 服务器内核分析和构建 (一)的更多相关文章
- 《Linux内核分析》第三周 构建一个简单的Linux系统MenuOS
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK THREE ...
- 高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群
高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群 libnet软件包<-依赖-heartbeat(包含ldirectord插件(需要perl-MailTools的rpm包)) l ...
- linux 内核分析工具 Dtrace、SystemTap、火焰图、crash等
<< System语言详解 >> 关于 SystemTap 的书. 我们在分析各种系统异常和故障的时候,通常会用到 pstack(jstack) /pldd/ lsof/ tc ...
- 中标麒麟高级服务器操作系统V6
平台: linux 类型: 虚拟机镜像 软件包: java-1.6.0 mysql-5.1.5 python-2.6 qt3-3.3.8b basic software linux neokylin ...
- 《Linux及安全》期中总结&《Linux内核分析》期终总结
[5216 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK NINE ...
- 《Linux内核分析》第八周 进程的切换和系统的一般执行过程
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK EIGHT ...
- 《Linux内核分析》期中总结
两个月Linux内核的学习,让我理解了Linux内核的基本工作原理,包括进程管理.内存管理.设备驱动.文件系统,从分析内核到了解整个系统是如何工作的.如何控制管理资源分配.进程切换并执行.各种策略和结 ...
- 《Linux内核分析》第四周 扒开系统调用的“三层皮”
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FOUR( ...
- 《Linux内核分析》第五周 扒开系统调用的三层皮(下)
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FIVE( ...
随机推荐
- visual studio R6034解决方案集 从VC6.0 或VC2003 到VC2005发现的问题
这是我转的一篇非常全的帖子 能查到的解决方法都在里面有提及: 我是使用 stdafx.h加入这句 code #pragma comment(linker, "\"/manifest ...
- Flash Builder 4.6 BUG 远程访问受阻
今天调试项目的时候,惊讶的发现在使用RemoteObject进行远程访问时出现奇怪现象,只能在服务器本地实现访问,在其他客户机上提示2048错误,send failed,差点没把我吓死,记得之前测试过 ...
- 慕课网-安卓工程师初养成-3-3 Java中的赋值运算符
来源:http://www.imooc.com/code/1298 赋值运算符是指为变量或常量指定数值的符号.如可以使用 “=” 将右边的表达式结果赋给左边的操作数. Java 支持的常用赋值运算符, ...
- 慕课网-安卓工程师初养成-2-6 Java中的数据类型
来源:http://www.imooc.com/code/1230 通常情况下,为了方便物品的存储,我们会规定每个盒子可以存放的物品种类,就好比在“放臭袜子的盒子”里我们是不会放“面包”的!同理,变量 ...
- android View 继承关系
二. View SurfaceView GLSurfaceView View SurfaceView GLSurfaceView 功能 显示视图,内置画布,提供图形绘制函数.触屏事件.按键事件 ...
- Android基础总结(9)——网络技术
这里主要讲的是如何在手机端使用HTTP协议和服务器端进行网络交互,并对服务器返回的数据进行解析,这也是Android最常使用到的网络技术了. 1.WebView的用法 Android提供的WebVie ...
- 【MySQL】MySQL锁和隔离级别浅析一
<MySQL技术内幕InnoDB存储引擎>第一版中对于MySQL的InnoDB引擎锁进行了部分说明,第二版有部分内容更新. 与MySQL自身MyISAM.MSSQL及其他平台BD锁的对比: ...
- jQuery插件之Cookie
一.jQuery.Cookie.js插件是一个轻量级的Cookie管理插件. 特别提醒,今日发现一个特别的错误,google浏览器提示:has no method $.cookie.火狐浏览器提示:$ ...
- Windows phone 8 学习笔记(1) 触控输入(转)
Windows phone 8 的应用 与一般的Pc应用在输入方式上最大的不同就是:Windows phone 8主要依靠触控操作.因此在输入方式上引入一套全新的触控操作方式,我们需要重新定义相关的事 ...
- sphinx 超好资料
http://www.ttlsa.com/?s=sphinx