这学期选的限选课是C语言,主要的感受是课程少,实验多。课程学时是八周,每周2学时;而实验也是八周,可是每周4学时。由此看来,还是实验比较重要。应了那句老话,实践是检验真理的唯一标准。
实验课最后的综合实验是做一个简易的B/S系统——Http Server和精简的浏览器,整体框架如下:
我实现的功能:
以下基本要求:
总共分为两个程序:浏览器端、服务器端
A、浏览器端是一个精简的浏览器,可以是用户界面也可以是控制台模式,基本功能如下:
1、 能接收用户输入的使用IP地址的URL,如http://127.0.0.1:80/test.html
2、 能显示接收到的响应,包括响应头和响应文件。可以显示在控制台上,也可以显示在用户界面上,也可以调用系统浏览器打开
3、 通信使用HTTP协议,至少应支持GET
B、服务器端是一个精简的Web服务器(HTTP服务器),使用控制台模式,基本功能如下:
1、 能接收用户浏览器端GET请求
2、 能够读取服务器中存储的html文件,并返回给请求客户端
3、 使用一种并发技术 (多进程/多线程/多路IO复用)来提高处理请求的并发度
4、 支持简单的日志功能:记录(访问ip、访问时间、访问资源)等信息
都已经很好地实现了;
扩展要求实现了:
1、 服务器端支持错误处理,如要访问的URL不存在时回复404错误等
2、 服务器端为了提高处理速度,可以使用缓存机制,包括文件缓存内存数据结构的设计;
题目要求就是要做一个HttpServer和一个精简的浏览器。
而为了实现这两个东西,我们大概要用到套接字(socket)和http协议相关的知识,其中包括URL的知识。
然后,我们要有一个browser程序,一个server程序。此外还有一个test.html文件作为传送的目标。
打开server让它运作;然后打开browser,输入URL,回车,请求就会生成报文(http协议)并发送给server。server接收到报文后,解析报文,并返回相应的应答报文。
题目要求中可以服务器和客户端都可以是终端形式,为了简单明了我也就没有选择开发带图形界面的服务器及客户端,又客户端浏览器可以的工作方式是:输入一个合法的url,能够显示从web服务器端发来的响应报文,它包括响应头及响应正文。我没有查阅相关资料来调用系统浏览器显示从服务器端发来的响应报文,故只实现终端显示,符合老师实验的基本要求,这样代码也会很简洁。
服务器在选择并发处理的技术上我选择的是多线程方式,因为它比用多进程方式占用的资源要少很多,由于linux下的线程和进程操作在学习操作系统时比较熟悉而其它的技术不太熟故并发处理技术我选择了多线程技术。
最后:
由于在进行综合实验时,我有问过好几个队,但他们都已经早商量好了队员,都达到了老师规定一个团队人数上限,又其它队自己不是很熟,不好意思开口。当我仔细阅读完老师的实验要求时,我对自己完成这个实验很有信心,就决定自己一个人一组。
实验功能简单分析
首先:
我发现要完成这个综合实验肯定要在linux环境下实现经典的socket通信,要有客户端、服务器,两者还要能很好地通信。(说这些似多余)
其次:
由于http服务器要为多个客户端服务,故其势必要支持并发处理,这就要求服务器端要实现多线程。(我选的是多线程并发)
再次:
因为做的是浏览器和http服务器,故在通信过程中是一定要支持http协议的,在socket通信时是要考虑如何编写http协议。(浏览器和服务器都要支持http协议,能正确发送http报文)
再者:
服务器端虽然存有html文件,但客户端访问时肯定要实现服务器先从文件中读取已有的文件然后传送给浏览器,这就要求服务器有读写文件的功能。(服务器端要能保存访问者信息也要求读写文件)
最后:
浏览器因为要能输入一个url后就能访问特定资源,因此浏览器要能判别一个输入是不是合法的url,继而以此访问服务器。
下面是无论是浏览器还是服务器都会用到的完成读写功能的filerw.c:
#include
#include
#include"filerw.h"
FILE* openfile(const char* filename, const char* mode){
FILE* fp = fopen(filename, mode);
if (!fp){
printf("openfile:failed in open ile%s.\n", filename);
exit(1);
}
return fp;
}
void readfile(FILE* fp, char cha[], unsigned int n){
unsigned int i = 0;
while (i
接下来,是 服务器端主要文件server.c:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "server.h"
#include
#define MAXDATASIZE 400//定义最大能发送正文的大小
#define BACKLOG 5//定义最大能监听线程数量
#define PORT 1234//定义的监听端口
struct BUFFER budata[3];//缓存结构,用于保存文件提高访问速度
//18-22所有资源
char source1[] = "test.html";
char source2[] = "hello.html";
char source3[] = "index.html";
char isright[] = " 200 ok";///访问成功一字段
char notfound[] = " 404 Not Found";///访问失败一字段
pthread_mutex_t work_mutex;//声明互斥锁
int main(void){
int listenfd, connfd;
pthread_t tid;
struct ARG *arg;
struct sockaddr_in server, client;
socklen_t len;
int result = pthread_mutex_init(&work_mutex, NULL);//初始化互斥锁
if (result != 0){
perror("pthread_mutex_init fault.");
exit(1);
}
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){//建立socket
perror("Creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1){//绑定端口
perror("Bind() error.");
exit(1);
}
if (listen(listenfd, BACKLOG) == -1){//监听
perror("listen() error\n");
exit(1);
}
len = sizeof(client);
while (1){
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1){//接受连接请求
perror("accept() error\n");
exit(1);
}
//54-55是获得当前系统时间
char stime[30];
gettime(stime);
char backgethere[550] = "HTTP/1.1";///
arg = (struct ARG *)malloc(sizeof(struct ARG));//为线程分配内存空间
arg->connfd = connfd;///56-60都是要传递给线程处理函数的参数对应赋值
arg->backget = backgethere;///
arg->systime = stime;///
//printf("systime:%s",arg->systime);///打印系统当前时间也就是访问时间
memcpy((void *)&arg->client, &client, sizeof(client));
if (pthread_create(&tid, NULL, function, (void*)arg)){//建立一个线程处理连接请求
perror("Pthread_create() error");
exit(1);
}
}
close(listenfd);//连接达到最大数目时关闭监听
pthread_mutex_destroy(&work_mutex);//销毁互斥锁
}//main函数结束
void* function(void* arg){//每个线程都调用的方法
struct ARG *info;
info = (struct ARG *)arg;
process_cli(info->connfd, info->client, info->backget, info->systime);//调用专用应对函数完成通信任务
free(arg);//释放线程占用内存
pthread_exit(NULL);//线程退出
}//接口函数结束
void process_cli(int connfd, struct sockaddr_in client, char* backget, char* systime){
char rooturl[40] = "/home/stu/test_end13_04_21_17/";//访问资源前面的相同部分路径
char* allsource[3];
char source[20];///
char* sourceaddr = source;///
int num, index, inde = 0;
char recvbuf[MAXDATASIZE];
char data[MAXDATASIZE];
char* ipaddr = inet_ntoa(client.sin_addr);///访问ip地址
FILE* fp;
FILE* fpjl;
allsource[0] = source1;
allsource[1] = source2;
allsource[2] = source3;
printf("you got a connection from:\n%s\n", ipaddr);
num = recv(connfd, recvbuf, MAXDATASIZE, 0);//获取浏览器发来的信息
recvbuf[num] = '\0';
printf("recevied message:\n%s\n", recvbuf);//打印浏览器请求信息
urlsource(recvbuf, sourceaddr);///获取请求中的资源路径
printf("the url source addr is:\n%s\n", source);///显示请求的资源
pthread_mutex_lock(&work_mutex);//有写操作加锁
fpjl = openfile("/home/stu/test_end13_04_21_17/visiter.data", "a");
writefile(fpjl, ipaddr, systime, source);保存访客信息到文件中,该处在并行访问时要互斥访问
pthread_mutex_unlock(&work_mutex);//写完解锁
for (index = 0; index<3; index++){
if (strcmp(allsource[index], source) == 0){
inde++;
if (budata[index].id == 1){//已有线程访问访问过了,现在从缓存中读数据
//printf("??buffer data:\n%s\n",budata[index].bufferdata);///??
GETback(isright, strlen(budata[index].bufferdata), budata[index].bufferdata, backget);
//printf("??backget:\n%s\n",backget);///打印服务器找到资源时发给浏览器的响应信息??
send(connfd, backget, strlen(backget), 0);//找到要求资源时给浏览器发送的信息
printf("the use message come from buffer##\n");///??
break;
}
//该资源还未被线程访问的情况117-126
budata[index].id = 1;///标记缓存中有该资源了
strcat(rooturl, source);路径拼接得到要访问文件的路径
fp = openfile(rooturl, "r");
readfile(fp, data, MAXDATASIZE);//首次访问从文件中读取资源
data[MAXDATASIZE] = '\0';
strcpy(budata[index].bufferdata, data);///资源存入缓存
//printf("##budata:\n%s\n",budata[index].bufferdata);///打印服务器找到资源时发给浏览器的响应信息
GETback(isright, strlen(data), data, backget);//
send(connfd, backget, strlen(backget), 0);//找到要求资源时给浏览器发送的信息
//printf("backget:\n%s\n",backget);///打印服务器找到资源时发给浏览器的响应信息
printf("the first use the message!!\n");
break;
}
}
if (inde == 0){
strcat(backget, notfound);
strcat(backget, "\r\n\r\n404 FILE NOT FOUND\n");///
send(connfd, backget, strlen(backget), 0);//未发现要求的资源时给浏览器发送的信息
//printf("backget:\n%s\n",backget);///打印服务器未找到资源时发给浏览器的信息
}
close(connfd);//关闭连接
}//线程调用函数结束
//服务器发给浏览器的响应文件处理
void GETback(char* isright, int length, char* data, char* backget){
char buffer[10];
char systime[30];
gettime(systime);
sprintf(buffer, "%d", length); //将数字转换字符串
char cha1[] = "\r\nDate:";
char cha2[] = "\r\nServer: win_web_server/0.1\r\nContent-Length: ";
char cha3[] = "\r\nContent-Type: text/html;charset=utf-8\r\nCache-Control: private\r\nExpires: ";
char cha4[] = "\r\nConnection: Keep-Alive\r\n\r\n";
strcat(backget, isright);
strcat(backget, cha1);
strcat(backget, systime);
strcat(backget, cha2);
strcat(backget, buffer);
strcat(backget, cha3);
strcat(backget, cha4);
strcat(backget, data);
//printf("The back getfunction is:\n%s\n",backget);打印响应信息
}//响应报头结束
void urlsource(char* getstr, char* sourceaddr){//获取请求中请求资源的路径
while (1){
if (*getstr == '/')
{
getstr++;
while (1){
if (*getstr == ' ')
break;
*sourceaddr = *getstr;
sourceaddr++;
getstr++;
}
break;
}
getstr++;
}
}//获取请求资源路径函数结束
void gettime(char* stime){//获取系统当前时间
time_t timep;
char* weizhi;
time(&timep);
strcpy(stime, ctime(&timep));
weizhi = strchr(stime, '\n');//获得时间字符串中\n位置
*weizhi = '\0';///将换行的\n换成结束符\0
}//获取系统时间函数结束
最后,是 客户端主要文件browser.c:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "browser.h"
#define MAXDATASIZE 600
#define RECIVEDATA 2000
#define MAXADDR 45
int main(void)
{
int sockfd;
char ch;
int i = 0;
struct sockaddr_in server;//18-38建立与服务器的连接
FILE* fp;
while (1){
char chha[MAXADDR];//用以保存uml的字符串
while ((ch = getchar()) != '\n'){
chha[i] = ch;
i++;
}//输入uml结束
chha[i] = '\0';
i = 0;
if (chha[0] == 'q')
break;//退出浏览器命令
else if (chha[0] == 'l'){
fp = openfile("/home/stu/test_end13_04_21_17/getsource.data", "r");
char cha[MAXDATASIZE];
readfile(fp, cha, MAXDATASIZE);
cha[MAXDATASIZE] = '\0';
printf("\nthe history of getsource:\n%s\n", cha);
}
else{
int index, indexs, indext = 0, indexf, indexfi = 0, indexsi;
char endget[300] = "GET /";//不断的给它添加尾字段后形成请求头
char straddr[sizeof(chha)];//保存ip地址
char strport[6] = "$$$$$";//5个$加一个\0
int PORT = 0;//保存端口号
char portal[6];//保存协议类型
char http[] = "http:";
char strneed[sizeof(chha)];//保存获取资源路径
printf("the straddr is:%s\n", chha);
for (index = 0; index
但这三个文件就这样使用是不行的,需要各种头文件,完整的工程代码:点我点我,如果有需要的,可以下载。