Web服务器的C语言实现

在输入网址后,浏览器做的事情

1、域名解析

2、三次握手建立连接

3、浏览器给web服务器发送请求报头

4、web服务器给浏览器回复应答报头和应答数据(html页面)

长连接:当关闭浏览器后才执行四次挥手

短连接:每发一次数据就三次挥手和四次握手一次

使用Linux来模拟web服务器

得使用root用户,因为端口80是属于系统的端口号,0-1023是系统预留端口。

web服务器的C语言实现

因为是TCP服务器首先得

初始化socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int InitSocket()
{
//创建套接字
int sockfd=socket(AF_INET, SOCK_STREAM, 0);
if (sockfd==-1)//防止失败,不用在子函数里面断言,这三个方法都可能失败,直接在main函数里面断言
return -1;
//对套接字进行命名
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(80); // root用户下运行
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&ser, sizeof(ser));
if (res==-1)
return -1;
//创建监听队列,在内核创建的监听队列,5是可以维护已完成三次握手的队列最大数目。
res=listen(sockfd,5);
if (res==-1)
return -1;
return sockfd;
}

获取连接

1
2
3
4
5
6
7
8
9
struct sockaddr_in cli;
socklen_t len=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)&cli,&len);
if(c<0)//如果小于0,连接失败
{
printf("accept error");
close(c);
continue;
}

接受数据(接受请求报文段)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int RecvData(int c, char *file_name)
{
char buff[1024] = {0};
int n=recv(c,buff,1023,0);
if (n<=0)//接受失败
return -1;
printf("%s\n",buff);//把接受数据打印一下
//根据HTTP的请求报头结构,获取index.html
//报头结构看上个blog
char *p=strtok(buff," "); // p指向GET,遇到空格结束切割
p=strtok(NULL," "); // p指向"/index.html"

char path[128]="/var/www/html";
// 给file_name中填充的是系统上肯定存在的文件
//path:"/var/www/html/index.html"
strcat(path, p);
// open只是用于检测文件是否存在
int fd=open(path, O_RDONLY);
//没找到就返回404这个文件,404表示没找到资源
if (fd == -1)
strcpy(file_name, "/var/www/html/404.html");
else
{
strcpy(file_name, path);
close(fd);
}

return 0;
}

发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//发送头部信息
void SendHead(int c,int flag,int size)
{
char buff[1024]={0};
//http协议版本
strcat(buff,"HTTP/1.0");
// 状态码
if (flag)
strcat(buff,"404 Not Found\r\n");
else
strcat(buff,"200 OK\r\n");
// 服务器名称
strcat(buff,"Server: MYWEB/1.0\r\n");
// 数据长度
strcat(buff,"Content-Length: ");
sprintf(buff+strlen(buff),"%d",size);
strcat(buff,"\r\n");
strcat(buff,"Content-Type: text/html; charset=utf-8\r\n");
strcat(buff,"\r\n");

send(c,buff,strlen(buff),0);
}

int SendData(int c, char *file_name)
{
// 1.给浏览器发送头部结构,头部结构见上个博客
// 2.给浏览器发送页面数据 file_name文件中的内容
int flag=0;//看到底是什么状态,保存状态信息
if (strstr(file_name,"404.html")!=NULL)
flag=1;
struct stat st;
stat(file_name,&st);//stat获取文件属性信息
SendHead(c,flag,st.st_size); // sockfd 状态信息 数据长度
int fd=open(file_name,O_RDONLY);
while(1)
{
char buff[128]={0};//每次读取128个数据给客户端发送过去
int n=read(fd,buff,127);
if (n<=0)
break;
send(c,buff,n,0);
if (n<=0)
return -1;
}
close(fd);
return 0;
}

整体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/stat.h>
int InitSocket()
{
//创建套接字
int sockfd=socket(AF_INET, SOCK_STREAM, 0);
if (sockfd==-1)//防止失败,不用在子函数里面断言,这三个方法都可能失败,直接在main函数里面断言
return -1;
//对套接字进行命名
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(80); // root用户下运行
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&ser, sizeof(ser));
if (res==-1)
return -1;
//创建监听队列,在内核创建的监听队列,5是可以维护已完成三次握手的队列最大数目。
res=listen(sockfd,5);
if (res==-1)
return -1;
return sockfd;
}
int RecvData(int c, char *file_name)
{
char buff[1024] = {0};
int n=recv(c,buff,1023,0);
if (n<=0)//接受失败
return -1;
printf("%s\n",buff);//把接受数据打印一下
//根据HTTP的请求报头结构,获取index.html
//报头结构看上个blog
char *p=strtok(buff," "); // p指向GET,遇到空格结束切割
p=strtok(NULL," "); // p指向"/index.html"

char path[128]="/var/www/html";
// 给file_name中填充的是系统上肯定存在的文件
//path:"/var/www/html/index.html"
strcat(path, p);
// open只是用于检测文件是否存在
int fd=open(path, O_RDONLY);
//没找到就返回404这个文件,404表示没找到资源
if (fd == -1)
strcpy(file_name, "/var/www/html/404.html");
else
{
strcpy(file_name, path);
close(fd);
}

return 0;
}
//发送头部信息
void SendHead(int c,int flag,int size)
{
char buff[1024]={0};
//http协议版本
strcat(buff,"HTTP/1.0");
// 状态码
if (flag)
strcat(buff,"404 Not Found\r\n");
else
strcat(buff,"200 OK\r\n");
// 服务器名称
strcat(buff,"Server: MYWEB/1.0\r\n");
// 数据长度
strcat(buff,"Content-Length: ");
sprintf(buff+strlen(buff),"%d",size);
strcat(buff,"\r\n");
strcat(buff,"Content-Type: text/html; charset=utf-8\r\n");
strcat(buff,"\r\n");

send(c,buff,strlen(buff),0);
}

int SendData(int c, char *file_name)
{
// 1.给浏览器发送头部结构,头部结构见上个博客
// 2.给浏览器发送页面数据 file_name文件中的内容
int flag=0;//看到底是什么状态,保存状态信息
if (strstr(file_name,"404.html")!=NULL)
flag=1;
struct stat st;
stat(file_name,&st);//stat获取文件属性信息
SendHead(c,flag,st.st_size); // sockfd 状态信息 数据长度
int fd=open(file_name,O_RDONLY);
while(1)
{
char buff[128]={0};//每次读取128个数据给客户端发送过去
int n=read(fd,buff,127);
if (n<=0)
break;
send(c,buff,n,0);
if (n<=0)
return -1;
}
close(fd);
return 0;
}
int main()
{
int sockfd=InitSocket();
assert(sockfd!=-1);
while (1)
{
struct sockaddr_in cli;
socklen_t len=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)&cli,&len);
if (c<0)
{
printf("accept error");
close(c);
continue;
}
char file_name[128]={0};
int num=RecvData(c,file_name);
if (num==-1)
{
close(c);
continue;
}
num=SendData(c,file_name);
if (num==-1)
{
close(c);
continue;
}
close(c); // 短链接方式
}
close(sockfd);
exit(0);
}

测试

首先运行编译后的程序

然后开个浏览器,输入”127.0.0.1/index.html”

我这的浏览器是空白是因为我自己的index.html文件里面是空白,没有自己写,如果自己写一点前端的东西,浏览器也会显示,并且web服务器在收到报头之后打印了收到的报文段