您的位置:首页 > 运维架构 > Linux

用C语言实现的简单Web服务器(Linux)

2012-04-19 17:08 686 查看
采摘处:http://blog.chinaunix.net/space.php?uid=22568683&do=blog&id=84698

相信大家对Apache都有所听闻,Apache是目前使用最为广泛我Web服务器。大家可以从http://news.netcraft.com/这个网站得到证实。这是腾讯的http://uptime.netcraft.com/up/graph?site=www.qq.com.Apache强大的功能和高效的性能并且开放源代码的这种模式对我很有吸引力,但无赖自己水平有限,无法从Apache庞大的source
code里面理清头绪,于是乎,我就冒出了个自己动手写一个小型的简单的Web服务器的想法,希望对这方面也和我一样感兴趣的朋友有所帮助。

我的实验环境为:

OS:Red Hat Enterprise Linux 5

gcc:4.1.2

libc:2.5

editor:Vim

lang:C

阅读该源代码需要以下预备知识:

C语言基础

Linux编程基础

socket编程基础(Linux)

TCP/IP基本原理

HTTP基本原理

关键字(Key Words):

Linux C, Web HTTP Server, Linux Socket.

-----------------------------------------------------------------------------------

下面是Mutu的第一个版本(0.1 Alpha),实现了WEB 服务器的最基本功能

包括以下源文件:

webserver.c----程序入口

init_socket.h init_socket.c----完成一些WEB服务器的初始化工作

get_time.h get_time.c----获得服务器的时间

http_session.h http_session.c----处理一次HTTP会话

以下是各文件源码:

webserver.c:

/*
* file:webserver.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "get_time.h"
#include "init_socket.h"
#include "http_session.h"

int main(int argc, char *argv[])
{
int listen_fd;
int connect_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
bzero(&server_addr, sizeof(struct sockaddr_in));
bzero(&client_addr, sizeof(struct sockaddr_in));

if(init_socket(&listen_fd, &server_addr) == -1)
{
perror("init_socket() error. in webserver.c");
exit(EXIT_FAILURE);
}

socklen_t addrlen = sizeof(struct sockaddr_in);
pid_t pid;

while(1)
{
if((connect_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addrlen)) == -1)
{
perror("accept() error. in webserver.c");
continue;
}
if( (pid = fork()) > 0)
{
close(connect_fd);
continue;
}
else if(pid == 0)
{
close(listen_fd);
printf("pid %d process http session from %s : %d\n", getpid(), inet_ntoa(client_addr.sin_addr), htons(client_addr.sin_port));
if(http_session(&connect_fd, &client_addr) == -1)
{
perror("http_session() error. in webserver.c");
shutdown(connect_fd, SHUT_RDWR);
printf("pid %d loss connection to %s\n", getpid(), inet_ntoa(client_addr.sin_addr));
exit(EXIT_FAILURE);        /* exit from child process, stop this http session */
}

printf("pid %d close connection to %s\n", getpid(), inet_ntoa(client_addr.sin_addr));
shutdown(connect_fd, SHUT_RDWR);
exit(EXIT_SUCCESS);
}
else
{
perror("fork() error. in webserver.c");
exit(EXIT_FAILURE);
}

}

shutdown(listen_fd, SHUT_RDWR);
return 0;
}


init_socket.h

/*
* file:init_socket.h
*/
#ifndef INIT_SOCKET_H
#define INIT_SOCKET_H
#include <netinet/in.h>

#define BACKLOG    20        /* length of listening queue on socket */
#define PORT    8080    /* web server listening port */

/* initialize the socket on server, include below
socket();
bind();
listen();
*/

/* listen_fd : the web server listen file decriptor
server_addr: the web server ipv4 address
RETURNS: success on 0, error on -1
*/
int init_socket(int *listen_fd, struct sockaddr_in *server_addr);

#endif


init_socket.c

/*
* file:init_socket.c
*/
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "init_socket.h"

int init_socket(int *listen_fd, struct sockaddr_in *server_addr)
{
if((*listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket() error. in init_socket.c");
return -1;
}

/* set reuse the port on server machine */
int opt = SO_REUSEADDR;
if(setsockopt(*listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
{
perror("setsockopt() error. in init_socket.c");
return -1;
}

server_addr->sin_family = AF_INET;
server_addr->sin_port = htons(PORT);
server_addr->sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(*listen_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr_in)) == -1)
{
perror("bind() error. in init_socket.c");
return -1;
}

if(listen(*listen_fd, BACKLOG) == -1)
{
perror("listen() error. in init_socket.c");
return -1;
}

return 0;
}


get_time.h

/*
* file: get_time.h
*/
#ifndef GET_TIME_H
#define GET_TIME_H

#define TIME_BUFFER_SIZE    40    /* buffer size of time_buffer */

char *get_time_str(char *time_buf);

#endif


get_time.c

/*
* file:get_time.c
*/
#include <time.h>
#include <stdio.h>
#include <string.h>
#include "get_time.h"

/* get the time on server,
return: the ascii string of time , NULL on error
argument: time_buf the buffer to store time_string
*/

char *get_time_str(char *time_buf)
{
time_t    now_sec;
struct tm    *time_now;
if(    time(&now_sec) == -1)
{
perror("time() in get_time.c");
return NULL;
}
if((time_now = gmtime(&now_sec)) == NULL)
{
perror("localtime in get_time.c");
return NULL;
}
char *str_ptr = NULL;
if((str_ptr = asctime(time_now)) == NULL)
{
perror("asctime in get_time.c");
return NULL;
}
strcat(time_buf, str_ptr);
return time_buf;
}


http_session.c

/*
* file: http_session.h
*/
#ifndef HTTP_SESSION_H
#define HTTP_SESSION_H

#include <netinet/in.h>

#define RECV_BUFFER_SIZE    1024        /* 1KB of receive buffer */
#define    SEND_BUFFER_SIZE    1050000        /* 1.xMB of send buffer */
#define    URI_SIZE            128            /* length of uri request from client browse */

#define TIME_OUT_SEC        10            /* select timeout of secend */
#define TIME_OUT_USEC        0            /* select timeout of usecend */

#define    FILE_OK                200
#define    FILE_FORBIDEN        403            /* there are no access permission*/
#define FILE_NOT_FOUND        404            /* file not found on server */
#define    UNALLOW_METHOD        405            /* un allow http request method*/
#define FILE_TOO_LARGE        413            /* file is too large */
#define    URI_TOO_LONG        414            /* */
#define    UNSUPPORT_MIME_TYPE    415
#define    UNSUPPORT_HTTP_VERSION    505
#define    FILE_MAX_SIZE        1048576        /* 1MB the max siee of file read from hard disk */

#define ALLOW                "Allow:GET"    /* the server allow GET request method*/
#define    SERVER                "Server:Mutu(0.1 Alpha)/Linux"

/* if the connect protocol is http then this function deal with it */
int http_session(int *connect_fd, struct sockaddr_in *client_addr);

/* if http protocol return 1, else return 0 */
int is_http_protocol(char *msg_from_client);

/* get the request header's uri */
char *get_uri(char *req_header, char *uri_buf);

/* get the uri status,access return 0, not exist return 1, permission deny return 2, error return -1 */
int get_uri_status(char *uri);

/* get the mime type of the file request in uri from client's browse */
char *get_mime_type(char *uri);

/* read the file which requested by client in uri ,and store in entity_buf.
success return bytes readed,error return -1
*/
int get_file_disk(char *uri, unsigned char *entity_buf);

/* set http replay header's status:
200:ok
404:file not found

*/
int set_rep_status();

int set_error_information(unsigned char *send_buf, int errorno);

int reply_normal_information(unsigned char *send_buf, unsigned char *file_buf, int file_size, char *mime_type);

#endif


http_session.c

/*
* file:http_session.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include "http_session.h"
#include "get_time.h"

int http_session(int *connect_fd, struct sockaddr_in *client_addr)
{
char recv_buf[RECV_BUFFER_SIZE + 1];            /* server socket receive buffer */
unsigned char send_buf[SEND_BUFFER_SIZE + 1];    /* server socket send bufrer */
unsigned char file_buf[FILE_MAX_SIZE + 1];
memset(recv_buf, '\0', sizeof(recv_buf));
memset(send_buf, '\0', sizeof(send_buf));
memset(file_buf, '\0', sizeof(file_buf));

char uri_buf[URI_SIZE + 1];                        /* store the the uri request from client */
memset(uri_buf, '\0', sizeof(uri_buf));

int maxfd = *connect_fd + 1;
fd_set read_set;
FD_ZERO(&read_set);

struct timeval timeout;
timeout.tv_sec = TIME_OUT_SEC;
timeout.tv_usec = TIME_OUT_USEC;

int flag = 1;
int res = 0;
int read_bytes = 0;
int send_bytes = 0;
int file_size = 0;
char *mime_type;
int uri_status;
FD_SET(*connect_fd, &read_set);
while(flag)
{

res = select(maxfd, &read_set, NULL, NULL, &timeout);
switch(res)
{
case -1:
perror("select() error. in http_sesseion.c");
close(*connect_fd);
return -1;
break;
case 0:            /* time out, continue to select */
continue;
break;
default:        /* there are some file-descriptor's status changed */
if(FD_ISSET(*connect_fd, &read_set))
{
memset(recv_buf, '\0', sizeof(recv_buf));
if((read_bytes = recv(*connect_fd, recv_buf, RECV_BUFFER_SIZE, 0)) == 0)
{
/* client close the connection */
return 0;
}
else if(read_bytes > 0)        /* there are some data from client */
{
if(is_http_protocol(recv_buf) == 0)    /* check is it HTTP protocol */
{
fprintf(stderr, "Not http protocol.\n");
close(*connect_fd);
return -1;
}
else        /* http protocol */
{
memset(uri_buf, '\0', sizeof(uri_buf));
if(get_uri(recv_buf, uri_buf) == NULL)    /* get the uri from http request head */
{
uri_status = URI_TOO_LONG;

}
else
{
printf("URI:%s\n", uri_buf);
uri_status = get_uri_status(uri_buf);
switch(uri_status)
{
case FILE_OK:
printf("file ok\n");
mime_type = get_mime_type(uri_buf);
printf("mime type: %s\n", mime_type);
file_size = get_file_disk(uri_buf, file_buf);
send_bytes = reply_normal_information(send_buf, file_buf, file_size, mime_type);

//         send(*connect_fd, send_buf, send_bytes, 0);

break;
case FILE_NOT_FOUND:    /* file not found on server */
printf("in switch on case FILE_NOT_FOUND\n");
send_bytes = set_error_information(send_buf, FILE_NOT_FOUND);

break;
case FILE_FORBIDEN:        /* server have no permission to read the request file */
break;
case URI_TOO_LONG:        /* the request uri is too long */

break;
default:
break;
}

send(*connect_fd, send_buf, send_bytes, 0);
}
}
}
}

}

}

return 0;
}

int is_http_protocol(char *msg_from_client)
{
/* just for test */
return 1;

int index = 0;
while(msg_from_client[index] != '\0' && msg_from_client[index] != '\n')
{
index++;
printf("%d%c",index - 1, msg_from_client[index - 1]);
}
if(strncmp(msg_from_client + index - 10, "HTTP/", 5) == 0)    /* HTTP Request firt line like this 'GET /index.html HTTP/1.1' , so last 10 byte are HTTP/1.1\r\n*/
{
return 1;
}

return 0;

}

char *get_uri(char *req_header, char *uri_buf)
{
int index = 0;
while( (req_header[index] != '/') && (req_header[index] != '\0') )
{
index++;
}
int base = index;
while( ((index - base) < URI_SIZE) && (req_header[index] != ' ') && (req_header[index] != '\0') )
{
index++;
}
if( (index - base) >= URI_SIZE)
{
fprintf(stderr, "error: too long of uri request.\n");
return NULL;
}
if((req_header[index - 1] == '/') && (req_header[index] == ' '))
{
strcpy(uri_buf, "index.html");
return uri_buf;
}
strncpy(uri_buf, req_header + base + 1, index - base - 1);
return uri_buf;

}

int get_uri_status(char *uri)
{
if(access(uri, F_OK) == -1)
{
fprintf(stderr, "File: %s not found.\n", uri);
return FILE_NOT_FOUND;
}
if(access(uri, R_OK) == -1)
{
fprintf(stderr, "File: %s can not read.\n", uri);
return FILE_FORBIDEN;
}
return FILE_OK;
}

char *get_mime_type(char *uri)
{
int len = strlen(uri);
int dot = len - 1;
while( dot >= 0 && uri[dot] != '.')
{
dot--;
}
if(dot == 0)        /* if the uri begain with a dot and the dot is the last one, then it is a bad uri request,so return NULL */
{
return NULL;
}
if(dot < 0)            /* the uri is '/',so default type text/html returns */
{
return "text/html";
}
dot++;
int type_len = len - dot;
char *type_off = uri + dot;
switch(type_len)
{
case 4:
if(!strcmp(type_off, "html") || !strcmp(type_off, "HTML"))
{
return "text/html";
}
if(!strcmp(type_off, "jpeg") || !strcmp(type_off, "JPEG"))
{
return "image/jpeg";
}
break;
case 3:
if(!strcmp(type_off, "htm") || !strcmp(type_off, "HTM"))
{
return "text/html";
}
if(!strcmp(type_off, "css") || !strcmp(type_off, "CSS"))
{
return "text/css";
}
if(!strcmp(type_off, "png") || !strcmp(type_off, "PNG"))
{
return "image/png";
}
if(!strcmp(type_off, "jpg") || !strcmp(type_off, "JPG"))
{
return "image/jpeg";
}
if(!strcmp(type_off, "gif") || !strcmp(type_off, "GIF"))
{
return "image/gif";
}
if(!strcmp(type_off, "txt") || !strcmp(type_off, "TXT"))
{
return "text/plain";
}
break;
case 2:
if(!strcmp(type_off, "js") || !strcmp(type_off, "JS"))
{
return "text/javascript";
}
break;
default:        /* unknown mime type or server do not support type now*/
return "NULL";
break;
}

return NULL;
}

int get_file_disk(char *uri, unsigned char *file_buf)
{
int read_count = 0;
int fd = open(uri, O_RDONLY);
if(fd == -1)
{
perror("open() in get_file_disk http_session.c");
return -1;
}
unsigned long st_size;
struct stat st;
if(fstat(fd, &st) == -1)
{
perror("stat() in get_file_disk http_session.c");
return -1;
}
st_size = st.st_size;
if(st_size > FILE_MAX_SIZE)
{
fprintf(stderr, "the file %s is too large.\n", uri);
return -1;
}
if((read_count = read(fd, file_buf, FILE_MAX_SIZE)) == -1)
{
perror("read() in get_file_disk http_session.c");
return -1;
}
printf("file %s size : %lu , read %d\n", uri, st_size, read_count);
return read_count;
}

int set_error_information(unsigned char *send_buf, int errorno)
{
register int index = 0;
register int len = 0;
char *str = NULL;
switch(errorno)
{

case FILE_NOT_FOUND:
printf("In set_error_information FILE_NOT_FOUND case\n");
str = "HTTP/1.1 404 File Not Found\r\n";
len = strlen(str);
memcpy(send_buf + index, str, len);
index += len;

len = strlen(SERVER);
memcpy(send_buf + index, SERVER, len);
index += len;

memcpy(send_buf + index, "\r\nDate:", 7);
index += 7;

char time_buf[TIME_BUFFER_SIZE];
memset(time_buf, '\0', sizeof(time_buf));
get_time_str(time_buf);
len = strlen(time_buf);
memcpy(send_buf + index, time_buf, len);
index += len;

str = "\r\nContent-Type:text/html\r\nContent-Length:";
len = strlen(str);
memcpy(send_buf + index, str, len);
index += len;

str = "\r\n\r\n<html><head></head><body>404 File not found<br/>Please check your url,and try it again!</body></html>";
len = strlen(str);
int htmllen = len;
char num_len[5];
memset(num_len, '\0', sizeof(num_len));
sprintf(num_len, "%d", len);

len = strlen(num_len);
memcpy(send + index, num_len, len);
index += len;

memcpy(send_buf + index, str, htmllen);
index += htmllen;
break;

default:
break;

}
return index;
}

int reply_normal_information(unsigned char *send_buf, unsigned char *file_buf, int file_size, char *mime_type)
{
char *str = "HTTP/1.1 200 OK\r\nServer:Mutu/Linux(0.1)\r\nDate:";
register int index = strlen(str);
memcpy(send_buf, str, index);

char time_buf[TIME_BUFFER_SIZE];
memset(time_buf, '\0', sizeof(time_buf));
str = get_time_str(time_buf);
int len = strlen(time_buf);
memcpy(send_buf + index, time_buf, len);
index += len;

len = strlen(ALLOW);
memcpy(send_buf + index, ALLOW, len);
index += len;

memcpy(send_buf + index, "\r\nContent-Type:", 15);
index += 15;
len = strlen(mime_type);
memcpy(send_buf + index, mime_type, len);
index += strlen(mime_type);

memcpy(send_buf + index, "\r\nContent-Length:", 17);
index += 17;
char num_len[8];
memset(num_len, '\0', sizeof(num_len));
sprintf(num_len, "%d", file_size);
len = strlen(num_len);
memcpy(send_buf + index, num_len, len);
index += len;

memcpy(send_buf + index, "\r\n\r\n", 4);
index += 4;

memcpy(send_buf + index, file_buf, file_size);
index += file_size;
return index;

}


所以源文件已经打包至附件。

I)大家可以单独编译,在shell 下输入如下命令即可:

gcc -Wall init_socket.c http_session.c get_time.c webserver.c -o webserver

II)下载附件后,将Mutu_webserver_0.1_Alpha.tar.gz解压到目录下:

tar zxvf Mutu_webserver_0.1_Alpha.tar.gz

直接用附件里面的makefile文件,在shell下输入如下命令:

make

以上两种方法都可以编译生成WEB 服务器。

生成webserver后,直接在shell下输入:

./webserver即可启动WEB服务器。

那么如何访问该服务器呢?

首先你要知道运行服务器主机的IP,在服务器主机上输入如下命令(需要超级用户权限):

ifconfig

如果你的是以太网(ethernet),那么会看到这样一行

inet addr:xxx.xxx.xxx broadcast:xxx.xxx.xxx.xxx mask:255.xxx.xxx.xxx

xxx代表数字(000-255),第一个inet addr后面的数字便是你的网卡地址。

如果你是在本机进行测试,那IP地址可以直接用127.0.0.1(回环地址,localhost)

取得服务器的IP后,用你喜欢的一款浏览器便可以访问WEB SERVER的内容了。

方法为:在浏览器的地址栏内输入:
http://xxx.xxx.xxx.xxx:8080
回车,即可(xxx.xxx.xxx.xxx无刚取得的服务器IP地址,8080为预设的端口)。

附件里面提供了几个测试用的页面,大家可以使用,也可以自己写一些HTML页面然后放到WEB服务器的根目录(即webserver文件所在的目录)


文件:Mutu_webserver_0.1_Alpha.tar.gz
大小:18KB
下载:下载
注意事项:

1.如果你改变了服务器的监听端口,则地址栏中也要做相应改变,0-1023的端口需要超级用户才能开启

2.本程序仅仅提供大家参考和测试,对运行此程序所造成的一切后果请使用者自负。

3.你可以自由传播和修改该程序。

4.如果我有时间的话,在下一个版本(0.2 Beta)中会给程序适当添加注释,并修正一些bug.

5.转接请注明出处,谢谢!

注:这还只是一个Alpha版本,所以我没有给程序写太多的注释,同时该程序也有不少bug,欢迎大家下载使用,同时为诚恳的请求各位把使用过程中遇到的问题告诉我,谢谢!

本人才疏学浅,这是第一次在CU发表文章,谢谢大家都支持,错误之处难免很多,欢迎各位批评指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: