结束网络编程_第24章制作 HTTP 服务器端

在这里插入图片描述

24.1 HTTP 概要

本章以编写真实应用程序为目标, 在所学理论知识的基础上, 编写 HTTP (Hypertext Transfer Protocol, 超文本传输协议) 服务器端, 即 Web 服务器端.

理解 Web 服务器端

互联网是普及使 Web 服务器端为大众熟知. 下面是我对 Web 服务器端的定义:
在这里插入图片描述
HTTP 是 Hypettext Transfer Protocol 的缩写, Hypertext (超文本) 是可以根据客户端请求而跳转的结构化信息. 例如, 各位通过浏览器访问图灵社区的主页是, 首页文件将传输到浏览器并展示给大家, 此时各位点击鼠标跳转到任意页面. 这种可以跳转的文本( Text ) 称为超文本.

HTTP 协议又是什么呢? HTTP协议是以超文本传输为目的而设计的应用层协议, 这种协议同样属于基于 TCP/IP 实现的协议, 因此, 我们也可以直接实现 HTTP. 从结果上看, 实现该协议相当于实现 Web 服务器端. 另外, 浏览器也属于基于套接字的客户端, 因为连接到任意 Web 服务器端, 另外, 浏览器也属于基于套接字的客户端, 因为连接到任意 Web 服务器端时, 浏览器内部也会创建套机字. 只不过浏览器多了一项功能, 它将服务器端传输的 HTML 格式的超文本解析为可读较强的视图. 总之, Web 服务器端是以 HTTP 协议为基础传输超文本的服务器端.
在这里插入图片描述

HTTP

下面详细讨论 HTTP 协议. 虽然它相对简单, 但要完全驾驭也并非易事. 接下来只介绍编写
Web 服务器端时的必要内容.

无状态的 Stateless 协议

为了在网络环境下同时向大量客户端提供服务, HTTP 协议的请求及相应方式设计如图 24-1 所示.
在这里插入图片描述
从图 24-1 中可以看到, 服务端相应客户端请求后立即断开连接. 换言之, 服务器端, 不会维持客户端状态. 即使同一客户端再次发送请求, 服务器端也无法辩别认出原先那个, 而会以相同方式处理请求. 因此, HTPP 又称为 “无状态的 Stateless 协议”.
在这里插入图片描述

请求消息 (Request Message) 的 结构

下面介绍客户端向服务器端发送请求消失的结构. Web 服务器需要解析并相应客户端请求, 客户端和服务器端之间的数据请求方式标准如图 24-22 所示.
在这里插入图片描述
从图 24-2 中可以看到, 请求消息 可以分为行, 消息头, 消息体等3部分. 其中 , 请求行含有 方式(请求目的) 信息. 经典请求方式有GET 和 POST, GET 主要用于请求数据, POST 主要 用于传输数据. 为了降低复杂度, 我们实现只能相应 GET 请求 Web 服务器端. 下面解析图 24-2 中的请求行信息 . 其中 “GET/index.html HTTP/1.1” 具有如下含义:
在这里插入图片描述
请求行只能通过一行 (line) 发送, 因此, 服务器端很容易从HTTP请求中提取第一行, 并分析请求 行中的消息 .

请求行只能通过 1 行 (line) 发送, 因此, 服务器端很容易从HTTP请求中提取 第一行, 并分析请求行中的信息 .

请求行下面的消息头中包含发送 请求的 (将要接收相应 消息的) 浏览器信息 , 用户认证信息 等关于 HTTP 消息的附加信息 . 最后的消息体中装有 客户端向服务器端传输数据, 为了装入数据, 需要以 POST 方式发送请求. 但我们的目的是实现 GET 方式的服务器端, 所以可以忽略这部分内容. 另外, 消息体和消息头之间以空格分开, 因此不会发生边界问题.

相应消息 (Response Message) 的结构

下面介绍 Web 服务器端向客户端传递的相应信息的结构. 从图 24–3 中 可以看到, 该相应消息 由状态行 头信息 , 消息体灯等 3部分 构成. 状态 行中含有 关于请求 的状态信息 , 这 是其与 请求消息相比最为显著的区别.
在这里插入图片描述
从图24-3中可以看到, 第一个字符串状态行中含有关于客户端请求处理结果. 例如, 客户端请求 index.html 文件时, 表示 index.html 文件是否存在, 服务器端是否发生问题而无法响应等不同情况的信息将写入状态行. 图24-3中的"HTTP/1.1 200 OK" 具体如下含义:
在这里插入图片描述
表示 “客户端请求的执行结果” 的数字称为 状态码, 典型的有如下几种.
在这里插入图片描述
消息头中含有传输的数据类型和长度等信息. 图24-3中的消息头含有如下信息:
在这里插入图片描述
最后插入 1 个空行后, 通过消息体发送客户端请求的文件数据. 以上就是实现 Web 服务器端过程中必要的 HTTP 协议. 要编写完整的 Web 服务器端还需要更多 HTTP 协议相关知识, 而对于我们的目标而言, 这些内容已经够了.

24.2 实现简单的 Web 服务器端
现在开始在 HTTP 协议 的基础上编写 Web 服务器端. 先给出 Windows 平台下的示例, 再给出 Linux 下的示例. 前面介绍了 HTTP 协议的相关背景知识, 有了这些基础就不难分析源代码. 因此, 除了一些简单的注释外, 不再另行说明代码.

实现基于 Windows 的多线程 Web 服务器端

Web 服务器端采用 HTTP 协议, 即使用 IOCP 或 epoll 模型也不会大幅度提升性能(当然并不是完全没有). 客户端和服务器端交换 1 次数据后将立即断开连接, 没有足够时间发挥 IOCP 或 epoll 的优势. 在服务器端和客户端保持较长连接的前提下 频繁发送大小不一的消息时(最典型的就是网游服务器端), 才嫩真正发挥出这2中模型的优势.
在这里插入图片描述
因此, 我通过 多线程 模型 实现 服务端 Web 服务器端. 也就是说, 客户端每次请求时, 都会创建一个新的线程相应客户端请求.
在这里插入图片描述

#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <WinSock2.h>
#include <process.h>

#define BUF_SIZE 2048
#define BUF_SMALL 100

unsigned WINAPI RequestHandler(void* arg);
const char* ContentType(char* file);
void SendData(SOCKET sock, char* ct, char* fileName);
void SendErrorMSG(SOCKET sock);
void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAdr, clntAdr;

	HANDLE hThread;
	DWORD dwThreadID;
	int clntAdrSize;

	if (argc != 2)
	{
		printf("Usage : %s <port> \n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error");
	}

	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hServSock == SOCKET_ERROR)
	{
		ErrorHandling("socket() error");
	}
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
	{
		ErrorHandling("bind() error");
	}

	if (listen(hServSock, 5) == SOCKET_ERROR)
	{
		ErrorHandling("listen() error");
	}

	/* 请求及响应 */
	while (1)
	{
		clntAdrSize = sizeof(clntAdr);
		hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSize);
		printf("Connection Request : %s : %d \n", 
			inet_ntoa(clntAdr.sin_addr), ntohs(clntAdr.sin_port));

		hThread = (HANDLE)_beginthreadex(
			NULL, 0, RequestHandler, (void*)hClntSock, 0, (unsigned*)&dwThreadID);
	}

	closesocket(hServSock);
	WSACleanup();
	return 0;
}


unsigned WINAPI RequestHandler(void* arg)
{
	SOCKET hClntSock = (SOCKET)arg;
	char buf[BUF_SIZE];
	char method[BUF_SMALL];
	char ct[BUF_SMALL];
	char fileName[BUF_SMALL];

	recv(hClntSock, buf, BUF_SIZE, 0);
	if (strstr(buf, "HTTP/") == NULL) /* strstr(在一字符串中查找指定的字符串) 查看是否为HTTP提出的请求 */
	{
		SendErrorMSG(hClntSock);
		closesocket(hClntSock);
		return 1;
	}

	strcpy(method, strtok(buf, " /")); /* strtok(分割字符串) */
	if (strcmp(method, "GET")) /* 查看是否为GET方式的请求 */
	{
		SendErrorMSG(hClntSock);
	}

	strcpy(fileName, strtok(NULL, " /")); /* 查看请求文件名 */
	strcpy(ct, ContentType(fileName)); /* 查看Content-type */
	SendData(hClntSock, ct, fileName); /* 响应 */
	return 0;
}

const char* ContentType(char* file)
{
	char extension[BUF_SMALL];
	char fileName[BUF_SMALL];
	strcpy(fileName, file);
	strtok(fileName, ".");
	strcpy(extension, strtok(NULL, "."));
	if (!strcmp(extension, "html") || !strcmp(extension, "htm"))
	{
		return "text/html";
	}
	else
	{
		return "text/plain";
	}
}

void SendData(SOCKET sock, char* ct, char* fileName)
{
	char protocol[] = "HTTP/1.0 200 OK\r\n";
	char servName[] = "Server : simple web server\r\n";
	char cntLen[] = "Content-length:2048\r\n";
	char cntType[BUF_SMALL];
	char buf[BUF_SIZE];
	FILE* sendFile;

	sprintf(cntType, "Content-type:%s\r\n\r\n", ct);
	if ((sendFile = fopen(fileName, "r")) == NULL)
	{
		SendErrorMSG(sock);
		return;
	}

	/* 传输头信息 */
	send(sock, protocol, strlen(protocol), 0);
	send(sock, servName, strlen(servName), 0);
	send(sock, cntLen, strlen(cntLen), 0);
	send(sock, cntType, strlen(cntType), 0);

	/* 传输请求数据 */
	while (fgets(buf, BUF_SIZE, sendFile) != NULL)
	{
		send(sock, buf, strlen(buf), 0);
	}

	closesocket(sock); /* 由HTTP协议响应后断开 */
}

void SendErrorMSG(SOCKET sock)
{
	char protocol[] = "HTTP/1.0 400 Bad Request\r\n";
	char servName[] = "Server:simple web server\r\n";
	char cntLen[] = "Content-length:2048\r\n";
	char cntType[] = "Content-type:text/html\r\n\r\n";
	char content[] = "<html><head><title>NETWORK</title></heand>"
		"<body><font size=+5><br> 发生错误! 查看请求文件名和请求方式! "
		"</font></boy></html>";

	send(sock, protocol, strlen(protocol), 0);
	send(sock, servName, strlen(servName), 0);
	send(sock, cntLen, strlen(cntLen), 0);
	send(sock, cntType, strlen(cntType), 0);

	closesocket(sock);
}

void ErrorHandling(const char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

运行结果:
在这里插入图片描述
在这里插入图片描述

实现基于 Linux 的多线程 Web 服务器端

Linux 下的 Web 服务器端与上述示例不同, 将使用标准 I/O 函数. 在此列出的目的的主要是为了让各位多复习各种知识点, 没有任何特殊含义.
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUF_SIZE 1024
#define SMALL_BUF 100

void *request_handler(void *arg);
void send_data(FILE *fp, char *ct, char *file_name);
char *content_type(char *file);
void send_error(FILE* fp);
void error_hangling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    int clnt_adr_size;
    char buf[BUF_SIZE];
    pthread_t t_id;
    if (argc != 2)
    {
        printf("Usage : %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    {
        error_hangling("listen() error");
    }

    if (listen(serv_sock, 20) == -1)
    {
        error_hangling("listen() error");
    }

    while (1)
    {
        clnt_adr_size = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_sock, &clnt_adr_size);
        printf("Connection Request : %s:%d\n", inet_ntoa(clnt_adr.sin_addr), ntohs(clnt_adr.sin_port));
        pthread_create(&t_id, NULL, request_handler, &clnt_sock);
        pthread_detach(t_id);
    }
    
    close(serv_sock);
    return 0;
}

void *request_handler(void *arg)
{
    int clnt_sock = *((int *)arg);
    char req_line[SMALL_BUF];
    FILE* clnt_read;
    FILE* clnt_write;

    char method[10];
    char ct[15];
    char file_name[30];

    clnt_read = fdopen(clnt_sock, "r");
    clnt_write = fdopen(dup(clnt_sock), "w");
    fgets(req_line, SMALL_BUF, clnt_read);
    if (strstr(req_line, "HTTP/") == NULL)
    {
        send_error(clnt_write);
        fclose(clnt_read);
        fclose(clnt_write);
    }

    strcpy(method, strtok(req_line, " /"));
    strcpy(file_name, strtok(NULL, " /"));
    strcpy(ct, content_type(file_name));
    if (strcmp(method, "GET") != 0)
    {
        send_error(clnt_write);
        fclose(clnt_read);
        fclose(clnt_write);
        return;
    }

    fclose(clnt_read);
    send_data(clnt_write, ct, file_name);
}

void send_data(FILE *fp, char *ct, char *file_name)
{
    char protocol[] = "HTTP/1.0 200 OK\r\n";
    char server[] = "Server:Linux Web Server \r\n";
    char cnt_len[] = "Content-length: 2048\r\n";
    char cnt_type[SMALL_BUF];
    char buf[BUF_SIZE];
    FILE* send_file;

    sprintf(cnt_type, "Content-type:%s\r\n\r\n", ct);
    send_file = fopen(file_name, "r");
    if (send_file == NULL)
    {
        send_error(fp);
        return;
    }

    /* 传输头文件信息 */
    fputs(protocol, fp);
    fputs(server, fp);
    fputs(cnt_len, fp);
    fputs(cnt_type, fp);

    /* 传输请求数据 */
    while (fgets(buf, BUF_SIZE, send_file) != NULL)
    {
        fputs(buf, fp);
        fflush(fp);
    }

    fflush(fp);
    fclose(fp);
}
char *content_type(char *file)
{
    char extension[SMALL_BUF];
    char file_name[SMALL_BUF];
    strcpy(file_name, file);
    strtok(file_name, ".");
    strcpy(extension, strtok(NULL, "."));

    if (!strcmp(extension, "html") || !strcmp(extension, "htm"))
    {
        return "text/html";
    }
    else
    {
        return "text/plain";
    }
    
}
void send_error(FILE* fp)
{
    char protocol[] = "HTTP/1.0 400 Bad Request\r\n";
    char server[] = "Server:Linux Web Server \r\n";
    char cnt_len[] = "Content-length:2048\r\n";
    char cnt_type[] = "Content-type:text/html\r\n\r\n";
    char content[] = "<html><head><title>NETWORK</title></head>"
    "<body><font size=+5><br>发生错误! 查看请求文件名和连接请求方式! "
    "</font></body></html>";

    fputs(protocol, fp);
    fputs(server, fp);
    fputs(cnt_len, fp);
    fputs(cnt_type, fp);
    fflush(fp);
}

void error_hangling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

运行结果失败, 无法访问, 不知原因, 等老师教到, 快了,

结语:

我最近 买了实体书 , 先看完电子版(先过一遍知识点, 我没有这么牛逼能记住, 可以复习的嘛! ), 再买实体版 , 避免它又成为收藏书没啥用, 这本书非常适合新手

你可以下面这个网站下载这本书<TCP/IP网络编程>
https://www.jiumodiary.com/

时间: 2020-06-23

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值