Linux C简单的web服务器
?????????????????????????????? Linux C簡單的web服務器
?
目錄
Linux C簡單的web服務器
一、基礎類型重命名
二、包裹函數(wrap.h/wrap.c 主要是網絡通訊和多線程的包裹函數)
三、服務端程序(web_server.h/web_server.c)—— 使用EPOLL高并發機制
四、HTTP解析(http.h/http.c寫了一個基本的框架,很容易添加需要解析的文件類型)
五、項目目錄組織(以項目的角度建立工程)
六、編譯執行
HTTP基本協議
HTTP基本協議參照上面鏈接。我這里只介紹軟件的設計過程和源碼。
一、基礎類型重命名
#ifndef _TYPE_H_ #define _TYPE_H_/* exact-width signed integer types */ typedef signed char int8_t; typedef signed short int int16_t; typedef signed int int32_t;/* exact-width unsigned integer types */ typedef unsigned char uint8_t; typedef unsigned short int uint16_t; typedef unsigned int uint32_t; //typedef unsigned int size_t;#ifndef NULL #ifdef __cplusplus // EC++ #define NULL 0 #else #define NULL ((void *) 0) #endif #endif#ifndef boolean typedef uint8_t boolean; #endif#ifndef FALSE #define FALSE 0 #endif#ifndef TRUE #define TRUE 1 #endif#endif /* _TYPE_H_ */二、包裹函數(wrap.h/wrap.c 主要是網絡通訊和多線程的包裹函數)
#ifndef WRAP_H #define WRAP_H#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/socket.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/epoll.h> #include <pthread.h>//===================================Socket/File Wrapper Function========================================== int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); void Bind(int fd, const struct sockaddr* sa, socklen_t salen); void Connect(int fd, const struct sockaddr* sa, socklen_t salen); void Listen(int fd, int backlog); int Socket(int family, int type, int protocol); void Setsockopt(int fd, int level, int optname, void *optval, socklen_t optlen); int Ioctl(int d, int request, ...); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Write(int fd, const void *ptr, size_t nbytes); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Writen(int fd, const void*vptr, size_t n); ssize_t Readline(int fd, void *vptr, size_t maxlen); int Open(const char *pathname, int flags, mode_t mode); void Close(int fd);//========================================Epoll Wrapper function============================================ int Epollcreate(int size); int Epollctl(int epfd, int op, int fd, struct epoll_event *event); int Epollwait(int epfd, struct epoll_event * events, int maxevents, int timeout);//======================================pthread Wrapper function============================================ void Pthread_create(pthread_t *tid, const pthread_attr_t *attr, void * (*func)(void *), void *arg); void Pthread_detach(pthread_t tid); void Pthread_join(pthread_t tid, void **status); void Pthread_mutex_lock(pthread_mutex_t *mptr); void Pthread_mutex_unlock(pthread_mutex_t *mptr); void Pthread_cond_signal(pthread_cond_t *cptr); void Pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);#endif #include "wrap.h"static void perr_exit(const char *s) {perror(s);exit(1); }int Accept(int fd, struct sockaddr *sa, socklen_t* salenptr) {int newfd; again:if((newfd = accept(fd, sa, salenptr)) < 0){if((errno == ECONNABORTED) || (errno == EINTR)){goto again;}else{perr_exit("accept error");}}return newfd; }void Bind(int fd, const struct sockaddr* sa, socklen_t salen) {if(bind(fd, sa, salen) < 0){perr_exit("bind error");} }void Connect(int fd, const struct sockaddr *sa, socklen_t salen) {if(connect(fd, sa, salen) < 0){perr_exit("connect error");} }void Listen(int fd, int backlog) {if(listen(fd, backlog) < 0){perr_exit("listen error");} }int Socket(int family, int type, int protocol) {int socketfd;if((socketfd = socket(family, type, protocol)) < 0){perr_exit("socket error");}return socketfd; }void Setsockopt(int fd, int level, int optname, void *optval, socklen_t optlen) {if(-1 == setsockopt(fd, level, optname, optval, optlen)){perr_exit("setsockopt error");} }ssize_t Read(int fd, void *ptr, size_t nbytes) {ssize_t n; again:if((n = read(fd, ptr, nbytes)) == -1){if(errno == EINTR){goto again;}else{return -1;}}return n; }ssize_t Write(int fd, const void *ptr, size_t nbytes) {ssize_t n; again:if((n = write(fd, ptr, nbytes)) == -1){if(errno == EINTR){goto again;}else{return -1;}}return n; }ssize_t Readn(int fd, void *vptr, size_t n) {size_t nleft;ssize_t nread;char *ptr;ptr = vptr;nleft = n;while(nleft > 0){if((nread = read(fd, ptr, nleft)) < 0){if(errno == EINTR){nread = 0;}else{return -1;}}else if(nread == 0){break;}nleft -= nread;ptr += nread;}return (n - nleft); }ssize_t Writen(int fd, const void *vptr, size_t n) {size_t nleft;ssize_t nwritten;const char* ptr;ptr = vptr;nleft = n;while(nleft > 0){if((nwritten = write(fd, ptr, nleft)) <= 0){if(nwritten < 0 && errno == EINTR){nwritten = 0;}else{return -1;}}nleft -= nwritten;ptr += nwritten;}return n; }static ssize_t my_read(int fd, char *ptr) {static int read_cnt;static char *read_ptr;static char read_buf[100];if(read_cnt <= 0){ again:if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0){if(errno == EINTR){goto again;}else{return -1;}}else if(read_cnt == 0){return 0;}read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1; }ssize_t Readline(int fd, void *vptr, size_t maxlen) {ssize_t n, rc;char c, *ptr;ptr = vptr;for(n = 1; n < maxlen; n++){if((rc = my_read(fd, &c)) == 1){*ptr++ = c;if(c == '\n'){break;}}else if(rc == 0){*ptr = 0;return n - 1;}else{return (n - 1);}}*ptr = 0;return n; }int Open(const char *pathname, int flags, mode_t mode) {int fd = open(pathname, flags, mode);if( -1 == fd ){perr_exit("open file error...");}return fd; }void Close(int fd) {if(close(fd) == -1){perr_exit("close error...");} }int Epollcreate(int size) {int sockfd;if((sockfd = epoll_create(size)) == -1){perr_exit("epoll create error...");}return sockfd; }int Epollctl(int epfd, int op, int fd, struct epoll_event *event) {int status;if((status = epoll_ctl(epfd, op, fd, event)) == -1){perr_exit("epoll ctl error...");}return status; }int Epollwait(int epfd, struct epoll_event * events, int maxevents, int timeout) {int ret;if((ret = epoll_wait(epfd, events, maxevents, timeout)) == -1){perr_exit("epoll wait error...");}return ret; }void Pthread_create(pthread_t *tid, const pthread_attr_t *attr,void * (*func)(void *), void *arg) {int n = pthread_create(tid, attr, func, arg);if ( n == 0){return;}errno = n;perr_exit("pthread_create error"); }void Pthread_detach(pthread_t tid) {int n = pthread_detach(tid);if ( n == 0){return;}errno = n;perr_exit("pthread_detach error"); }void Pthread_join(pthread_t tid, void **status) {int n = pthread_join(tid, status);if ( n == 0 ){return;}errno = n;perr_exit("pthread_join error"); }void Pthread_mutex_lock(pthread_mutex_t *mptr) {int n = pthread_mutex_lock(mptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_mutex_lock error..."); }void Pthread_mutex_unlock(pthread_mutex_t *mptr) {int n = pthread_mutex_unlock(mptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_mutex_unlock error..."); }void Pthread_cond_signal(pthread_cond_t *cptr) {int n = pthread_cond_signal(cptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_cond_signal error..."); }void Pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr) {int n = pthread_cond_wait(cptr, mptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_cond_wait error..."); }三、服務端程序(web_server.h/web_server.c)—— 使用EPOLL高并發機制
#ifndef SERVER_H_ #define SERVER_H_#include <pthread.h> #include <sys/epoll.h> #include "wrap.h" #include "klist.h"#define WEB_SERVER_PORT 80 #define WEB_SOCKET_EVENTS 65535typedef struct {int client_fd;struct sockaddr_in client_addr;struct list_head list; } web_client_t;typedef struct {int sockfd; // server socketint port; // server portstruct sockaddr_in addr; // server addrint epollfd; // epoll handlestruct epoll_event event_list[WEB_SOCKET_EVENTS]; // epoll event listpthread_t recv_thread;web_client_t client; // client list -- save all client info } web_server_t;/* recv and send queue frame */ #define TCP_FRAME_SIZE 1200 typedef struct {int sockfd; // client socketuint16_t length;char data[TCP_FRAME_SIZE]; } web_frame_t;void *WebServertInit(void);#endif /* SERVER_H_ */ #include "web_server.h" #include "http.h"static web_server_t *web_socket_init(void) {int opt = 1;web_server_t *current;struct epoll_event event;current = (web_server_t *)malloc(sizeof(web_server_t));current->port = WEB_SERVER_PORT;current->sockfd = Socket(AF_INET, SOCK_STREAM, 0);// SOL_SOCKET: port can same, ip notSetsockopt(current->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));current->addr.sin_family = AF_INET;current->addr.sin_port = htons(current->port);current->addr.sin_addr.s_addr = INADDR_ANY;Bind(current->sockfd, (struct sockaddr *)¤t->addr, sizeof(current->addr));Listen(current->sockfd, 10);current->epollfd = Epollcreate(WEB_SOCKET_EVENTS);event.events = EPOLLIN | EPOLLET;event.data.fd = current->sockfd;// epoll_ctl set addEpollctl(current->epollfd, EPOLL_CTL_ADD, current->sockfd, &event);INIT_LIST_HEAD(¤t->client.list);return current; }static void web_socket_accept(web_server_t *arg) {web_server_t *current = arg;struct sockaddr_in addr;int len = sizeof(struct sockaddr_in);int new_fd = Accept(current->sockfd, (struct sockaddr *)&addr, (socklen_t *)&len);printf("new connection client_fd ( %d ) %s: %d\n", new_fd, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));/* register epoll */struct epoll_event event;event.data.fd = new_fd;event.events = EPOLLIN | EPOLLET;Epollctl(current->epollfd, EPOLL_CTL_ADD, new_fd, &event);/* add client node */web_client_t *node = (web_client_t *)malloc(sizeof(web_client_t));node->client_fd = new_fd;memcpy(&node->client_addr, &addr, sizeof(struct sockaddr_in));list_add(&node->list, ¤t->client.list); }static void web_socket_recv(web_server_t *arg, int sockfd) {web_server_t *current = arg;int length = 0;web_frame_t frame = {0};web_client_t *point = NULL;struct list_head *pos, *cur = NULL;length = read(sockfd, frame.data, TCP_FRAME_SIZE);if(0 == length){list_for_each_safe(pos, cur, ¤t->client.list){/* delete client node, close connect socket */point = list_entry(pos, web_client_t, list);if(point->client_fd == sockfd){printf("client[%d] close\n", sockfd);list_del(pos);free(point);Close(sockfd);}}}else if(length > 0){frame.sockfd = sockfd;frame.length = length;printf("***************************************\r\n");printf("%s\r\n", frame.data);printf("***************************************\r\n");HTTPSend(sockfd, frame.data, frame.length);#if 0list_for_each_safe(pos, cur, ¤t->client.list){/* delete client node, close connect socket */point = list_entry(pos, web_client_t, list);if(point->client_fd == sockfd){printf("client[%d] close\n", sockfd);list_del(pos);free(point);Close(sockfd);return;}} #endif}else{exit(-1);}}static void *server_recv_thread(void *arg) {web_server_t *current = (web_server_t *)arg;while(1){int timeout = 300, i;int ret = Epollwait(current->epollfd, current->event_list, WEB_SOCKET_EVENTS, timeout);if(ret == 0){// timeoutcontinue;}for(i = 0; i < ret; ++i){if((current->event_list[i].events & EPOLLERR) ||(current->event_list[i].events & EPOLLHUP) ||!(current->event_list[i].events & EPOLLIN)){printf("epoll error\n");Close(current->event_list[i].data.fd);exit(-1);}if(current->event_list[i].data.fd == current->sockfd){web_socket_accept(current);}else{web_socket_recv(current, current->event_list[i].data.fd);}}}Close(current->epollfd);Close(current->sockfd);return NULL; }void * WebServertInit(void) {web_server_t *current = web_socket_init();Pthread_create(¤t->recv_thread, NULL, server_recv_thread, current);return (void *)current; }四、HTTP解析(http.h/http.c寫了一個基本的框架,很容易添加需要解析的文件類型)
#ifndef _HTTP_H_ #define _HTTP_H_#include "type.h"void HTTPSend(int socketfd, char *content, uint16_t length);#endif /* _HTTP_H_ */ #include "http.h" #include <stdio.h> #include <string.h> #include "wrap.h"static void http_html_cb(int sockfd, const char *filename); static void http_jpg_cb(int sockfd, const char *filename); static void http_gif_cb(int sockfd, const char *filename);typedef struct {char *type;void (* http_cb)(int sockfd, const char *data); } http_type_t;static http_type_t http_items[] = {{".html", http_html_cb},{".jpg", http_jpg_cb},{".jpeg", http_jpg_cb},{".gif" , http_gif_cb}, };static const char *http_ack = "HTTP/1.1 200 OK\r\n""Content-Type: %s\r\n""Server: LiBang's Server V1.0\r\n""Accept-Ranges: bytes\r\n""Content-Length: %d\r\n\r\n";static void http_url(char *http_head, char *file_type, char *filename) {uint16_t i = 0, j = 0, k = 0;while(http_head[i] != '/'){++i;}while(http_head[i] != '.'){++i;filename[k++] = http_head[i];}while(http_head[i] != ' '){file_type[j++] = http_head[i++];filename[k++] = http_head[i];}filename[k - 1] = '\0'; // delete ' ' }static void http_pakedge_send(int sockfd, const char *filename, char *type) {FILE *filefd;char http_head[2048] = {0}, buffer[1024000] = {0};uint32_t len;filefd = fopen(filename, "rb");if(filefd == NULL){printf("\r\n error...\r\n");return;}fseek(filefd, 0, SEEK_END);len = ftell(filefd);rewind(filefd);fread(buffer, len, 1, filefd);sprintf(http_head, http_ack, type, len);send(sockfd, http_head, strlen(http_head), 0);send(sockfd, buffer, len, 0); }static void http_html_cb(int sockfd, const char *filename) {http_pakedge_send(sockfd, filename, "text/html"); }static void http_jpg_cb(int sockfd, const char *filename) {http_pakedge_send(sockfd, filename, "image/jpeg"); }static void http_gif_cb(int sockfd, const char *filename) {http_pakedge_send(sockfd, filename, "image/gif"); }void HTTPSend(int sockfd, char *content, uint16_t length) {char i, type[36] = {0}, filename[100] = {0};http_url(content, type, filename);printf("URL: %s, filename: %s\r\n", type, filename);for(i = 0; i < sizeof(http_items) / sizeof(http_items[0]); ++i){if(memcmp(http_items[i].type, type, strlen(http_items[i].type)) == 0){http_items[i].http_cb(sockfd, filename);break;}} }要添加的文件類型就在http_items這個結構體數組里面,按照格式添加就OK了。
接下來創建一個html文件(index.html)
<html> <head><title> Test Page </title></head> <body><p> Test OK </p><img src = "mypic.jpg"> </body>\ </html>在這個html里面,網頁會顯示“Test OK”和一張圖片(這個圖片自己想辦法弄吧,哈哈)。
到這里,項目的架構基本上就完成了,再建一個main.c文件,調用一下接口就完了。
#include <stdio.h> #include "web_server.h"int main(void) {WebServertInit();printf("------------test-----------\n");while(1){sleep(1);}return 0; }五、項目目錄組織(以項目的角度建立工程)
common/
?????????????? klist.h? type.h? wrap.c? wrap.h? Makefile
webserver/
?????????????? http.h? http.c? web_server.h? web_server.c? Makefile
index.html
mypic.jpj
main.c
Makefile
Makefile.build
頂層目錄有一個Makefile,各子層目錄下都有一個Makefile。
先寫頂層目錄下的Makefile和Makefile.build
依次是Makefile和Mkefile.build文件,最終會在頂層生成一個可執行文件http(目錄可以自行指定):
CROSS_COMPILE =AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nmSTRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdumpexport AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMPCFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/common CFLAGS += -I $(shell pwd)/webserver CFLAGS += -I $(shell pwd)/LDFLAGS := -lm -lpthreadexport CFLAGS LDFLAGSTOPDIR := $(shell pwd) export TOPDIRTARGET := httpobj-y += common/ obj-y += webserver/ obj-y += main.oall : make -C ./ -f $(TOPDIR)/Makefile.build$(CC) -o $(TARGET) built-in.o $(LDFLAGS)clean:rm -f $(shell find -name "*.o")rm -f $(shell find -name "*.d")rm -f $(TARGET) PHONY := __build __build:obj-y := subdir-y :=include Makefile__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) subdir-y += $(__subdir-y)subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)cur_objs := $(filter-out %/, $(obj-y)) dep_files := $(foreach f,$(cur_objs),.$(f).d) dep_files := $(wildcard $(dep_files))ifneq ($(dep_files),)include $(dep_files) endifPHONY += $(subdir-y)__build : $(subdir-y) built-in.o$(subdir-y):make -C $@ -f $(TOPDIR)/Makefile.buildbuilt-in.o : $(cur_objs) $(subdir_objs)$(LD) -r -o $@ $^dep_file = .$@.d%.o : %.c$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<.PHONY : $(PHONY)好了,說完了頂層目錄(我就不給各位詳細分析Makefile了,我以前也寫過Makefile的博客,可以參考),再說說各子層Makefile的編寫。上面把模板寫好了,各子層就只需要包含它自己的.o文件就好。
common目錄下的Makefile:
obj-y += wrap.owebserver目錄下的Makefile:
obj-y += http.o obj-y += web_server.o六、編譯執行
切換到頂層目錄,執行以下make即可生成可執行文件http。
有四個警告,你們去消除吧,哈哈,執行以下看看效果唄,在web網頁上面輸入 http://自己的IP/index.html,這個index.html是自己編寫的。好了看看效果吧,正常解析出了這個html。
我們在log里面也打印出了接收到的消息,我們來看看。
OK,到這里一個最簡單的一個web服務器就完成了,服務器采用了epoll機制,打開多個網頁不成問題,http解析的部分寫的比較簡單,但是結構是比較清晰的,根據自己的需要可以添加各種類型的文件,一種類型一個處理函數,框架是不用動的。不過有一點可惜的是,目前大多數都是用https協議了(http+ssl),就是不再是透明傳輸了,加了一層加密層,讓數據更加安全,這個簡單的https的實現后面再說。
?
?
?
?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的Linux C简单的web服务器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux tar压缩文件命令,tar打
- 下一篇: FLEX地图应用教程集