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;
}
老师说的几句话我觉得很好,“你出现的问题在很多年前别人就已经出现了,
所以一定会有解决办法的”,“毕业之后,你跟那些找到工作的同学就是差了
几万行的代码,要多练”,共勉。