Socket: HTTP Server

玩socket第三篇,使用TCP模拟简单的HTTP服务。

HTTP

即超文本传输协议,其实就是一种通过TCP发送和接收信息的格式。如果服务端和客户端都按照这个格式来交换信息,它就变成了两者间的一种协议,也可以看作一种标准。

例如请求头:

GET / HTTP/1.1
Host: www.google.com

第一行先是指定请求方法,然后指明请求的资源路径,再说明所用的HTTP协议版本;
第二行表示向指定主机发出请求。

响应头:

HTTP/1.1 200 OK
Content-Length: 3059
Server: GWS/2.0
Date: Sat, 11 Jan 2003 02:44:04 GMT
Content-Type: text/html
Cache-control: private
Set-Cookie: PREF=ID=73d4aef52e57bae9:TM=1042253044:LM=1042253044:S=SMCc_HRPCQiqy
X9j; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Connection: keep-alive

第一行是先说明所用的HTTP协议版本,再显示响应的状态,下面则是响应的信息。
响应头之后紧跟着一个空行,然后由HTML格式的文本组成了实际请求的内容。

参考:维基百科:超文本传输协议

原理

浏览器会按照HTTP协议发送和接收内容。我们在服务端接收到浏览器的请求时,手动构建一个符合HTTP响应头的字符串,然后使用TCP方法返回给浏览器,浏览器就可以正确显示我们返回的内容。
这里的服务端当获取到对“/”或者是“/index.html”的请求时,就以合适的响应头返回当前目录下index.html内的内容。否则返回404 Not Found的状态码。

代码

服务端:

/*************************************************************************
    > File Name: server.cpp
    > Author: nian
    > Blog: https://whoisnian.com
    > Mail: [email protected]
    > Created Time: 2017年08月24日 星期四 23时36分28秒
 ************************************************************************/
#include<cstdio>
#include<cstring>
#include<ctime>

//引入头文件
#include<sys/types.h> 
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

#include<sys/stat.h>
#include<fcntl.h>

//设置服务器监听端口号
const unsigned long port = 80;

int main(void)
{
	//创建服务器端socket,地址族为AF_INET(IPv4),传输方式为TCP
	int server_socket;
	server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//初始化监听IP为本地所有IP,端口为已设置的port
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
	
	//绑定socket与IP和端口
	bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

	//使socket进入监听模式
	listen(server_socket, 10);
	
	while(1)
	{
		//接收客户端请求
		int client_socket;
		struct sockaddr_in client_addr;
		socklen_t client_addr_len;
		client_addr_len = sizeof(client_addr);
		client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);

		//获取浏览器发送的请求
		char request[1000];
		read(client_socket, request, sizeof(request));
		printf("%s\n", inet_ntoa(client_addr.sin_addr));

		//获取当前时间
		time_t Time;
		char time_of_now[50];
		time(&Time);
		strftime(time_of_now, 50, "%a, %d %b %Y %H:%I:%S %Z", gmtime(&Time));
		
		char address[500];
		sscanf(request, "%*s%s", address);
		//如果不是请求”/“或”/index.html“,返回404
		if(strcmp(address, "/")&&strcmp(address, "/index.html"))
		{
			//初始化响应
			char response[100];
			memset(response, 0, sizeof(response));

			//设置响应头
			sprintf(response, "HTTP/1.1 404 Not Found\nDate: %s\nServer: C++ Server\n\n", time_of_now);

			//返回服务器响应
			write(client_socket, response, strlen(response));
		}
		//否则返回当前目录下index.html的内容
		else
		{
			//初始化响应
			char response[100000];
			char html[90000];
			memset(response, 0, sizeof(response));
			memset(html, 0, sizeof(html));

			//设置响应头
			sprintf(response, "HTTP/1.1 200 OK\nDate: %s\nServer: C++ Server\n\n", time_of_now);
		
			//设置返回的内容
			int fp = open("./index.html", O_RDONLY);
			read(fp, html, sizeof(html));
			close(fp);
			strcat(response, html);
		
			//返回服务器响应
			write(client_socket, response, strlen(response));
		}
		close(client_socket);
	}
	close(server_socket);
	return 0;
}

当前目录下index.html示例:

<html>
  <head>
	<title>
	  Permission denied
	</title>
  </head>

  <body>
	<br/><br/>
	<h1 style="font-family:verdana;font-size:10em;text-align:center;color:#303F9F">
	  Permission denied
	</h1>
  </body>
</html>

注意