MyHttp,从socket开始实现一个服务器及Http请求类 [0]

本文介绍了作者使用C++实现MyHttp的过程中,从创建自定义String类到利用Socket进行HTTP请求的基本步骤。通过获取套接字、建立连接、发送请求和接收响应,初步实现了简单的HTTP请求功能。同时,作者分享了对C++和C#的个人看法,并预告后续将完善项目并考虑使用线程池。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先需要一个差不多的String

我实际上是不喜欢c++的,写起来不仅心智负担太重,还特别简陋。听说c++20马上要出来了,虽然我连c++11都不怎么会,17更不用说,但我估计c++入门书籍快2000页了,估计依旧是标准库里没有网络库,也没有库管理工具,我永远喜欢c#及Nuget.
stl中的string过于简陋,写习惯c#过来感觉就是简直不能用,不过性能还行。为了容易移到其他项目上,oop的封装,solid的开闭原则,继承string自己实现MyString 是最好的选择,后面可能随时修改

项目代码仓库 GitHub

class MyString:public string
{
public:
	MyString()=default;
	~MyString()=default;
	template<class T>
	MyString(T&& arg) :string(forward<T>(arg))
	{

	}
	vector<MyString> Split(MyString flag,bool SkipEmpty=true)
	{
		vector<MyString> return_data;
		int pos = find(flag);
		bool FirstLine = true;
		while (pos!=-1)
		{
			MyString line;
			auto PushData=[&return_data,SkipEmpty](MyString line){
				if (line.length() != 0 || !SkipEmpty)
				{
					return_data.push_back(line);
				}
			};
			if (FirstLine)
			{
				line = substr(0, pos);
				PushData(line);
				FirstLine = false;
			}
			else
			{
				int right = find(flag, pos+1);
				int left = pos + flag.length();
				if (right==-1)
				{
					line = substr(left);
					PushData(line);
					break;
				}
				line = substr(left, right - left);
				PushData(line);
				pos = right;
			}
		}
		return return_data;
	}
	vector<MyString> Split(char flag='\n')
	{
		vector<MyString> return_data;
		istringstream data;
		data.str(data());
		string temp;
		while (getline(data,temp,flag))
		{
			return_data.push_back(temp);
		}
		return return_data;
	}
	MyString Trim(char target=' ')
	{
		int left=find_first_not_of(target);
		int right = find_last_not_of(target);
		return substr(left, right-left+1);
	}
	 
private:

};

socket,http是什么东西?这个直接看其他人的,
首先http是基于tcp
图来自其他人,侵删
这是响应体这是请求头

使用socket来进行Http请求数据需要经过

获取一个套接字


SOCKET GetSocket(int port, bool IsServer = false)
{
	//初始化。,WSA windows异步套接字
	WSADATA inet_WsaData;//
	WSAStartup(MAKEWORD(2, 0), &inet_WsaData);//socket2.0版本
	if (LOBYTE(inet_WsaData.wVersion) != 2 || HIBYTE(inet_WsaData.wVersion) != 0)
	{//高位字节指明副版本、低位字节指明主版本
		WSACleanup();
		return -1;
	}
	auto tcp_socket = socket(AF_INET,SOCK_STREAM, 0);//ipv4,tcp,tcp或udp该参数可为0
	
	if (!IsServer)
	{
		return tcp_socket;
	}//服务器才需要干下面这些
	sockaddr_in saddr;
	saddr.sin_family = AF_INET;//ipv4 udp tcp 等
	saddr.sin_port = htons(port); //端口;
	saddr.sin_addr.s_addr = INADDR_ANY;//是监听地址
	if (::bind(tcp_socket, (sockaddr*)&saddr, sizeof(saddr)) == -1)
	{//因为using namespace std,所以要使用::指定全局函数,而非std::bind
		throw exception("bind error");
	}
	if (::listen(tcp_socket, 20) == -1)//监听的套接字句柄,指定监听的最大连接数量
	{//listen函数将socket变为被动类型的,等待客户的连接请求。
		throw exception("listen error");
	}
	return tcp_socket;
}

使用Connect进行连接

	in_addr sa;
	MyString ServerAddr = "127.0.0.1";//我开了 .net core web server在本地测试,你们自行替换  
	if (InetPton(AF_INET,ServerAddr.c_str(),&sa)==-1)
	{
		throw new exception("地址转换错误");
	}
	sockaddr_in saddr;
	saddr.sin_family = AF_INET;//ipv4
	saddr.sin_port = htons(5000);//端口
	saddr.sin_addr=sa;
	if (::connect(Socket, (sockaddr*)&saddr, sizeof(saddr))!=0)
	{
		MyString Msg = "连接失败错误码:" + WSAGetLastError();
		throw new exception(Msg.c_str());
	}

使用send发送请求

	MyHttp::Request a;//myhttp在最下面放
	a.SetHttpMethod(MyHttp::MethodEnum::Get);
	a.Path = "/api/auth/zanllp";
	a.Header["Host"] = "127.0.0.1:5000";
	a.Header["Accept-Encoding"]="gzip, deflate, br";
	a.Header["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8";
	a.Header["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 OPR/58.0.3135.79";

	MyString SendBuf = a.SendRequest();
	cout << SendBuf << endl;
	send(Socket, SendBuf.c_str(), SendBuf.length(), 0);

使用recv读取响应

	char buf[1024] = { '\0' };
	recv(Socket, buf, sizeof(buf), 0);//1
	MyHttp::Response da(buf);
	cout << da.ParseFromSource()<< endl;

结果

在这里插入图片描述
基本可以就行简单的请求了

不知道ip怎么办?

	MyString GetIpByHostname(MyString hostname)
	{
		addrinfo hints, *res;
		in_addr addr;
		int err;
		memset(&hints, 0, sizeof(addrinfo));
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_family = AF_INET;//ipv4
		if ((err = getaddrinfo(hostname.c_str(), NULL, &hints, &res)) != 0) {
			MyString Msg = "错误" + err + MyString(gai_strerror(err));
			throw exception(Msg.c_str());
		}
		addr.s_addr = ((sockaddr_in*)(res->ai_addr))->sin_addr.s_addr;
		char str[INET_ADDRSTRLEN];
		auto ptr = inet_ntop(AF_INET, &addr, str, sizeof(str));
		freeaddrinfo(res);
		return str;
	}

//在connetcion时修改如下
	MyString ServerAddr = MyHttp::GetIpByHostname("www.baidu.com");
	if (InetPton(AF_INET,ServerAddr.c_str(),&sa)==-1)
	{
		throw new exception("地址转换错误");
	}
	sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(80);
	saddr.sin_addr=sa;

赶赶单单

目前的MyHttp进度

#include <winsock2.h>
#include <Windows.h>
#include <stdio.h>
#include <iostream>
#include <thread>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include<WS2tcpip.h>
#include<map>


namespace MyHttp
{
	MyString GetIpByHostname(MyString hostname)
	{
		addrinfo hints, *res;
		in_addr addr;
		int err;
		memset(&hints, 0, sizeof(addrinfo));
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_family = AF_INET;//ipv4
		if ((err = getaddrinfo(hostname.c_str(), NULL, &hints, &res)) != 0) {
			MyString Msg = "错误" + err + MyString(gai_strerror(err));
			throw exception(Msg.c_str());
		}
		addr.s_addr = ((sockaddr_in*)(res->ai_addr))->sin_addr.s_addr;
		char str[INET_ADDRSTRLEN];
		auto ptr = inet_ntop(AF_INET, &addr, str, sizeof(str));
		freeaddrinfo(res);
		return str;
	}


	enum MethodEnum
	{
		Get,
		Post,
		Put,
		Delete
	};

	class Response
	{
	public:
		MyString Source;
		MyString ProtocolVersion = "HTTP/1.1";
		MyString Code = "200";
		MyString Status = "ok";
		MyString Server = "CppTinyServer";
		map<MyString, MyString> Header;
		MyString ResponseBody;
		~Response() = default;
		Response() = default;

		template<class T>
		Response(T&& arg)
		{
			Source = MyString(forward<T>(arg));
		}
		MyString& BuildResponseHeader()
		{
			Source = ProtocolVersion + " " + Code + " " + Status + "\r\n";
			Header["Server"] = Server;
			Header["Content-Length"] = to_string(ResponseBody.length());
			for (auto x : Header)
			{
				Source += x.first + ":" + x.second + "\r\n";
			}
			Source += "\r\n";
			return Source;
		}

		MyString& ParseFromSource()
		{
			auto ThrowError = [] {throw exception("解析错误"); };
			auto data = Source.Split("\r\n");
			if (data.size()==0)
			{
				return ResponseBody;
			}
			auto FirstLine = data[0].Split(' ');
			if (FirstLine.size() != 3)
			{
				ThrowError();
			}
			//
			ProtocolVersion = FirstLine[0].Trim();
			Code = FirstLine[1].Trim();
			Status = FirstLine[2].Trim();
			data.erase(data.begin());
			//
			for (auto x : data)
			{
				auto pair = x.Split(':');
				if (pair.size() != 2)
				{
					continue;
				}
				Header[pair[0].Trim()] = pair[1].Trim();
			}
			//
			if (Source.find("\r\n\r\n")==Source.length()+4)
			{
				return ResponseBody;
			}
			ResponseBody = move(*--data.end());
			return ResponseBody;
		}
	};

	class Request
	{
	public:
		MyString Source;
		MyString Method;
		MyString Path;
		MyString ProtocolVersion = "HTTP/1.1";
		map<MyString, MyString> Header;
		MyString RequestBody;
		Request() = default;
		~Request() = default;



		template<class T>
		Request(T&& arg)
		{
			Source = MyString(forward<T>(arg));
		}

		void SetHttpMethod(MethodEnum method, MyString other = "")
		{
			if (other != "")
			{
				Method = other;
				return;
			}
			switch (method)
			{
			case MethodEnum::Get:
				Method = "GET";
				break;
			case MethodEnum::Post:
				Method = "POST";
				break;
			case MethodEnum::Put:
				Method = "PUT";
				break;
			case MethodEnum::Delete:
				Method = "DELETE";
				break;
			default:
				break;
			}
		}

		void ParseFromSource()
		{
			auto ThrowError = [] {throw exception("解析错误"); };
			if (Source.length() < 10)
			{
				ThrowError();
			}
			auto data = Source.Split("\r\n");
			if (data.size() == 0)
			{
				ThrowError();
			}
			//请求行
			auto FirstLine = data[0].Split(' ');
			if (FirstLine.size() != 3)
			{
				ThrowError();
			}
			Method = FirstLine[0].Trim();
			Path = FirstLine[1].Trim();
			ProtocolVersion = FirstLine[2].Trim();
			data.erase(data.begin());
			//请求头
			for (auto x : data)
			{
				auto pair = x.Split(':');
				if (pair.size() != 2)
				{
					if (pair.size() == 3)//host:127.0.0.1:5000
					{
						Header[pair[0].Trim()] =
							pair[1].Trim() + ":" + pair[2].Trim();
					}
					continue;
				}
				Header[pair[0].Trim()] = pair[1].Trim();
			}
			//请求体
			if (Source.find_last_of("\r\n") == Source.length() + 2)
			{
				return;
			}
			RequestBody =move( *--data.end());
		}

		MyString SendRequest()
		{
			Source = Method + " " + Path + " " + ProtocolVersion + "\r\n";
			for (auto x : Header)
			{
				Source += x.first + ":" + x.second + "\r\n";
			}
			Source += "\r\n";
			Source += RequestBody;
			return Source;
		}
	};

}

有点乱等过几天有时间把工程推到GitHub上,服务器也弄能运行,但是用的thread,而不是线程池,等搞懂后再写。这个系列可能有十几篇也可能直接鸽了。还是c#天下第一,await+async+task 没有解决不了的多线程异步任务,wpf的跨线程更新界面除外,或者说那TM是直接把任务拉到界面处理的绘图一多还是卡,但是瑕不掩瑜最重要的是给我赚了足够多的钱。

大佬们给个star吧 GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值