TY2048
Escapist.桃花源
2021-02-28 00:42

C++的WinSocket库问题,望高手解答

最近对socket相关的东西感兴趣(其实之前接触过的,研究QQ的收发数据包的时候和UDP相关)。因为这次想看看WinInet库(对socket的一层封装)的原理而接触了socket,打算直接用socket写一个http/https的访问类:

代码:

#pragma once
#include"EspString.hpp"
#pragma comment(lib,"ws2_32.lib")
#include<iostream>
class EspHttp
{
private:
	unsigned int HSocket = INVALID_SOCKET;
	bool IsPost = false;
	bool IsHttps = false;
	unsigned int ErrorCode;
	unsigned short nPort;
	EspString lpszScheme;//http or https
	EspString lpszEntireURL;
	EspString lpszHostName;//www.baidu.com
	EspString lpszURLPath;// ...com/[.....]<-
	EspString lpszProtocol;//Submitted/received protocol
	EspString lpszAddInfo;//POST method
	EspString lpszResult;//recv
	EspString lpszStatus;//HTTP/1.1 200 OK   HTTP/1.1 400 Bad Request
	EspString lpszContent;// Manbody of the Result
	bool RequestOK;//Whether is HTTP/1.1 200 OK
	bool AnalyzeURL()
	{
		unsigned long ParsePos = 0; unsigned long TargetPos = 0;
		assert(lpszEntireURL.GetBuffer() != NULL && ::IsBadStringPtrA(lpszEntireURL.GetBuffer(), -1) == false);
		EspString lpszTempString;

		TargetPos = lpszEntireURL.Find(":", 0);
		if (TargetPos == -1)
			return false;
		lpszTempString = lpszEntireURL.GetLeftStr(TargetPos);
		if (lpszTempString.CompareNoCase("http"))
			lpszScheme = "http";
		else if (lpszTempString.CompareNoCase("https"))
		{
			lpszScheme = "https";
			IsHttps = true;
		}
		else
			return false;

		ParsePos += (lpszTempString.GetLength() + 3);
		TargetPos = lpszEntireURL.Find(":", ParsePos);
		if (TargetPos != -1)
		{
			lpszTempString = lpszEntireURL.GetMidStr(ParsePos, TargetPos - ParsePos);
			if (lpszTempString.IsEmpty())return false;
			lpszHostName = lpszTempString;
			ParsePos += (lpszTempString.GetLength() + 1);
			TargetPos = lpszEntireURL.Find("/", ParsePos);
			if (TargetPos == -1)
				return false;
			nPort = ::strtoul(lpszEntireURL.GetMidStr(ParsePos, TargetPos - ParsePos).GetAnsiStr(), 0, 0);
		}
		else
		{
			TargetPos = lpszEntireURL.Find("/", ParsePos);
			if (TargetPos == -1)
				return false;
			lpszTempString = lpszEntireURL.GetMidStr(ParsePos, TargetPos - ParsePos);
			if (lpszTempString.IsEmpty())return false;
			lpszHostName = lpszTempString;
			if (IsHttps)
				nPort = 443;
			else
				nPort = 80;
		}
		lpszURLPath = lpszEntireURL.GetMidStr(TargetPos, lpszEntireURL.GetLength() - TargetPos);
		return true;
	}
public:
	EspHttp()
	{
		WSADATA SocketData = { 0 };
		ErrorCode = ::WSAStartup(MAKEWORD(2, 2), &SocketData);
		if (ErrorCode == 0)
		{
			HSocket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (HSocket == INVALID_SOCKET)
				ErrorCode = ::WSAGetLastError();
		}
	}
	~EspHttp()
	{
		if (ErrorCode == 0)
		{
			if (HSocket != INVALID_SOCKET)
				::closesocket(HSocket);
			::WSACleanup();
		}
	}
	unsigned int GetLastError() { return ErrorCode; }

	bool ConnectTo(const char* lpszURL, bool IsPOST = false, const char* lpszAddInfo = NULL)
	{
		assert(lpszURL != NULL && ::IsBadStringPtrA(lpszURL, -1) == false);
		lpszEntireURL = lpszURL;
		if (lpszEntireURL.IsEmpty())
			return false;
		if (!AnalyzeURL())
			return false;
		SOCKADDR_IN SocketAddr = { AF_INET,::htons(nPort),*((LPIN_ADDR) * ::gethostbyname(lpszHostName.GetAnsiStr())->h_addr_list) };
		if (::connect(HSocket, (LPSOCKADDR)&SocketAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
		{
			ErrorCode = ::WSAGetLastError();
			::closesocket(HSocket);
			return false;
		}
		IsPost = IsPOST;
		if (IsPost)
		{
			lpszProtocol.Append("POST ");
			if (lpszAddInfo != NULL)
				this->lpszAddInfo = lpszAddInfo;
		}
		else
			lpszProtocol.Append("GET ");
		lpszProtocol.Append(lpszURLPath.GetAnsiStr());
		lpszProtocol.Append(" HTTP/1.1\r\n");
		lpszProtocol.Append("Host: "); lpszProtocol.Append(lpszHostName.GetAnsiStr()); lpszProtocol.Append("\r\n");
	}
	void AddToProtocol(const char* lpszName, const char* lpszValue)
	{
		assert(lpszName != NULL && ::IsBadStringPtrA(lpszName, -1) == false);
		assert(lpszValue != NULL && ::IsBadStringPtrA(lpszValue, -1) == false);
		lpszProtocol.Append(lpszName);
		lpszProtocol.Append(": ");
		lpszProtocol.Append(lpszValue);
		lpszProtocol.Append("\r\n");
	}
	bool SendRequest()
	{
		lpszProtocol.Append("\r\n");
		if (IsPost)
			lpszProtocol.Append(lpszAddInfo);
		if (::send(HSocket, lpszProtocol.GetAnsiStr(), lpszProtocol.GetLength() + 1, 0) == SOCKET_ERROR)
		{
			ErrorCode = ::WSAGetLastError();
			return false;
		}
		char* Buffer = (char*)::malloc(1025);
		if (Buffer == NULL)
			throw("Allocate Buffer Unsuccessfully");
		unsigned long BufferSize = 0;
		lpszResult.Empty();
		do
		{
			BufferSize = ::recv(HSocket, Buffer, 1024, 0);
			if (BufferSize > 0)
			{
				::memset(Buffer + BufferSize, 0, (1025 - BufferSize) * sizeof(char));
				lpszResult.Append(Buffer);
			}
			else if (BufferSize == 0)
				break;
			else if (BufferSize == SOCKET_ERROR)
			{
				ErrorCode = ::WSAGetLastError();
				return false;
			}
		} while (BufferSize != 0);
		if (!lpszResult.IsEmpty())
		{
			unsigned long Pos1 = lpszResult.Find("\r\n");
			if (Pos1 == -1)return false;
			lpszStatus = lpszResult.GetLeftStr(Pos1);
			unsigned long Pos2 = lpszResult.Find("\r\n\r\n");
			if (Pos2 == -1)return false;
			lpszProtocol = lpszResult.GetMidStr(Pos1, Pos2);
			lpszContent = lpszResult.GetMidStr(Pos2+1, lpszResult.GetLength() - Pos2);
			return true;
		}
		else
			return false;
	}
	EspString GetResponce()
	{
		return lpszResult;
	}
};

可以实现最简单的Get和Post请求功能,亲测成功(一些开放的和抓取到的API都能请求成功)但是我在之前是在SendRequest之后直接返回的所有数据(没有接下来那一步解析),发现这个程序它recv了两次(SendRequest部分第149行),第一次是200 OK,数据读取也正常。但是第二次就是400 Bad Request了,很迷惑。

所有输出:

HTTP/1.1 200 OK
Date: Sat, 27 Feb 2021 16:38:39 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 973
Connection: keep-alive
Bili-Status-Code: 0
Bili-Trace-Id: 4f3d3a4ca5603a75
X-Cache-Webcdn: BYPASS from cn-sdjn2-cmcc-w-02

{"code":0,"message":"0","ttl":1,"data":{"mid":(隐藏),"name":"(隐藏),"sex":"(隐藏)","face":"(隐藏)","sign":"濂借倽鍑€熷姏锛岄€佹垜涓婇潚浜戙€?,"rank":10000,"level":4,"jointime":0,"moral":0,"silence":0,"birthday":"","coins":0,"fans_badge":false,"official":{"role":0,"title":"","desc":"","type":-1},"vip":{"type":0,"status":0,"theme_type":0,"label":{"path":"","text":"","label_theme":""},"avatar_subscript":0,"nickname_color":""},"pendant":{"pid":0,"name":"","image":"","expire":0,"image_enhance":"","image_enhance_frame":""},"nameplate":{"nid":0,"name":"","image":"","image_small":"","level":"","condition":""},"is_followed":false,"top_photo":"http://i0.hdslb.com/bfs/space/44873d3568bdcb3d850d234e02a19602972450f1.png","theme":{},"sys_notice":{},"live_room":{"roomStatus":0,"liveStatus":0,"url":"","title":"","cover":"","online":0,"roomid":0,"roundStatus":0,"broadcast_type":0}}}HTTP/1.1 400 Bad Request
Server: Tengine
Date: Sat, 27 Feb 2021 16:38:39 GMT
Content-Type: text/html
Content-Length: 571
Connection: close

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
 Sorry for the inconvenience.<br/>
Please report this message and include the following information to us.<br/>
Thank you very much!</p>
<table>
<tr>
<td>URL:</td>
<td>http://default.bilibili.com</td>
</tr>
<tr>
<td>Server:</td>
<td>cn-sdjn2-cmcc-w-02</td>
</tr>
<tr>
<td>Date:</td>
<td>2021/02/28 00:38:39</td>
</tr>
</table>
<hr/>Powered by Tengine<hr><center>tengine</center>
</body>
</html>

↑(调用的B站用户信息获取API)

请问为什么会连着请求了两次而且第一次成功返回第二次失败,如果其他地方发现有问题也希望您指出,谢谢~

(EspString是自己写的一个字符串类,你当成CString就好......)

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答