Linux C/C++服务器
http服务器的实现
http服务器说的是在reactor网络io框架基础上加上http协议,实现可与浏览器交互的后端服务,reactor在之前有介绍,下面我们先来看看http协议都有哪些东西
http协议
http消息分两部分,一个是客户端请求消息,一个是服务器响应消息
http客户端请求消息
客户端发送一个http请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行(/r/n)和请求数据四部分组成
客户端请求示例:
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
http服务器响应消息
HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文
服务端响应示例:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
结果:网页端显示
Hello World! My payload includes a trailing CRLF.
关于http请求方法、响应头信息、状态码这些详细信息,在这个文档中都有
reactor服务端框架
框架主要由这四个接口组成,1.ntyreactor_run() 保证服务端7*24小时运行 2.accpet_cb() 与客户端建立连接 3.recv_cb() 接受客户端http请求 4.send_cb() 发送http响应
其中accept_cb()可以做ip限制、负载均衡的场景中使用
reactor框架
reactor框架在之前有介绍,这里对之前版本的reactor代码做了部分优化,让它看起来更漂亮更健壮一些;
event事件中新增回调函数及http方法
struct ntyevent {
int fd;
int events;
void *arg;
int (*callback)(int fd, int events, void *arg);
int status;
char buffer[BUFFER_LENGTH];
char wbuffer[BUFFER_LENGTH];
int length;
int wlength;
//long last_active;
// http reqeust
int method;
char resource[RESOURCE_LENGTH];
};
event回调函数设置接口,在send_cb和recv_cb接口的最后调用它
void nty_event_set(struct ntyevent *ev, int fd, NCALLBACK callback, void *arg) {
ev->fd = fd;
ev->callback = callback;
ev->events = 0;
ev->arg = arg;
//ev->last_active = time(NULL);
return ;
}
在epoll_wait事件触发后调用相应的回调函数
//ntyreactor_run
while (1) {
int nready = epoll_wait(reactor->epfd, events, MAX_EPOLL_EVENTS, 1000);
if (nready < 0) {
printf("epoll_wait error, exit\n");
continue;
}
for (i = 0;i < nready;i ++) {
struct ntyevent *ev = (struct ntyevent*)events[i].data.ptr;
if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {
ev->callback(ev->fd, events[i].events, ev->arg); //here
}
if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {
ev->callback(ev->fd, events[i].events, ev->arg);
}
}
}
那么剩下的就只有http协议如何去解析recv_cb()和回传的问题send_cb()
recv_cb()
parse
// connection
// sock_item --> fd, rbuffer, wbuffer, clientaddr
int recv_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = ntyreactor_idx(reactor, fd);
if (ev == NULL) return -1;
int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0);
nty_event_del(reactor->epfd, ev);
if (len > 0) {
ev->length = len;
ev->buffer[len] = '\0';
//printf("recv [%d]:%s\n", fd, ev->buffer);
nty_http_request(ev); // parser http hdr
nty_event_set(ev, fd, send_cb, reactor);
nty_event_add(reactor->epfd, EPOLLOUT, ev);
} else if (len == 0) {
nty_event_del(reactor->epfd, ev);
//printf("recv_cb --> disconnect\n");
close(ev->fd);
} else {
if (errno == EAGAIN && errno == EWOULDBLOCK) { //
} else if (errno == ECONNRESET){
nty_event_del(reactor->epfd, ev);
close(ev->fd);
}
//printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return len;
}
send_cb()
responce
int send_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = ntyreactor_idx(reactor, fd);
if (ev == NULL) return -1;
nty_http_response(ev); //encode
int len = send(fd, ev->wbuffer, ev->wlength, 0);
if (len > 0) {
//printf("resource: %s\n", ev->resource);
int filefd = open(ev->resource, O_RDONLY);
//if (filefd < 0) return -1;
struct stat stat_buf;
fstat(filefd, &stat_buf);
int flag = fcntl(fd, F_GETFL, 0);
flag &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flag);
int ret = sendfile(fd, filefd, NULL, stat_buf.st_size);
if (ret == -1) {
printf("sendfile: errno: %d\n", errno);
}
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flag);
close(filefd);
send(fd, "\r\n", 2, 0);
nty_event_del(reactor->epfd, ev);
nty_event_set(ev, fd, recv_cb, reactor);
nty_event_add(reactor->epfd, EPOLLIN, ev);
} else {
nty_event_del(reactor->epfd, ev);
close(ev->fd);
//printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return len;
}
代码实现,比较简单,直接看源码就好