从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换
项目仓库 GitHub
请求url处理
- url有两种,一种是通过域名,一种直接ip
- 直接ip常用于客户端的api调用,或者是在测试环境
- 域名一般是ajax调用api,或者是网页的获取,有几个好处,ip可能会变但网址一般不变,可以不用修改。并且可以起到负载均衡的作用,无论dns还是nginx.
请求url字符串必须以协议名开头,协议名开头,c#,java的httpclient都是这样没记错的话,至于协议现在只支持http.https等我写完服务器再来搞
MyString protocolStr = "http://";
if (url.find(protocolStr)!=0)
{
MyString Msg = "标明协议!仅允许使用" + protocolStr;
throw exception(Msg.c_str());
}
对于http协议,host和请求路径是分开的,所以需要对url进一步拆分
url.erase(0, protocolStr.length());//移除协议名称
MyString host = url.substr(0, url.find("/"));
Header["Host"] = host;
url.erase(0, host.length());
Path = url.length()!=0?url:"/";//在前面已经将http协议头和主机名ip移去
Socket socket = GetSocket();
//getsocket函数已修改,默认获取客户端socket,客户端的端口由系统临时分配不需要指定
//若指定端口号则认为在获取一个服务器socket
Connection(socket,host);
connection仅用于请求,连接目标服务器端口
- host可以以为
- 127.0.0.1:5000
- www.baidu.com
- zanllp.cn:5000
若在host中有:,则修改端口默认80
若在host中有字母,则通过GetIpByHost获取ip
目前仅考虑ipv4,ipv6等ssl后再来
void Connection(Socket socket,MyString host)
{
int port = 80;//没有:8080,直接80就行
int indexPort = host.find(":");
if (indexPort!=-1)
{//127.0.0.1:5000,改端口为5000
port = stoi(host.substr(indexPort+1));
host = host.substr(0, indexPort);//去掉端口
}
in_addr sa;
MyString ServerAddr = //包含字母是网址,没有ip
host.HasLetter() ? GetIpByHost(host) : host;
if (InetPton(AF_INET, ServerAddr.c_str(), &sa) == -1)
{
throw new exception("地址转换错误");
}
sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr = sa;
if (::connect(socket, (sockaddr*)&saddr, sizeof(saddr)) != 0)
{
MyString Msg = "连接失败错误码:" + WSAGetLastError();
throw new exception(Msg.c_str());
}
}
响应读取
在connection后,就可以发送等待接收了
接受到的流使用mystring托管
recv的返回值,0为接受结束,大于0为收到的字节数,小于0是错误,
这里使用阻塞接受,因为不久后改成异步,所有耗时长的io都必须异步
BuildRequestString();//把请求的各个属性转换成字符串
send(socket, Source.c_str(), Source.length(), 0);
//读取响应由,Response托管
int num = 0;
char buf[1024] = {0};
Response resp;
num = recv(socket, buf, sizeof(buf) - 1, 0);
resp.Source += buf;
resp.ParseFromSource();
int lenHeader = resp.Source.length() - resp.ResponseBody.length();
int len = resp.Length;//解析后在响应头里的正文长度
while (resp.Source.length()-lenHeader<len)
{////接收到的字符少于缓冲区长度,接收结束,也有可能是错误或者连接关闭
num = recv(socket, buf, sizeof(buf) - 1, 0);
resp.Source += buf;
memset(buf, 0, sizeof(buf));
if (num < 0)
{
MyString Msg = "网络异常,Socket错误码:" + num;
throw exception(Msg.c_str());
}
}
接受字节数为什么是sizeof(buf) - 1,这很重要。这里使用mystring来保存流,字符串是通过‘\0’来判断流是在哪里结束的如果接收的字节把buf占满了,字符串复制到第511个还会继续下去,具体复制多少,这个不好说,反正在输出字符串时将会看到一堆,在每次复制完对buf重新填0同理以上。
这里需要先解析第一个包,获取响应头的正文长度,然后死循环读取,每次读到的字节数可能不太一样,具体参照滑动窗口(没记错的话,书上的东西早忘了)。
当然这种只适用于响应头里有正文长度的,如果是Transfer-Encoding: chunked那就是另外一回事了,这个过两天再写。
如何取名是个问题
给变量,函数。。。取名是个很麻烦的问题,写代码3年往往是看哪个不舒服就马上改。
为此有个很好的决解办法,一些简单的需求可以使用静态函数,这样就可以少取一个变量名了就像这样
MyHttp::Response resp = MyHttp::Request::StaticRequest(MyHttp::Get, "http://www.baidu.com");
cout << resp.ParseFromSource() << endl;
Run it
resp接手调用staticRequest回来的字节流,调用ParseFromSource进行解析,返回响应体。从图中可以看到一个问题,中文乱码很明显这是网页编码的问题返回的字节流是utf8而c++是gbk
gbk-utf8转换
现在把这两个函数封装到MyString内
这个不是我写的,来源看注释
//转换到gbk 中文显示乱码调用这个
MyString ToGbk()
{
//由blog.****.net/u012234115/article/details/83186386 改过来
auto src_str = c_str();
int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
wchar_t* wszGBK = new wchar_t[len + 1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
char* szGBK = new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
MyString result(szGBK);
if (wszGBK) delete[] wszGBK;
if (szGBK) delete[] szGBK;
return result;
}
//转换到utf8
MyString ToUtf8()
{
//由blog.****.net/u012234115/article/details/83186386 改过来
auto src_str = c_str();
int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
wchar_t* wszGBK = new wchar_t[len + 1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
char* szGBK = new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
MyString result(szGBK);
if (wszGBK) delete[] wszGBK;
if (szGBK) delete[] szGBK;
return result;
}
解析响应默认不进行转换,不是所有请求都需要
Try again
没问题,本地测试环境试试
Post测试
chunked还不支持
无耻的求个star GitHub