C/C++爬虫篇之网络编程(请求服务器)

目录

  • C/C++套接字
  • C/C++请求服务器实现

先来张效果图:
在这里插入图片描述

1- C/C++套接字

在说明套接字之前,先简单了解客户端和服务器之间的关系。(往后再发TCP三次握手和四次挥手详细叙述过程
1、TCP三次握手

标记说明
SYN建立连接标记,当某主机发送信息的时候,该标记会被设置为1 ,并和其他数据一起发送出去。
ACK确认信息标记,当接收到某主机发送的信息之后,该标记会被设置为1,并和其他数据一起返回给某主机,表示确认接收到某主机发送的信息。
seq报文序列号,该值分配是根据时间产生的一个随机值,通常情况下每间隔4ms会加1。唯一识别报文。
ack确认序号,该值是ack=seq+1。

注意:ACK是确认标记,ack是确认序号。两者是不一样的,ACK占1位,而ack占32位。

(1)TCP报文信息如下
在这里插入图片描述
(2)与服务器建立连接的过程如下
在这里插入图片描述

第一次握手,是客户端为了与服务器建立单向连接,客户端发送j建立连接的信息(SYN+seq)。
第二次握手,是服务器确认并接受客户端的请求连接信息,服务端确认并接受后(ACK+ack),此时,客户端可以向服务端发送信息,并且能知道服务端收到自己发送的信息;于此同时,服务端还会携带服务端自己与客户端请求建立连接的信息(SYN+seq)。
第三次握手,是客户端确认并接受服务器的请求连接信息,客户端确认并接受后(ACK+ack),此时,服务器可以向客户端发送信息,并且能知道客户端收到自己发送的信息。

(注意:假如没有第三次握手,服务端发送消息给客户端,服务端是不能知道客户端是否接收到信息的,第二次握手也是同理的。这就是TCP/IP是安全可靠的原因。)

2、TCP四次挥手

标记说明
FIN断开连接标记,当某主机发送信息的时候,该标记会被设置为1 ,并和其他数据一起发送出去。
ACK确认信息标记,当接收到某主机发送的信息之后,该标记会被设置为1,并和其他数据一起返回给某主机,表示确认接收到某主机发送的信息。
seq报文序列号,该值分配是根据时间产生的一个随机值,通常情况下每间隔4ms会加1。唯一识别报文。
ack确认序号,该值是ack=seq+1。

注意:ACK是确认标记,ack是确认序号。两者是不一样的,ACK占1位,而ack占32位。

(1)与服务器断开连接的过程如下
在这里插入图片描述

第一次挥手,是客户端想与服务端断开连接的信息。

第二次挥手,是服务器同意客户端与自己单向连接的信息,但还没有断开服务器与客户端的连接。此时客户端需要等待服务器将客户端断开连接之前的请求所有数据,发送到客户端,这时,服务器才会发送服务器与客户端的连接,即第三次挥手。

第三次挥手,是服务器想与客户端断开连接的信息。

第四次挥手,是客户端同意服务器与自己连接的信息。


好了,说了那么多,现在就说一下c/c++怎么连接服务器的吧。

1、什么是套接字?
(1)套接字简单的说就是用于连接服务器的一把钥匙,一条通道。
(2)是服务器和客户端之间通信的约定。
(3)在window系统中,套接字被解释为句柄。
(4)在Linux/Unix系统中,套接字被解释为文件描述符
(其实,套接字就是一些数字,套接字能使客户端和服务器连接并通信)
2、window下,套接字连接服务器的过程
在这里插入图片描述

2- C/C++请求服务器实现

代码如下

#include<iostream>
#include<String>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")//加载网络编程静态库
using namespace std;

//解析URL
void ParseURL(string url, string&host, string&res)
{
	//这里只是简单解析本例题的URL
	if (url.find("http://") != url.npos)
	{
		url = url.substr(7);//删除字符串中的http://
	}
	else if (url.find("https://") != url.npos)
	{
		url = url.substr(8);//删除字符串中的https://
	}
	//获取域名 删除URL协议头后的studentwebsite.cn/index.html,找到第一个"/"的位置 返回下标
	int pos = url.find_first_of("/");
	host = url.substr(0, pos);
	//获取资源地址
	res = url.substr(pos);
	//
	cout << "域名:" << host << endl;
	cout << "资源:" << res << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
	//WSAStartup存储返回网络的信息
	WSADATA wsdata;
	//初始化网络编程能够使用的函数或方法等
	WSAStartup(MAKEWORD(2, 2), &wsdata);
	//初始化网络之后,就能创建套接字等了

	//创建套接字
	SOCKET skt = socket(AF_INET, SOCK_STREAM, 0);
	if (skt == SOCKET_ERROR)
	{
		cout << "创建套接字失败.." << endl;
		return 0;
	}
	else
	{
		cout << "创建套接字成功.." << endl;
	}
	//URL是指地址,比如http://studentwebsite.cn/index.html
	string host_url;//URL
	string host;//域名
	string res;//资源地址
	cout << "请输入URL:";
	cin >> host_url;
	//解析URL,即将URL才分为域名+域名后面的资源地址,比如上面的拆分之后是:域名:studentwebsite.cn;资源地址:/index.html
	ParseURL(host_url, host, res);

	//设置要连接的服务器地址
	HOSTENT*ip = gethostbyname(host.c_str());//获取主机信息,里面包含IP地址

	//将IP地址绑定到套接字
	sockaddr_in address;
	memset(&address, 0, sizeof(sockaddr_in));//将结构体对象的所有变量初始化为0
	address.sin_family = AF_INET;//遵循的协议族
	address.sin_port = htons(80);//上面的URL端口是80,一般http端口号是80,htons作用是将端口号主机字节序转换为网络字节序
	memcpy(&address.sin_addr, ip->h_addr, 4);//转换为4个字节的正规IP地址

	//连接服务器
	int cflag = connect(skt, (SOCKADDR*)&address, sizeof(SOCKADDR));
	if (cflag == SOCKET_ERROR)
	{
		cout << "连接服务器失败.." << endl;
		return 0;
	}
	else
	{
		cout << "连接服务器成功.." << endl;
	}
	//准备发送给服务器,客户端需要的信息请求http://studentwebsite.cn/index.html
	string req = "";
	req += "GET " + res + " HTTP/1.1\r\n";
	req += "Host: " + host + "\r\n";
	req += "User-Agent:*\r\n";
	req += "Connection:Close\r\n";
	req += "\r\n";


	//给服务器发送信息
	int clen = send(skt, req.c_str(), req.length(), 0);

	//接受服务器返回的信息
	string info;//接受的信息
	char ch;//每次接受的信息
	int rlength = 0;//接受数据的总大小
	int rlen = recv(skt, &ch, 1, 0);//每次接受的数据大小
	rlength += rlen;
	while (rlen != 0 && rlen != SOCKET_ERROR)
	{
		info += ch;
		rlen = recv(skt, &ch, 1, 0);//每次接受的数据大小
		rlength += rlen;
	}

	//编码转换 防止在控制台显示乱码
	char*pszBuffer = new char[info.length() + 1];
	wchar_t* pszWideBuffer = new wchar_t[(info.length() + 1) * 2];
	memset(pszWideBuffer, 0, (info.length() + 1) * 2);
	memset(pszBuffer, 0, info.length() + 1);
	MultiByteToWideChar(CP_UTF8, 0, info.c_str(), info.length(), pszWideBuffer, (info.length() + 1) * 2);//将unicode编码,转换为宽字节
	WideCharToMultiByte(CP_ACP, 0, pszWideBuffer, wcslen(pszWideBuffer), pszBuffer, info.length() + 1, NULL, NULL);//将宽字节,转换为控制台编码
	//cout << pszBuffer;
	info = pszBuffer;
	delete[] pszBuffer;
	delete[] pszWideBuffer;

	//显示
	cout << "客户端给服务器发送了:" << req.length() << "个字节" << endl;
	cout << "服务器返回给客户端了:" << rlength << "个字节" << endl;
	cout << info << endl;//在控制台打印从服务器请求到信息
	system("pause");
	return 0;
}

示例1:

运行效果图
在这里插入图片描述
根据提示,我们输入

studentwebsite.cn/index.html

输入之后,效果图(下面请求回来的数据,是笔者我自己在自己的服务器上写的一段简单的HTML+css代码)
在这里插入图片描述

示例2:

我们在百度搜索"哪吒",然后复制地址栏的URL(https://www.baidu.com/s?ie=UTF-8&wd=%E5%93%AA%E5%90%92),如图
在这里插入图片描述
然后,我们运行我们的程序,根据提示输入

https://www.baidu.com/s?ie=UTF-8&wd=%E5%93%AA%E5%90%92

效果是,在不退出程序的情况下,不断的在控制台打印从服务器请求到的信息,为了简明,我将打印到控制台的语句cout << info << endl;注释,效果图如下
在这里插入图片描述

总结

可将上面代码复制到自己的编译器,编译运行,试试效果。怎么学习?就是先体验效果,培养兴趣,然后再深度学习。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖的码蚁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值