How in reactor Based on? Is how to use reactor as a server and realize the business of the server.
Based on reactor , realize simple http protocol encapsulation. Just to illustrate how reactor does business, the business logic of the real http server is very complex.
The core of the server network layer, such as nginx and redis, is epoll, and the implementation uses reactor.
Relationship between http protocol encapsulation and reactor
reactor contains
-
reactor_run, event loop, io detection through epoll;
-
accept_cb, handle network connection;
-
recv_cb receives http requests from clients
-
send_cb send http response
What scenarios in the application layer use accept_cb?
- Restrict the access of specific ip;
- The function of load balancing is to forward the request to which server for processing.
If the http server has no special requirements, you do not need to modify the accept in reactor_ CB.
How to implement http based on reactor
http uses the reqeust reply model for communication. The client sends an http request, and the server processes the request and returns a response. The transport layer of http is based on TCP.
http tcp link life cycle:
- accept_cb
- recv_cb
- send_cb
For the server, you first need to use recv_cb receives http data and obtains a complete frame of http data by unpacking and pasting packets; Then analyze and process the http data; Finally, send_cb sends a response to the client.
Implement simple response
First, implement the simplest response. For all http requests, directly reply to a response with a piece of html. After returning to the client, the browser can display it directly. Send only_ In CB, you can send data by calling.
int http_response(struct ntyevent *ev) { if (ev == NULL) return -1; memset(ev->buffer, 0, BUFFER_LENGTH); const char *html = "<html><head><title>hello http</title></head><body><H1>Cong</H1></body></html>\r\n\r\n"; ev->length = sprintf(ev->buffer, "HTTP/1.1 200 OK\r\n\ Date: Sun, 30 Jan 2022 05:55:32 GMT\r\n\ Content-Type: text/html;charset=UTF-8\r\n\ Content-Length: 81\r\n\r\n%s", html); return ev->length; } int send_cb(int fd, int events, void *arg) { struct ntyreactor *reactor = (struct ntyreactor*)arg; struct ntyevent *ev = ntyreactor_idx(reactor, fd); http_response(ev); // int len = send(fd, ev->buffer, ev->length, 0); if (len > 0) { printf("send[fd=%d], [%d]%s\n", fd, len, ev->buffer); nty_event_del(reactor->epfd, ev); nty_event_set(ev, fd, recv_cb, reactor); nty_event_add(reactor->epfd, EPOLLIN, ev); } else { close(ev->fd); nty_event_del(reactor->epfd, ev); printf("send[fd=%d] error %s\n", fd, strerror(errno)); } return len; }
The HTTP response consists of four parts: status line, message header, blank line and response body.
http get request implementation
Implement http get request to obtain static resources.
The request message that the client sends an HTTP request to the server includes the following formats: request line, request header, blank line and request data. The following figure shows the general format of the request message.
Read request line
The http request needs to be parsed, the request type (GET, POST, etc.) determined, and the resource (uri) obtained
int http_request(struct ntyevent *ev) { // GET, POST char linebuf[1024] = {0}; int idx = readline(ev->buffer, 0, linebuf); if (strstr(linebuf, "GET")) { ev->method = HTTP_METHOD_GET; //uri int i = 0; while (linebuf[sizeof("GET ") + i] != ' ') i++; linebuf[sizeof("GET ")+i] = '\0'; sprintf(ev->resource, "./%s/%s", HTTP_WEBSERVER_HTML_ROOT, linebuf+sizeof("GET ")); } else if (strstr(linebuf, "POST")) { } }
When reading the http request header, it should be noted that http separates each line by \ r\n, and the first line is the request line, which contains the request type (GET, POST), uri, protocol version, etc.
How to define the integrity of a package in a user-defined protocol:
- Use separators, such as http \ r\n;
- Add the length of the packet in the protocol header.
http response
Find the static resource according to the uri above, and prepare the response status line and message header.
When sending different types of static resources, you can use the corresponding content type.
Send response
mmap
When sending a static resource file, you need to first read the file into memory, and then send the data in memory to the corresponding network fd. By using sendfile to send files, two steps are no longer required.
sendfile uses mmap
Zero copy uses mmap mode, which is essentially DMA mode and does not require CPU participation. For ordinary copy, the move instruction of CPU is required to copy data from disk to memory.
There is an area in the process called memory allocation area. When calling mmap, the file will be mapped to the corresponding area. Operating the file is the same as operating memory.
Complete code
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <sys/stat.h> #include <sys/sendfile.h> #define BUFFER_LENGTH 4096 #define MAX_EPOLL_EVENTS 1024 #define SERVER_PORT 9105 #define PORT_COUNT 1 #define HTTP_WEBSERVER_HTML_ROOT "html" #define HTTP_METHOD_GET 0 #define HTTP_METHOD_POST 1 typedef int NCALLBACK(int ,int, void*); struct ntyevent { int fd; int events; void *arg; int (*callback)(int fd, int events, void *arg); int status; char buffer[BUFFER_LENGTH]; int length; long last_active; // http param int method; // char resource[BUFFER_LENGTH]; int ret_code; }; struct eventblock { struct eventblock *next; struct ntyevent *events; }; struct ntyreactor { int epfd; int blkcnt; struct eventblock *evblk; //fd --> 100w }; int recv_cb(int fd, int events, void *arg); int send_cb(int fd, int events, void *arg); struct ntyevent *ntyreactor_idx(struct ntyreactor *reactor, int sockfd); 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 ; } int nty_event_add(int epfd, int events, struct ntyevent *ev) { struct epoll_event ep_ev = {0, {0}}; ep_ev.data.ptr = ev; ep_ev.events = ev->events = events; int op; if (ev->status == 1) { op = EPOLL_CTL_MOD; } else { op = EPOLL_CTL_ADD; ev->status = 1; } if (epoll_ctl(epfd, op, ev->fd, &ep_ev) < 0) { printf("event add failed [fd=%d], events[%d]\n", ev->fd, events); return -1; } return 0; } int nty_event_del(int epfd, struct ntyevent *ev) { struct epoll_event ep_ev = {0, {0}}; if (ev->status != 1) { return -1; } ep_ev.data.ptr = ev; ev->status = 0; epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_ev); return 0; } int readline(char *allbuf, int idx, char *linebuf) { int len = strlen(allbuf); for(;idx < len;idx ++) { if (allbuf[idx] == '\r' && allbuf[idx+1] == '\n') { return idx+2; } else { *(linebuf++) = allbuf[idx]; } } return -1; } int http_request(struct ntyevent *ev) { // GET, POST char linebuf[1024] = {0}; int idx = readline(ev->buffer, 0, linebuf); if (strstr(linebuf, "GET")) { ev->method = HTTP_METHOD_GET; //uri int i = 0; while (linebuf[sizeof("GET ") + i] != ' ') i++; linebuf[sizeof("GET ")+i] = '\0'; sprintf(ev->resource, "./%s/%s", HTTP_WEBSERVER_HTML_ROOT, linebuf+sizeof("GET ")); } else if (strstr(linebuf, "POST")) { } } int recv_cb(int fd, int events, void *arg) { struct ntyreactor *reactor = (struct ntyreactor*)arg; struct ntyevent *ev = ntyreactor_idx(reactor, fd); int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0); // if (len > 0) { ev->length = len; ev->buffer[len] = '\0'; printf("C[%d]:%s\n", fd, ev->buffer); //http http_request(ev); //send(); nty_event_del(reactor->epfd, ev); 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); close(ev->fd); //printf("[fd=%d] pos[%ld], closed\n", fd, ev-reactor->events); } else { nty_event_del(reactor->epfd, ev); close(ev->fd); printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno)); } return len; } int http_response(struct ntyevent *ev) { if (ev == NULL) return -1; memset(ev->buffer, 0, BUFFER_LENGTH); #if 0 const char *html = "<html><head><title>hello http</title></head><body><H1>Cong</H1></body></html>\r\n\r\n"; ev->length = sprintf(ev->buffer, "HTTP/1.1 200 OK\r\n\ Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n\ Content-Type: text/html;charset=ISO-8859-1\r\n\ Content-Length: 83\r\n\r\n%s", html); #else printf("resource: %s\n", ev->resource); int filefd = open(ev->resource, O_RDONLY); if (filefd == -1) { // return 404 ev->ret_code = 404; ev->length = sprintf(ev->buffer, "HTTP/1.1 404 Not Found\r\n" "Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n" "Content-Type: text/html;charset=ISO-8859-1\r\n" "Content-Length: 85\r\n\r\n" "<html><head><title>404 Not Found</title></head><body><H1>404</H1></body></html>\r\n\r\n" ); } else { struct stat stat_buf; fstat(filefd, &stat_buf); close(filefd); if (S_ISDIR(stat_buf.st_mode)) { ev->ret_code = 404; ev->length = sprintf(ev->buffer, "HTTP/1.1 404 Not Found\r\n" "Date: Sun, 30 Jan 2022 05:55:32 GMT\r\n" "Content-Type: text/html;charset=ISO-8859-1\r\n" "Content-Length: 85\r\n\r\n" "<html><head><title>404 Not Found</title></head><body><H1>404</H1></body></html>\r\n\r\n" ); } else if (S_ISREG(stat_buf.st_mode)) { ev->ret_code = 200; ev->length = sprintf(ev->buffer, "HTTP/1.1 200 OK\r\n" "Date: Sun, 30 Jan 2022 05:55:32 GMT\r\n" // "Content-Type: text/html;charset=ISO-8859-1\r\n" // "Content-Type: application/pdf\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %ld\r\n\r\n", stat_buf.st_size ); } } #endif return ev->length; } int send_cb(int fd, int events, void *arg) { struct ntyreactor *reactor = (struct ntyreactor*)arg; struct ntyevent *ev = ntyreactor_idx(reactor, fd); http_response(ev); // int len = send(fd, ev->buffer, ev->length, 0); if (len > 0) { // http header and body are sent twice printf("send[fd=%d], [%d]%s\n", fd, len, ev->buffer); if (ev->ret_code == 200) { int filefd = open(ev->resource, O_RDONLY); struct stat stat_buf; fstat(filefd, &stat_buf); // sendfile uses mmap sendfile(fd, filefd, NULL, stat_buf.st_size); close(filefd); } nty_event_del(reactor->epfd, ev); nty_event_set(ev, fd, recv_cb, reactor); nty_event_add(reactor->epfd, EPOLLIN, ev); } else { close(ev->fd); nty_event_del(reactor->epfd, ev); printf("send[fd=%d] error %s\n", fd, strerror(errno)); } return len; } int accept_cb(int fd, int events, void *arg) { struct ntyreactor *reactor = (struct ntyreactor*)arg; if (reactor == NULL) return -1; struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); int clientfd; if ((clientfd = accept(fd, (struct sockaddr*)&client_addr, &len)) == -1) { if (errno != EAGAIN && errno != EINTR) { } printf("accept: %s\n", strerror(errno)); return -1; } int flag = 0; if ((flag = fcntl(clientfd, F_SETFL, O_NONBLOCK)) < 0) { printf("%s: fcntl nonblocking failed, %d\n", __func__, MAX_EPOLL_EVENTS); return -1; } struct ntyevent *event = ntyreactor_idx(reactor, clientfd); nty_event_set(event, clientfd, recv_cb, reactor); nty_event_add(reactor->epfd, EPOLLIN, event); printf("new connect [%s:%d], pos[%d]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), clientfd); return 0; } int init_sock(short port) { int fd = socket(AF_INET, SOCK_STREAM, 0); fcntl(fd, F_SETFL, O_NONBLOCK); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(port); bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (listen(fd, 20) < 0) { printf("listen failed : %s\n", strerror(errno)); } return fd; } int ntyreactor_alloc(struct ntyreactor *reactor) { if (reactor == NULL) return -1; if (reactor->evblk == NULL) return -1; struct eventblock *blk = reactor->evblk; while (blk->next != NULL) { blk = blk->next; } struct ntyevent *evs = (struct ntyevent*)malloc((MAX_EPOLL_EVENTS) * sizeof(struct ntyevent)); if (evs == NULL) { printf("ntyreactor_alloc ntyevents failed\n"); return -2; } memset(evs, 0, (MAX_EPOLL_EVENTS) * sizeof(struct ntyevent)); struct eventblock *block = (struct eventblock *)malloc(sizeof(struct eventblock)); if (block == NULL) { printf("ntyreactor_alloc eventblock failed\n"); return -2; } memset(block, 0, sizeof(struct eventblock)); block->events = evs; block->next = NULL; blk->next = block; reactor->blkcnt ++; // return 0; } struct ntyevent *ntyreactor_idx(struct ntyreactor *reactor, int sockfd) { int blkidx = sockfd / MAX_EPOLL_EVENTS; while (blkidx >= reactor->blkcnt) { ntyreactor_alloc(reactor); } int i = 0; struct eventblock *blk = reactor->evblk; while(i ++ < blkidx && blk != NULL) { blk = blk->next; } return &blk->events[sockfd % MAX_EPOLL_EVENTS]; } int ntyreactor_init(struct ntyreactor *reactor) { if (reactor == NULL) return -1; memset(reactor, 0, sizeof(struct ntyreactor)); reactor->epfd = epoll_create(1); if (reactor->epfd <= 0) { printf("create epfd in %s err %s\n", __func__, strerror(errno)); return -2; } struct ntyevent *evs = (struct ntyevent*)malloc((MAX_EPOLL_EVENTS) * sizeof(struct ntyevent)); if (evs == NULL) { printf("ntyreactor_alloc ntyevents failed\n"); return -2; } memset(evs, 0, (MAX_EPOLL_EVENTS) * sizeof(struct ntyevent)); struct eventblock *block = (struct eventblock *)malloc(sizeof(struct eventblock)); if (block == NULL) { printf("ntyreactor_alloc eventblock failed\n"); return -2; } memset(block, 0, sizeof(struct eventblock)); block->events = evs; block->next = NULL; reactor->evblk = block; reactor->blkcnt = 1; return 0; } int ntyreactor_destory(struct ntyreactor *reactor) { close(reactor->epfd); //free(reactor->events); struct eventblock *blk = reactor->evblk; struct eventblock *blk_next = NULL; while (blk != NULL) { blk_next = blk->next; free(blk->events); free(blk); blk = blk_next; } return 0; } int ntyreactor_addlistener(struct ntyreactor *reactor, int sockfd, NCALLBACK *acceptor) { if (reactor == NULL) return -1; if (reactor->evblk == NULL) return -1; //reactor->evblk->events[sockfd]; struct ntyevent *event = ntyreactor_idx(reactor, sockfd); nty_event_set(event, sockfd, acceptor, reactor); nty_event_add(reactor->epfd, EPOLLIN, event); return 0; } int ntyreactor_run(struct ntyreactor *reactor) { if (reactor == NULL) return -1; if (reactor->epfd < 0) return -1; if (reactor->evblk == NULL) return -1; struct epoll_event events[MAX_EPOLL_EVENTS+1]; int checkpos = 0, i; while (1) { /* long now = time(NULL); for (i = 0;i < 100;i ++, checkpos ++) { if (checkpos == MAX_EPOLL_EVENTS) { checkpos = 0; } if (reactor->events[checkpos].status != 1) { continue; } long duration = now - reactor->events[checkpos].last_active; if (duration >= 60) { close(reactor->events[checkpos].fd); printf("[fd=%d] timeout\n", reactor->events[checkpos].fd); nty_event_del(reactor->epfd, &reactor->events[checkpos]); } } */ 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); } if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) { ev->callback(ev->fd, events[i].events, ev->arg); } } } } // 3, 6w, 1, 100 == // <remoteip, remoteport, localip, localport> int main(int argc, char *argv[]) { unsigned short port = SERVER_PORT; // listen 8888 if (argc == 2) { port = atoi(argv[1]); } struct ntyreactor *reactor = (struct ntyreactor*)malloc(sizeof(struct ntyreactor)); ntyreactor_init(reactor); int i = 0; int sockfds[PORT_COUNT] = {0}; for (i = 0;i < PORT_COUNT;i ++) { sockfds[i] = init_sock(port+i); ntyreactor_addlistener(reactor, sockfds[i], accept_cb); } //dup2(sockfd, STDIN); ntyreactor_run(reactor); ntyreactor_destory(reactor); for (i = 0;i < PORT_COUNT;i ++) { close(sockfds[i]); } free(reactor); return 0; } nts[i].events, ev->arg); } } } } // 3, 6w, 1, 100 == // <remoteip, remoteport, localip, localport> int main(int argc, char *argv[]) { unsigned short port = SERVER_PORT; // listen 8888 if (argc == 2) { port = atoi(argv[1]); } struct ntyreactor *reactor = (struct ntyreactor*)malloc(sizeof(struct ntyreactor)); ntyreactor_init(reactor); int i = 0; int sockfds[PORT_COUNT] = {0}; for (i = 0;i < PORT_COUNT;i ++) { sockfds[i] = init_sock(port+i); ntyreactor_addlistener(reactor, sockfds[i], accept_cb); } //dup2(sockfd, STDIN); ntyreactor_run(reactor); ntyreactor_destory(reactor); for (i = 0;i < PORT_COUNT;i ++) { close(sockfds[i]); } free(reactor); return 0; }
I recommend a free open course of zero sound college. Personally, I think the teacher speaks well. I share it with you: Linux, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP/IP, collaboration, DPDK and other technical contents, learn immediately