x86/debian GNU Linux/gcc
1. 题目
实现一个简单的Web服务器myhttpd。服务器程序启动时要读取配置文件/etc/myhttpd.conf,其中需
要指定服务器监听的端口号和服务目录,例如:
Port=8000 Directory=/var/www |
注意,1024以下的端口号需要超级用户才能开启服务。如果你的系统中已经安装了某种Web服务器(例如Apache),应该为myhttpd选择一个不同的端口号。当浏览器向服务器请求文件时,服务器就从服务目录(例如/var/www)中找出这个文件,加上HTTP协议头一起发给浏览器。就从服务目录(例如/var/www)中找出这个文件,加上HTTP协议头一起发给浏览器。
但是,如果浏览器请求的文件是可执行的则称为CGI程序,服务器并不是将这个文件发给浏览器,而是在服务器端执行这个程序,将它的标准输出发给浏览器,服务器不发送完整的HTTP协议头,CGI程序自己负责输出一部分HTTP协议头。(Code has not include this part)
2. 总结
This code below is a server, browser is like a client(see基于TCP/IP的简单的聊天程序). If client know server's “ip address”and “port number”, then client can connect to the server.
The browser send information below when first connect to server, we can call read() function to receive this information. When server call write() function send information to client, server must include “HTTP Head” that browser can Distinguish server's information's type. After receive server's information, browser automatic analysis server's information then handle it(such as show it on browser).
3. Ready
Create myhttp.conf file:
Then write below content to myhttp.conffile:
Port=8000 Directory=/var/www |
Create index.html file:
Then write below content to index.html:
<html><body><h1>It works!</h1> <p>This is the default web page for this server.</p> <p>Hefei, liyue, dengbo, caibosong, yangtao, lxr in lab1012 of 25</p> </body></html> |
4. code
/*Filename: myhttp.c
*Brife: As a web server, response browse's text and CGI reqest
*Author: One fish
*Date: 2014.9.11 Thu
*Last modifed: 2014.9.12-17:28 Fri
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
//---------------------------------------------------------Macro define-------------------
#define MAXLINE 80
#define BACKBLOG 10
#define HTTP_SIZE 800
#define HTTP_FILENAME "myhttp.txt" //The file save browser
#define HTML_FILENAME "index.html" //The file send to browser when browser connect to server
#define CONF_FILENAME "/etc/myhttp.conf" //Web dirctory information
#define HTTP_OK_HEAD "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n\r\n"
//---------------------------------------------------------Macro define-------------------
//----------------------Global varible----------------------------------------------------
typedef struct listen{
int port;
char dir[20];
}myweb;
//----------------------Global varible----------------------------------------------------
//--------------------------------------Function declaration-----------------------------
void perr_exit(const char *s);
int socket_init(int ser_port);
void handle_close(int socktfd);
myweb read_web_conf(const char *confile);
int read_http_client(int connfd, const char *filename);
int write_http_client(int connfd, const char *htmlname);
//--------------------------------------Function declaration-----------------------------
int main(void)
{
int connfd;
char wbuf[MAXLINE];
myweb mywebads;
//Read web port number and web dirctory from "/etc/myhttp.conf"
mywebads = read_web_conf(CONF_FILENAME);
//Create socket, bind to special ip adress and port number
connfd = socket_init(mywebads.port);
//After browse connect server,
//then read information into HTTP_FILENAME from browse
read_http_client(connfd, HTTP_FILENAME);
//Get the web dirctory and index.html
memset(wbuf, 0, MAXLINE);
strcpy(wbuf, mywebads.dir);
strcat(wbuf, "/");
strcat(wbuf, HTML_FILENAME);
//Response to browse
write_http_client(connfd, wbuf);
//Close server
handle_close(connfd);
return 0;
}
//---------------------------------------------------Function define--------------------------------------------
//Read port number and web-dirctory from "/etc/myhttp.conf"
myweb read_web_conf(const char *confile)
{
FILE *fp;
char *pstr;
char buf[MAXLINE];
myweb webaddress;
if ( NULL == (fp = fopen(confile, "r") ) ) {
perr_exit("fopen confile");
}
memset(buf, 0, MAXLINE);
//Read port number
if (NULL == fgets(buf, MAXLINE - 1, fp)) {
perr_exit("fgets error");
}
pstr = strchr(buf, '=');
pstr++;
webaddress.port = atoi(pstr);
memset(buf, 0, MAXLINE);
//Read wen dirctory
if (NULL == fgets(buf, MAXLINE - 1, fp)) {
perr_exit("fgets error");
}
pstr = strchr(buf, '=');
pstr++;
strcpy(webaddress.dir, pstr);
//Clear end '\n'
pstr = strchr(webaddress.dir, '\n');
*pstr = '\0';
return webaddress;
}
//Create server's socket and handle the error
int create_socket(int family, int type, int protocol)
{
int scktfd;
if ( (scktfd = socket(family, type, protocol)) < 0 )
perr_exit("socket error");
return scktfd;
}
//Bind socket description to port(ip + port)
void bind_server_socket(int scktfd, const struct sockaddr *sa, socklen_t salen)
{
if ( bind(scktfd, sa, salen) < 0)
perr_exit("bind error");
}
//Set server's listen number
void set_listen(int scktfd, int backlog)
{
if (listen(scktfd, backlog) < 0)
perr_exit("listen error");
}
//Server accept client connect function
int handle_accept(int scktfd, struct sockaddr *sa, socklen_t *salenptr)
{
int clifd;
again:
if ( ( clifd = accept(scktfd, sa, salenptr) ) < 0) {
if ( (errno == ECONNABORTED) || (errno == EINTR) ) {
goto again;
} else {
perr_exit("accept error");
}
return clifd;
}
}
//Create one socket and bind it to special address and port
int socket_init(int ser_port)
{
int socktfd;
int connfd;
int cliaddr_len;
char str[INET_ADDRSTRLEN];
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
connfd = -1;
socktfd = -1;
//Create a socket and return its description
socktfd = create_socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(ser_port);
//Bind socktfd to any ip address and a special port
bind_server_socket(socktfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//Set server's listen number
set_listen(socktfd, BACKBLOG);
cliaddr_len = sizeof(cliaddr);
printf("Accepting browse visting...\n");
//Wait clients connect
connfd = handle_accept(socktfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
//Print client's information
printf("received from %s at port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
return connfd;
}
//Read form browse via read()
ssize_t read_http_infor(int fd, void *ptr, size_t nbytes)
{
ssize_t rv;
again:
if ( -1 == (rv = read(fd, ptr, nbytes)) ) {
if (errno == EINTR) {
goto again;
} else {
return -1;
}
return rv;
}
}
//Write http information via write()
ssize_t write_http_infor(int fd, const void *ptr, size_t nbytes)
{
ssize_t rv;
again:
if ( -1 == (rv = write(fd, ptr, nbytes)) ) {
if (errno == EINTR) {
goto again;
} else {
return -1;
}
}
return rv;
}
//Handle close socket file description error
void handle_close(int scktfd)
{
if (-1 == close(scktfd))
perr_exit("close error");
}
//Error handle function
void perr_exit(const char *s)
{
perror(s);
exit(1);
}
//Recive information client(browse) send, save them into file
int read_http_client(int connfd, const char *filename)
{
int rv;
FILE *fp;
char buf[HTTP_SIZE];
rv = -1;
fp = NULL;
if ( NULL == (fp = fopen(filename, "w+") ) ) {
perr_exit("fopen file");
}
if ( -1 == (rv = read_http_infor(connfd, buf, HTTP_SIZE)) ) {
perr_exit("read browse");
}
if ( 0 > (rv = fprintf(fp, "%s\n", buf)) ) {
perr_exit("fprintf output");
}
return rv;
}
//Response to browse via write() according to http information which browse send
int write_http_client(int connfd, const char *htmlname)
{
int rv;
char lbuf[MAXLINE];
char wbuf[800];
FILE *fp;
if ( NULL == (fp = fopen(htmlname, "r")) ) {
perr_exit("fopen file");
}
memset(wbuf, 0, sizeof(wbuf));
//HTTP protocol head which used to know the information's type by browse
strncpy(wbuf, HTTP_OK_HEAD, strlen(HTTP_OK_HEAD));
//The information's content
while ( NULL != fgets(lbuf, MAXLINE - 1, fp) ) {
strncat(wbuf, lbuf, strlen(lbuf));
}
//Write information to browse
write_http_infor(connfd, wbuf, sizeof(wbuf));
fclose(fp);
return 0;
}
//---------------------------------------------------Function define--------------------------------------------
|
5. run
compile and run this code:
gcc myhttp.c -o mythhp ./myhttp Accepting browse visting... |
打开浏览器,输入服务器IP(ifconfig)and port number:192.168.1.168:8000. At this time, server's status:
root@debian:/home/lly/mydir/lly_books/linux_c_programming_osl/socket#./myhttp Accepting browse visting... received from 192.168.1.118 at port 41377 root@debian:/home/lly/mydir/lly_books/linux_c_programming_osl/socket# |
browser send information to server(savein myhttp.txt):
vi myhttp.txt |
This content save in myhttp.txt by server code: int read_http_client(int connfd, const char *filename)
其中每一行的末尾都是回车加换行(C语言的"\r\n"),第一行是GET请求和协议版本,其余几行选项字段我们不讨论,HTTP协议头的最后有一个空行,也是回车加换行。
我们实现的Web服务器只要能正确解析第一行就行了,这是一个GET请求,请求的是服务目录的根目录/(在本例中实际上是/var/www),Web服务器应该把该目录下的索引页(默认是index.html)发给浏览器,也就是把/var/www/index.html发给浏览器。
The interface on browser is:
This text send by server with this code:int write_http_client(int connfd, const char *htmlname)
Server must add http-head before thistext, then send to browser. One of the http-head is:HTTP/1.1 200OK\r\nContent-Type:text/html\r\n\r\n".服务器应答的HTTP头也是每行末尾以回车加换行结束,最后跟一个空行的回车加换行。
HTTP头的第一行是协议版本和应答码,200表示成功,后面的消息OK其实可以随意写,浏览器是不关心的,主要是为了调试时给开发人员看的。虽然网络协议最终是程序与程序之间的对话,但是在开发过程中却是人与程序之间的对话,一个设计透明的网络协议可以提供很多直观的信息给开发人员,因此,很多应用层网络协议,如HTTP、FTP、SMTP、POP3等都是基于文本的协议,为的是透明性(transparency)。
The text on the browser was server send just now. Server send this information from /var/www/index.html,index.html's content is:
vi /var/www/index.html
Server must add http-head before thistext, then send to browser. One of the http-head is:HTTP/1.1 200OK\r\nContent-Type:text/html\r\n\r\n".服务器应答的HTTP头也是每行末尾以回车加换行结束,最后跟一个空行的回车加换行。
HTTP头的第一行是协议版本和应答码,200表示成功,后面的消息OK其实可以随意写,浏览器是不关心的,主要是为了调试时给开发人员看的。虽然网络协议最终是程序与程序之间的对话,但是在开发过程中却是人与程序之间的对话,一个设计透明的网络协议可以提供很多直观的信息给开发人员,因此,很多应用层网络协议,如HTTP、FTP、SMTP、POP3等都是基于文本的协议,为的是透明性(transparency)。
HTTP头的第二行表示即将发送的文件的类型(称为MIME类型),这里是text/html,纯文本文件是text/plain,图片则是image/jpg、image/png等。
然后就发送文件的内容,发送完毕之后主动关闭连接,这样浏览器就知道文件发送完了。这一点比较特殊:通常网络通信都是客户端主动发起连接,主动发起请求,主动关闭连接,服务器只是被动地处理各种情况,而HTTP协议规定服务器主动关闭连接。
This is a practice of communication ofserver and browser. CGI and multi-process situation not included inthis code. If server need handle more complicated situation, serverapplication can follow below steps:
1.解析浏览器的请求,在服务目录中查找相应的文件,如果找不到该文件就返回404错误页面
2. 如果找到了浏览器请求的文件,用stat(2)检查它是否可执行
3. 如果该文件可执行:
a. 发送HTTP/1.1200 OK给客户端
b.fork(2),然后用dup2(2)重定向子进程的标准输出到客户端socket
c.在子进程中exec(3)该CGI程序
d.关闭连接
4. 如果该文件不可执行:
a.发送HTTP/1.1200 OK给客户端
b.如果是一个图片文件,根据图片的扩展名发送相应的Content-Type给客户端
c.如果不是图片文件,这里我们简化处理,都当作Content-Type:text/html
d.简单的HTTP协议头有这两行就足够了,再发一个空行表示结束
e.读取文件的内容发送到客户端
f.关闭连接
[2014.9.12--20:06]
LCNote Over.
所有评论(0)