Linux下HTTP通信简析及实现HTTP服务器

在Linux下我们使用HTTP协议进行通讯,即客户端在浏览器请求服务器的某个服务(页面)。我们在主机上进行测试的时候,可以开启httpd服务以处理客户端的请求,一般来说我们的页面放在/var/www/html底下,我们这些操作需要切换到root用户。

开启httpd服务  :  service  httpd  start

查看httpd状态  :  service  httpd  status

关闭httpd服务  :  service  httpd  stop

Linux下HTTP通信简析及实现HTTP服务器_第1张图片

可以看出我不在root用户是开启不了httpd服务滴,所以我们切换到root用户在开启一次。

现在httpd服务已经开启了,我们再来看看我的/var/www/html底下我自己写了一个静态的html页面

我们接下来用火狐浏览器请求一下我主机上的这个index.html页面,看看会不会将页面给我返回。

Linux下HTTP通信简析及实现HTTP服务器_第2张图片

可以看出httpd将我们请求的这个index页面给我返回了,那么关于httpd这个服务器怎么实现呢?我们一起来看看

HTTP服务器解析

我们知道http是应用层的协议,它默认使用的传输层协议是tcp,现在我们来模拟一下http的服务器书写。

首先应注意的是TCP连接从建立到关闭的过程中,客户端(浏览器)仅给服务器发送了一个HTTP请求(比较小),而服务器会将客户端请求的页面响应传给客户端:,在浏览器上显示)

HTTP请求

Linux下HTTP通信简析及实现HTTP服务器_第3张图片

第一行是请求行,剩下这2-4行是请求头部。

Linux下HTTP通信简析及实现HTTP服务器_第4张图片

1:请求行

由3部分组成,分别为:请求方法、URL(网址)以及协议版本,之间由空格分隔。

其中GET是请求方法,表示客户端以只读的方式申请资源。常见的HTTP请求方法如下:

Linux下HTTP通信简析及实现HTTP服务器_第5张图片

上面的http://www.baidu.com/index.html即是目标资源的URL ,http是获取目标资源的应用层协议,其他常见的协议还有ftp,file等。www.baidu.com指定了目标资源所在的主机(DNS解析获取IP),index.html即资源文件名称。

HTTP/1.0表示客户端使用的HTTP版本号是1.0,目前的HTTP主流版本是1.1

2:请求头部

请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔

常见请求头如下:

 头部字段名                                        说明
     Host 接受请求的服务器地址,可以是IP:端口号,也可以是域名
 User-Agent 发送请求的应用程序名称(客户端使用的程序)
  Connection 指定与连接相关的属性,如Keep-Alive(长连接)或者close(短连接)
Accept-Charset 通知服务端可以发送的编码格式
Accept-Encoding 通知服务端可以发送的数据压缩格式
Accept-Language 通知服务端可以发送的语言

 对于头部字段Connection有两种连接属性:长连接与短连接

短连接是指客户端和服务器之间的一个TCP连接只能为一个HTTP请求服务,处理完一个HTTP请求之后,服务器就主动将TCP连接关闭了,之后同一个客户端再发送一个HTTP请求就需要重新建立连接。长连接与之相反,指多个请求可以使用同一个连接。但是长连接编程上稍微复杂一点,但性能有了很大提高。

3:分界

在所有头部字段之后,HTTP请求必须包含一个空行,以标识头部字段的结束。空行必须只包含一个回车符与换行符,不能有其他字符,甚至是空白字符。

4:正文

在空行之后,HTTP请求可以包含可选的消息体,比如get请求就没有请求正文。但如果消息体非空,则HTTP请求报头字段中必须包含描述消息体长度的字段“Content-Length”。

HTTP应答

http应答的部分内容如下:

Linux下HTTP通信简析及实现HTTP服务器_第6张图片

第一行是状态行,其他2--7行是应答的头部字段

Linux下HTTP通信简析及实现HTTP服务器_第7张图片

1:状态行

由3部分组成,分别为:协议版本,状态码,状态信息,之间由空格分隔

状态代码为3位数字,200~299的状态码表示成功,300~399的状态码指资源重定向,400~499的状态码指客户端请求出错,500~599的状态码指服务端出错(HTTP/1.1向协议中引入了信息性状态码,范围为100~199)

Linux下HTTP通信简析及实现HTTP服务器_第8张图片

2:响应头部

与请求头部类似,响应报文添加了一些附加信息

响应头部字段 说明
Server 服务器应用程序软件的名称和版本
Content-Type 响应正文的类型(是图片还是二进制字符串)
Content-Length 响应正文长度
Content-Charset 响应正文使用的编码
Content-Encoding 响应正文使用的数据压缩格式
Content-Language 响应正文使用的语言

对于示例Content-Type:text/html;charset=gbk表示目标文档index.html是text类型中的html文档。charset是text文档类型的一个参数,用于指定文档的字符编码。 

示例的Cookie是服务器发送给客户端的一个特殊信息,客户端每次向服务器发送请求的时候都需要带上这些信息(通过请求字段“cookie”),这样服务器就可以区分不同的客户了。基于浏览器的自动登陆就是用cookie实现的(客户端会将cookie的信息保存在本机上,只需验证即可实现自动登陆)

3:与HTTP请求相同,头部字段完成之后也需要一个空行进行区分头部和响应正文。

4:响应正文

即发送客户端请求的页面内容,需要在头部中声明正文的长度

 简单仿写HTTP服务器

首先我们将我们服务器的端口号绑定为80/8080端口(浏览网页服务默认的端口号都是80/8080),那么我们首先要关闭httpd服务,否则就将端口号占了(root用户),接下来的操作代码中体现,话不多说上代码。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//仿写http服务器,需要关闭httpd以及使用root用户操作

void SendError(int c)  //发送错误页面(连带头部)
{
	char buff[1024]={0};     
	strcpy(buff,"HTTP/1.1 404 NOT FOUND\n\r");
	strcat(buff,"Server:myhttp/1.0\n\r");
	strcat(buff,"Content-Length: ");
	sprintf(buff+strlen(buff),"%d",0);
	strcat(buff,"\n\r");
	strcat(buff,"Content-Type:text/html;charset=utf-8\n\r");
	strcat(buff,"\n\r");   //空行标识数据部分和头部分开
	strcat(buff,"404 NOT FOUND");  //发送给客户端的数据,用于显示
	send(c,buff,strlen(buff),0);
}
void SendHead(int size,int c)  //发送给客户端应答报头
{
	char buff[1024]={0};
	strcpy(buff,"HTTP/1.1 200 OK\n\r");
	strcat(buff,"Server:myhttp/1.0\n\r");
	strcat(buff,"Content-Length: ");
	sprintf(buff+strlen(buff),"%d",size);
	strcat(buff," \n\r");
	strcat(buff,"Content-Type:text/html;charset=utf-8\n\r");
	strcat(buff,"\n\r");   //空行标识数据部分和头部分开
	send(c,buff,strlen(buff),0);
}
void AnayRequest(char *buff,char *pathname) //解析参数获取到请求的页面地址
{
	//GET  /index.html HTTP/1.1   Linux上获取到的请求第一行,并没有域名及IP
	char *p=strtok(buff," ");
	p=strtok(NULL," ");   //获取到了/index.html
	strcpy(pathname,"/var/www/html");  //请求的页面在本机的var/www/html底下
	strcat(pathname,p); //将分割的页面名称与之相连接,得到请求页面的绝对地址
}
void SendData(char *pathname,int c)  //发送页面
{
	struct stat st;
	if(-1!=stat(pathname,&st))  //通过路径获取文件属性
	{
		SendHead(st.st_size,c);  //发送应答报头,传入请求页面文件的大小
	}
	else
	{
		SendError(c);  //所有错误都发送这个404错误(只发送一句话而已)
		return;
	}
	int fd=open(pathname,O_RDONLY);//发送页面正文给客户端进行显示,即请求的页面
	if(fd==-1)
	{
		SendError(c);
		return;
	}
	while(1)
	{
		char buff[128]={0};
		int n=read(fd,buff,127);
		if(n<=0)
		{
			close(fd);
			break;
		}
		send(c,buff,strlen(buff),0); //发送页面内容给客户端
	}
}


int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);

	struct sockaddr_in ser,cli;
	ser.sin_family=AF_INET;
	ser.sin_port=htons(80);
	ser.sin_addr.s_addr=inet_addr("127.0.0.1");  //本地回环地址

	int res=bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
	assert(res==0);
	listen(sockfd,5);

	while(1)
	{
		int len=sizeof(cli);
		int c=accept(sockfd,(struct sockaddr*)&cli,&len);
		if(c<0)
			continue;
		char buff[1024]={0};   //短连接不需要循环读取
		int n=recv(c,buff,1023,0);
		if(n<=0)
		{
			close(c);
			continue;
		}
		printf("%s\n",buff);  //将客户端HTTP请求内容输出
		char pathname[128]={0};
		AnayRequest(buff,pathname);  //分析参数
		SendData(pathname,c);   //发送请求的html网页
		close(c);
	}
}

ps:Linux上HTTP请求报头的请求行与我上面解析的有点不同,URL直接是资源文件名称,没有主机名(IP或者域名) 

运行结果:

Linux下HTTP通信简析及实现HTTP服务器_第9张图片

右侧的代码是客户端的HTTP请求

我们再来测试一个不存在的网页,看看错误页面 

Linux下HTTP通信简析及实现HTTP服务器_第10张图片

可以看出我吧请求资源页面写错了,就会给我返回一个我服务器给客户端发送的错误输出404 NOT FOUND字样,就酱。

你可能感兴趣的:(Linux下HTTP通信简析及实现HTTP服务器)