C++ MyHttpServer网络服务器实验


C++课程老师把这一个实验分成了五个小实验,VS版本是2019的,涉及Http协议的请求处理、响应处理、多线程等,有一两个实验老师的示例代码很精简、很amazing,所以里面有些代码就没有拿我自己写的。做完这个服务器的实验,我对网络编程有了不一样的理解,觉得很棒(给老师打call),而且在做实验的过程中遇到了很多问题,一一解决之后感觉不错,所以想要share出来,具体一些原理不太懂的话可以上网找大佬的笔记,例如Http报文格式、多线程那些,写的都很不戳。最终该实验可以实现在同一个局域网下,同时有五台电脑可访问指定文件夹下的文件,只放最后一次实验的完整代码。在实验六的基础上加入多线程之后,会出现一个异常,我直接忽略掉点继续之后,可以完成响应,异常丞待解决。

一、实验要求

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、文件夹管理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、实验结果

在本机电脑,输入127.0.0.1/g.png,借用舍友的电脑,输入192.168.1.105/g.png(本机IP地址),均可得到以下结果:
在这里插入图片描述
在这里插入图片描述
由上图可知,线程2和线程3被用于响应了,一次性可以响应五个。

四、程序代码

//------------------MyHttpServer.cpp-----------------------------
// MyHttpServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#define  _WINSOCK_DEPRECATED_NO_WARNINGS  

#include "CMyHttpServer.h"


									//因为socket实际上是int变量,故可直接返回int  

int main() {

	CMyHttpServer svr;
	svr.init(80);
	svr.start();

	return 0;
}
//-------------------CMyHttpServer.h--------------
#pragma once
class CMyHttpServer
{
	int m_sock=0;
public:
	CMyHttpServer();
	~CMyHttpServer();
	int init(int port);
	int start(int sock);
	int start();
	void handle(int socket_feed);

};
//-------------------CMyHttpServer.cpp--------------
#include "pch.h"
#define  _WINSOCK_DEPRECATED_NO_WARNINGS  
#include "CMyHttpServer.h"
#include "CMyHttpRequest.h"
#include "CMyHttpResponse.h"


#include <winsock2.h>   
#include<iostream>  
#include<string>  
#include<thread>  
#include<fstream>
#include<sstream>

#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll    

#define BUF_SIZE 10086  
//直接用std好像会出现bug  
using std::cout;
using std::endl;
using std::thread;
using std::string;
using std::stringstream;			//字符串流,方便处理字符串
using std::ifstream;				//文件流,进行文件操作


CMyHttpServer::CMyHttpServer()
{
}


CMyHttpServer::~CMyHttpServer()
{
}

int CMyHttpServer::init(int port)
{
	//TCP的服务器端socket基本流程
	//socket->bind->listen->accept->send/recv->closesocket
	
	//初始化dll  
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	//创建套接字    
	SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);
	//绑定套接字    
	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充    
	sockAddr.sin_family = PF_INET;  //使用IPv4地址    
									//sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //回送IP地址   
	sockAddr.sin_addr.s_addr = INADDR_ANY;          //服务器直接默认本地ip  
	sockAddr.sin_port = htons(port);  //端口    
	bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
	m_sock = servSock;
	return servSock;	
}


CRITICAL_SECTION cs; //定义临界区全局变量
//线程函数
DWORD WINAPI Http_Thread(LPVOID lpParam)
{
	int n = (int)lpParam;
	EnterCriticalSection(&cs);
	CMyHttpResponse res(int(lpParam));
	LeaveCriticalSection(&cs);
	printf("Thread #%d returned successfully\n", n);
	return 0;
}

int CMyHttpServer::start(int sock)
{

	//进入监听状态    
	listen(sock, 20);
	while (true)
	{
		int socket_feed = accept(sock, nullptr, nullptr);
		handle(socket_feed);
	}

	//终止 DLL 的使用    
	WSACleanup();

	return 0;
}

int CMyHttpServer::start()
{
	if (m_sock != 0)
		start(m_sock);
	return 0;
}

void CMyHttpServer::handle(int socket_feed)
{
	cout << "线程为:" << std::this_thread::get_id() << std::endl;
	char buf[1024] = { '\0' };
	string cmd;
	string filename;
	recv(socket_feed, buf, sizeof(buf), 0);
	stringstream sstream;//2
	cout << buf;

	CMyHttpRequest req(buf);
	
	DWORD ThreadID;
	HANDLE hThread[5];
	//初始化临界区
	InitializeCriticalSection(&cs);
	for (int i = 0; i < 5; i++)
	{
		//创建线程,并调用Thread写文件
		hThread[i] = CreateThread(NULL, 0, Http_Thread, (LPVOID)(socket_feed), 0, &ThreadID);
		printf("Thread #%d has been created successfully.\n", i + 1);
	}
	//等待所有进程结束
	WaitForMultipleObjects(5, hThread, TRUE, INFINITE);
	//删除临界区
	DeleteCriticalSection(&cs);
	//关闭文件句柄
	CloseHandle(hThread);
	

	CMyHttpResponse res(socket_feed);
	
	sstream << buf;
	sstream >> cmd;
	sstream >> filename;
	cout << cmd << " " << filename << endl;
	//string head = "HTTP/1.0 200 OK\r\nContent - type:text/html\r\n\r\n";
	//send(socket_fd, head.c_str(), strlen(head.c_str()), 0);
	//send(socket_fd, buf, strlen(buf), 0);
	if (req.getMethod() == "GET")
	{
		ifstream file;
		filename = "./www/" + filename.substr(1, filename.length() - 1);
		file.open(filename, ifstream::binary);
		string head = "HTTP/1.0 200 OK\r\nContent - type:text/plain\r\n\r\n";
		//检查文件是否存在
		if (!file)
		{
			res.setStatus(404);
			res.setHeader("Content-Type", "text/plain");

			char buf[1024] = "hello my http server. \r\n file not found!\0";
			res.Write(buf, strlen(buf));
			cout << "file not exists." << endl;
			closesocket(socket_feed);
			return;
		}

		//如果文件存在,输出文件MIME类型
		res.setStatus(200);
		if (filename.find(".html") != string::npos || filename.find(".htm") != string::npos)
		{
			res.setHeader("Content-Type", "text/html");
		}
		else if (filename.find(".png") != string::npos)
		{
			res.setHeader("Content-Type", "image/png");
		}
		else if (filename.find(".jpg") != string::npos)
		{
			res.setHeader("Content-Type", "image/jpg");
		}
		else
		{
			res.setHeader("Content-Type", "text/plain");
		}

		//res.Write("Here is the content!", 25);

		//传输文件
		while (!file.eof())
		{
			char buf[1024] = { '\0' };
			memset(buf, 0, sizeof(buf));
			file.read(buf, sizeof(buf) - 1);
			int n = file.gcount();
			res.Write(buf, n);
			//send(socket_feed, buf, n, 0);
		}
		file.close();
	}
	closesocket(socket_feed);
}
//-----------------CMyHttpRequest.h-------------------
#pragma once
#include<string>  
#include <map>

using std::map;
using std::string;


class CMyHttpRequest
{
	string szMethod;
	string szContextPath;
	string szProtoVer;
	map<string, string> mHeaders;
	map<string, string> mParas;

public:
	CMyHttpRequest(char *buf);
	~CMyHttpRequest();
	string getMethod();
	string getHeader(string& headname);
	string getParameter(string& paraname);
	string getContextPath();

};
//---------------CMyHttpRequest.cpp--------------
#include "pch.h"
#include "CMyHttpRequest.h"
#include<string>  
#include<sstream>
#include <map>

using std::map;
using std::string;
using std::stringstream;			//字符串流,方便处理字符串


CMyHttpRequest::CMyHttpRequest(char *buf)
{
	int ipos;
	string ss;
	stringstream sstream;
	sstream << buf;

	sstream >> szMethod;
	sstream >> szContextPath; //类似于/x.html?k1=v1&k2=v2&k3=v3
	sstream >> szProtoVer;

	while (std::getline(sstream, ss))
	{
		ipos = ss.find(':');
		if (ipos > 2)
		{//还有一个\r没处理
			string headkey =  ss.substr(0, ipos);
			string headvalue = ss.substr(ipos + 1);
			mHeaders[headkey] = headvalue;
		}
	}
	ipos = szContextPath.find('?');
	if(ipos>1)
	{
		//处理:/x.html ? k1 = v1 & k2 = v2 & k3 = v3
		sstream.str("");//清空原有缓存
		sstream.clear();//恢复eof符号
		sstream << szContextPath.c_str();
		std::getline(sstream, szContextPath, '?'); //获得/x.html
		while (std::getline(sstream, ss, '&')) //处理k1=v1&k2=v2&k3=v3
		{
			ipos = ss.find('=');
			if (ipos > 0)
			{
				string parakey = ss.substr(0, ipos);
				string paravalue = ss.substr(ipos + 1);
				mParas[parakey] = paravalue;
			}

		}
	}
}


CMyHttpRequest::~CMyHttpRequest()
{
}

string CMyHttpRequest::getMethod()
{
	return szMethod;
}

string CMyHttpRequest::getHeader(string & headname)
{
	return mHeaders[headname];
}

string CMyHttpRequest::getParameter(string & paraname)
{
	return mParas[paraname];
}

string CMyHttpRequest::getContextPath()
{
	return szContextPath;
}
//-------------------CMyHttpResponse.h--------------
#pragma once
#include<string>  
#include <map>
#include<fstream>

using std::string;
using std::map;
using std::ifstream;				//文件流,进行文件操作


class CMyHttpResponse
{
	int iSocket;
	string szStatus;
	int iStatusCode;
	map<string, string> mHeaders;
	int iHeaderWrited;

public:
	CMyHttpResponse(int sockfeed);
	~CMyHttpResponse();
	int WriteHead();
	int Write(string &szRes);
	int Write(const char *buf, int size);
	int WriteFile(ifstream file);

	string getHeader(string& headname);
	int setHeader(string& headname, string &headvalue);
	int setHeader(const char* headname, const char* headvalue);
	int setStatus(int code);


};
//-------------------CMyHttpResponse.cpp--------------
#include "pch.h"
#include "CMyHttpResponse.h"
#include <winsock2.h>   
#include<fstream>
#include<iostream>  

#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll    

using std::cout;
using std::endl;
using std::string;
using std::stringstream;			//字符串流,方便处理字符串
using std::ifstream;				//文件流,进行文件操作


CMyHttpResponse::CMyHttpResponse(int sockfeed):iHeaderWrited(0),iSocket(sockfeed)
{
}


CMyHttpResponse::~CMyHttpResponse()
{
}

int CMyHttpResponse::WriteHead()
{
	string head;
	if (iStatusCode == 200)
	{
		head = "HTTP/1.0 200 OK\r\n";
	}
	else if (iStatusCode == 404)
	{
		head = "HTTP/1.0 404 Not Found\r\n";
	}
	map<string, string>::iterator iter;
	for (iter = mHeaders.begin(); iter != mHeaders.end(); iter++)
	{
		head += (iter->first + ": " + iter->second + "\r\n");
	}
	head += "\r\n";
	send(iSocket, head.c_str(), head.length(), 0);
	iHeaderWrited = 1;

	return 0;
}

int CMyHttpResponse::Write(string & szRes)
{
	Write(szRes.c_str(), szRes.length() - 1);
	return 0;
}

int CMyHttpResponse::Write(const char * buf, int size)
{
	if (iHeaderWrited == 0)
	{
		WriteHead();
	}
	send(iSocket, buf, size, 0);

	return 0;
}

int CMyHttpResponse::WriteFile(ifstream file)
{
	if (!file)
	{
		return 0;
	}

	传输文件
	//while (!file.eof())
	//{
	//	char buf[1024] = { '\0' };
	//	memset(buf, 0, sizeof(buf));
	//	file.read(buf, sizeof(buf) - 1);
	//	int n = file.gcount();
	//	send(socket_feed, buf, n, 0);
	//}
	//file.close();

	return 0;
}

string CMyHttpResponse::getHeader(string & headname)
{
	return mHeaders[headname];
}

int CMyHttpResponse::setHeader(string & headname, string & headvalue)
{
	mHeaders[headname] = headvalue;
	return 0;
}

int CMyHttpResponse::setHeader(const char * headname, const char * headvalue)
{
	string h(headname);
	string v(headvalue);
	mHeaders[h] = v;
	return 0;
}

int CMyHttpResponse::setStatus(int code)
{
	iStatusCode = code;
	return 0;
}
   老师说的几句话我觉得很好,“你出现的问题在很多年前别人就已经出现了,
   所以一定会有解决办法的”,“毕业之后,你跟那些找到工作的同学就是差了
   几万行的代码,要多练”,共勉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值