server.c
#include "t_stdio.h"
#include
#include "info.h"
#include
#include "serv_tool.h"
#include
#define THREAD_NUM 100
sem_t tnum;//define a semaphore variable
void *run_deal(void *);//declare a function which is used to dispose a I/O event via a new thread
int main(void){
serv_info_t serv_info;//structure defined in info.h used to store server infomation.
/*
defined in serv_tool.c, #include "serv_tool.h"
generate a socket device which has been listened by function listen() and update serv_info
note: using the function get_listened_socket() update serv_info is not better which is ought to
be replaced with a independent function split from the function get_listened_socket()
*/
int sfd = get_listened_socket(&serv_info);
if(-1 == sfd) {
printf("in server.c main...get_listened_socket...\n");
return -1;
}
//epoll create and control
int epfd = epoll_create(1);
if(-1 == epfd) E_MSG("epoll_create", -1);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sfd;
int v = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);
if(-1 == v) E_MSG("epoll_ctl", -1);
//sem initn, THREAD_NUM is the max number of threads
//note:using macro is not better which is ought to be written in server.conf
sem_init(&tnum, 0, THREAD_NUM);
//epoll wait and deal with client's request
struct epoll_event events[100];
while(1){
int maxfd = epoll_wait(epfd, events, 100, -1);
if(-1 == maxfd) E_MSG("epoll_wait", -1);
for(int i=0; i<maxfd; i++){
if(events[i].data.fd == sfd){
int cfd = accept(sfd);
if(-1 == cfd) {
printf("in server.c main...h_accept...error\n");
return -1;
}
ev.events = EPOLLIN|EPOLLONESHOT; //EPOLLONESHOT is important for socket fd
ev.data.fd = cfd;
v = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if(-1 == v) E_MSG("epoll_ctl", -1);
}else{
printf("wait.....\n");
//check the remaining amount of threads and if it is 0, suspend the thread.
sem_wait(&tnum);
//create a thread, tid is unused, so don't store it.
pthread_t tmp;
/* structure deal_arg_t defined in serv_tool.h, stores function deal_cli_req()'s
arguments.When create a thread, the arg will be passed into the function
pthread_create() to be the arguments of function deal_cli_req(). */
deal_arg_t args;
args.fd = events[i].data.fd;
args.p_s_info = &serv_info;
pthread_create(&tmp, NULL, run_deal, (void *)&args);
//mark it detached
pthread_detach(tmp);
//print the id of the thread which will terminate.
printf("cread %lu end...\n", tmp);
}
}
}
return 0;
}
void *run_deal(void *args){
//Separate function deal_cli_req()'s arguments from structure deal_arg_t.
deal_arg_t *tmp = (deal_arg_t *)args;
//deal with client's request
deal_cli_req(tmp->fd, tmp->p_s_info);
//tnum + 1
//note: That sem_post() is invoked here is not good
//because the current thread do not really terminate yet.
sem_post(&tnum);
return NULL;
}
t_stdio.h
错误码宏函数#ifndef __T_STDIO_H__
#define __T_STDIO_H__
#include
#define E_MSG(STR,VAL) do{\
perror(STR);\
return (VAL);\
}while(0)
#endif
info.c
info.h
包含了一个用来接收配置文件中配置信息的结构体和客户端请求信息的结构体,对外提供一个服务器配置信息读取并存到结构体中的函数。info.h
#ifndef __SERVER_INFO_H__
#define __SERVER_INFO_H__
#include
#include
typedef struct server_info{
unsigned short int port;
int listen_backlog;
struct in_addr serv_ip;
char release_dir[128];
}serv_info_t;
//request info from client
typedef struct{
char method[12];
char URL[128];
char version[12];
int Content_Length;
}req_info_t;
//get the infomation from configuration file and store it in the structure serv_info_t
int serv_info_init(serv_info_t *);
#endif //__SERVER_INFO_H__
info.c
#include "info.h"
#include "t_stdio.h"
#include
#include
#include
#include
#include
#include
#include
#include
#define CONFIG_PATH "server.conf" //configuration directory
int serv_info_init(serv_info_t *info){
//open server.conf
int fd = open(CONFIG_PATH, O_RDONLY);
if(-1 == fd) E_MSG("open", -1);
//read and assignment
//note: if file is bigger than 4096B will error
char buf[4096];
int rcount = read(fd, buf, sizeof(buf));
if(-1 == rcount) E_MSG("read", -1);
//printf("%s", buf);
char *content = buf;
char *next = NULL;
do{
next = strsep(&content, "\n");
//printf("next:\n%s\n\n\n", next);
char *val = next;
char *key = strsep(&val, "=");
//printf("key:%s\n", key);
//printf("val:%s\n\n", val);
if(!strcmp(key, "port"))
info->port = htons((unsigned short int)atoi(val));//network byte order
else if(!strcmp(key, "listen_backlog"))
info->listen_backlog = atoi(val);
else if(!strcmp(key, "serv_ip"))
inet_pton(AF_INET, val, &info->serv_ip);
else if(!strcmp(key, "release_dir"))
strcpy(info->release_dir, val);
else
printf("unknown config...\n");
}while(strncmp(content, "\n", 1));
//close file
close(fd);
return 0;
}
serv_tool.c
serv_tool.h
封装了server.c中直接用到的自定义函数serv_tool.h
#ifndef __SERV_TOOL_H__
#define __SERV_TOOL_H__
#include
#include "info.h"
typedef struct{
int fd;
serv_info_t *p_s_info;
}deal_arg_t;
extern int get_listened_socket(serv_info_t *);
extern int deal_cli_req(int, const serv_info_t *);
#endif //__SERV_TOOL_H__
serv_tool.c
#include
#include
#include
#include
#include
#include
#include
#include
#include "info.h"
// return a file discriptor which has been listened
int get_listened_socket(serv_info_t *serv_info){
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfd) E_MSG("socket", -1);
//reuse the addr
int optval = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
//initialize server's info
//data in struct s_info has the network byte order if necessary
serv_info_init(serv_info);
//bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = serv_info->port;
addr.sin_addr = serv_info->serv_ip;
int bin = bind(sfd, (const struct sockaddr *)&addr, sizeof(addr));
if(-1 == bin) E_MSG("bind", -1);
//listen
int lis = listen(sfd, serv_info->listen_backlog);
if(-1 == lis) E_MSG("listen", -1);
return sfd;
}
//deal with client's request
int deal_cli_req(int fd, const serv_info_t *s_info){
char buf[4096];
//store request infomation, the structure is defined in serv_tool.h
req_info_t req_info;
int r = 0;
int entity = 0; //if request line and head line has been resolved, set 1
while((r = read(fd, buf, sizeof(buf)))>0){
char *rows = buf;
char *next = NULL;
char *word = NULL;
next = strsep(&rows, "\r");
int line_count = 0;//record the resolving line
//resolve request line and head line
do{
if(entity) break; //should resolve entity body
line_count++;
if(!next || (*rows != '\n')){
printf("request format error\n");
return -1;
}
if(1 == line_count){
word = strsep(&next, " ");
printf("%lumethod:%s\n", pthread_self(), word);
strcpy(req_info.method, word);
word = strsep(&next, " ");
printf("%luURL:%s\n", pthread_self(), word);
printf("%luversion:%s\n", pthread_self(), next);
strcpy(req_info.URL, word);
strcpy(req_info.version, next);
}else{
rows++;
word = strsep(&rows, ":");
int choice = 0;
if(!strcmp(word, "Content-Length")) choice = 1;
//printf("%s:", word);//if necessary, store this in structure.
rows++;
word = strsep(&rows, "\r");
//store other key: val in this code area
switch(choice){
case 1:
req_info.Content_Length = atoi(word);
break;
default:
break;
}
}
if((*(rows+1)) == '\r')
entity = 1;
}while(!entity);
//resolve Entity body
//note: the job should be done by Backend language such java, but the code is written here because I need test the client's POST request.
if(!strcmp(req_info.method, "POST")){
rows = rows + 3;
*(rows+req_info.Content_Length) = '\0';
char *post_key = NULL;
char *post_val = NULL;
char usrname[32];
char password[32];
post_key = strsep(&rows, "=");
post_val = strsep(&rows, "&");
strcpy(usrname, post_val);
printf("%s=%s\n", post_key, post_val);
post_key = strsep(&rows, "=");
post_val = strsep(&rows, "&");
strcpy(password, post_val);
printf("%s=%s\n", post_key, post_val);
if((!strcmp(usrname, "cjl")) && (!strcmp(password, "123456")))
write(fd, "success", 7);
else
write(fd, "failed", 6);
}
break;//note : !!!!!!!!!!!!!need modify, if the request is bigger than 4096B, error
}
char type[64] = {0};
char *tmp = strrchr(req_info.URL, '.');
strcpy(type, ++tmp);
if(!strcmp(req_info.method, "GET")){
char path[128] = {0};
strcpy(path, s_info->release_dir);
strcat(path, req_info.URL);
//404
if(access(path, F_OK|R_OK)){
if(!strcmp(type, "html")){
char *response = "HTTP/1.1 404 \r\nContent-Type: text/html\r\n\r\n";
write(fd, response, strlen(response));
char *str404 = "404NOT FOUND";
write(fd, str404, strlen(str404));
}else if(!strcmp(type, "png")){
char *response = "HTTP/1.1 404 \r\n\r\n";
write(fd, response, strlen(response));
}else{
}
}else{
if(!strcmp(type, "html")){
char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
write(fd, response, strlen(response));
}else if(!strcmp(type, "png")){
char *response = "HTTP/1.1 200 OK\r\nContent-Type: image/png\r\n\r\n";
write(fd, response, strlen(response));
}else{
}
int src_fd = open(path, O_RDONLY);
while((r=read(src_fd, buf, sizeof(buf))) > 0){
write(fd, buf, r);
}
}
}
close(fd);
printf("%lu deal end...\n", pthread_self());
return 0;
}
可改进:
sem_post(&tnum);
语句,此时100个线程都未结束,但是信号量为100,即可创建的线程数为100,此时如果有新的I/O事件,比如又来了100个I/O事件,会创建100个新线程,然后信号量为0,但是加上之前执行到sem_post(&tnum);
的100个线程,此时有200个线程同时运行,当然,执行到sem_post(&tnum);
的100个线程是即将结束的,也不占用太多的资源,但终归是逻辑上有点不好的。配置文件说明
port=9999
listen_backlog=100
serv_ip=0.0.0.0
release_dir=/home/cjl/uc/uc_project/server2.0/web
编译:gcc server.c info.c serv_tool.c -lpthread -o server
,运行server,打开浏览器访问本机任意ip加配置文件指定端口号(默认9999)/index.html即可访问,注意:下图的ip是我自己的ip
小收获: