前段时间写了个嵌入式linux的web服务器(
帖子在这),最近再改版,想像JSP那种java和HTML混写的形式,整一个c语言和HTML混写。这顺便把我当时从头开始的思路整理出来,给有兴趣的朋友参考参考。
本文假设您有C语言基础,了解一些linux下的编程,知道有个叫socket的东西...如果您不知道这些,并且也想自己写一个web服务器,那您可以先学学这些,或者找我吧。。。
先说说一些简单的原理吧。浏览器访问web服务器的时候,默认会连接服务器的80端口,并向其发送HTTP报文,用来告诉服务器要请求哪个资源。这个HTTP报文其实是明文,ascii码字符串,也就是说我们能读出这个数据流的话,转成字符串也能直接看出些东西,那当浏览器要向服务器请求页面的话,他所发送的http请求是什么样的呢?
以下一段代码可以假装成一个服务器,监听在80端口上,并把收到的数据在控制台上打印。
头文件省略…
9 #define BUFFER_SIZE 10240
10 #define PORT 80
11
12 int main()
13 {
14 struct sockaddr_in server_socket, client_socket;
15 int sockfd, client_fd,sin_size;
16
17 char buff[BUFFER_SIZE];
18
19 sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建一个socket
20
21 server_XXXXXXXXXn_family = AF_INET; //结构体初始化22 server_XXXXXXXXXn_port = htons(PORT);23 server_XXXXXXXXXn_addr.s_addr = INADDR_ANY;24 bzero(&(server_XXXXXXXXXn_zero), 8);25 //将socket与80端口绑定,监听80端口
26 bind(sockfd, (struct sockaddr *)&server_socket, sizeof(struct sockaddr)) ;
27 //设置监听
28 listen(sockfd, 5) ;
29
30 sin_size = sizeof(client_socket); //下面等待客户端连接,如没有连接该函数会一直等到有连接进来才返回。
31 client_fd = accept(sockfd, (struct sockaddr *)&client_socket, &sin_size);
32 buff[BUFFER_SIZE] = 0;
33
34 recv(client_fd, buff, BUFFER_SIZE - 1,0);
35 perror("recv");
36 printf("%s\n",buff);
37 close(client_fd);
38
39 sleep(10);
40 return 0;
41 }
编译并运行这个程序,然后使用一个浏览器访问运行这个程序电脑的IP,比如说
http://192.168.128.111/XXXXXXXXXXml。192.168.128.111是运行这个程序计算机IP,/XXXXXXXXXXml是表示请求在web根目录下的一个叫XXXXXXXXXXml的页面,然后再来看看服务器收到了什么?
第三行的recv:success是由程序第35行输出的perror输出success表示上一步执行没有错误,第四行看到了一个GET /XXXXXXXXXXml HTTP/1.1,这就是一个最重要的开始 Get 表示客户端是以GET方式提交请求,方式还有其它的,比如POST,这个后面再讲。
一个空格以后的/XXXXXXXXXXml 表示客户端请求在web根目录下的一个叫XXXXXXXXXXml的页面。HTTP/1.1 是表示浏览器的版本信息。
弄明白这三条就够我们开始了,下面一大串后面再讲,有兴趣的也可以自己先查查资料。
既然客户端都请求了,现在我们试着给客户端返回点什么。。
在程序前面声明两个字符串:
char httphead[]= {"HTTP/1.1 200 OK\r\n\r\n"};
char httpdata[]= {" <head> <title>primula</title><head>
wahahaha
"};
然后再close前把这个字符串发到客户端
63 send(client_fd, httphead, strlen(httphead) , 0);
64 send(client_fd, httpdata, strlen(httpdata) , 0);
65 close(client_fd);
然后再次编译执行,再用浏览器访问
出现了,IE的标题变成primula ,正文也有了。学过HTML的仁兄会明白,其实添加上去的第二个数组就是一段HTML文本,<title>标签指明了浏览器标题名,正文的wahahaha在
标签的修饰下变大了。
如果我们换个url呢,比如http://192.168.128.111/XXXXXXXXml,就能发现,不管你输入的是什么网址(当然这个网址是在我们这个服务器下的,比如我的192.168.128.111),它返回的都是这个页面。
这是为什么呢?因为服务器压根就没有看你提交的申请,不管你申请的是什么页面,都返回个这个东西给你,浏览器只是很诚实的提交你的URL,然后很诚实的将服务器返回的信息显示回来。就像你老爸叫你去买烟,就算你给老板说了你要买云烟或者轿子,但老板每次给你的都是红塔山。因为你只是个跑腿的,也确实从老板那收到烟了,但你却不知道这个烟没对,依然屁颠屁颠跑回去了…同样,浏览器也只是个跑腿的,他负责将你的链接交给服务器,负责显示从服务器那收到的信息,只有你才能判断出服务器每次给你的都是红塔山。。
这个特性可以用在404错误页面上,当你访问一个不存在的页面时,服务器可以将一个固定的错误页面返回给你,这个也后面再说。
刚才说到了第二个数组的内容,现在说第一个字符串的内容
HTTP/1.1 200 OK\r\n\r\n (为了明显点,多加了几个空格)
HTTP/1.1:表示服务器使用的HTTP版本。
200是状态码,表示成功,常见得还有404啊500啊什么的,
OK是描述状态的字符
最后跟了一串\r\n\r\n,这个是干什么的呢?
\r\n是一个回车换行,第一个\r\n 是HTTP/1.1 200 OK的,第二个\r\n是一个空行,HTTP协议规定报文头的结束是以一个空行结束的,也就是浏览器在读报文头读到一个空行的时候,就知道报文头结束了,接下来是正文了,当然在这正文就是第二个字符串。
服务器返回的一个典型的HTTP报文就是这样的
HTTP/1.1 200 OK\r\n
Server: daisy\r\n
……………………\r\n
\r\n --------------从这个空行开始,下面的内容都是正文了
<html>
<head>
……………………..
现在大体上就能够知道服务器和浏览器是怎么通过HTTP传信息的了吧。
再想一下一个web服务器的实现,如果我们能从客户端的请求中提取出他请求的页面内容,比如从GET /XXXXXXXXXXml HTTP/1.1 中提取出/XXXXXXXXXXml 这个信息,是不是我们就能够在某个目录中去找XXXXXXXXXXml 这个文件并传送给客户端,如果下次又被客户端以http://192.168.128.111/XXXXXXXXml的url请求了一个XXXXXXXXml的文件,那不是也可以在某个目录下去找XXXXXXXXml了么,那也就是只要我们把网页放在某个目录下,让一个监听在80端口上的程序能够解析出发送来的请求,然后将请求的页面传过去。。。这不就是一个简单的web服务器了吗?
未完。。有空再继续。。。(可能吧[s:220])
200字以内,仅用于支线交流,主线讨论请采用回复功能。