【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序

 

一、开发环境及工具:

· Microsoft Visual Studio(本程序使用的是Microsoft Visual Studio 2012)

 

· Chrome浏览器(其他浏览器也可以)

 

二、原理及实现:

1.原理概述:

用浏览器登录教务系统,实际上是Request了一个POST请求。

而在浏览器的选课界面中,点击“选课”按钮,则相当于向对方服务器Request了一个GET请求。

本程序使用C++编程语言,利用Windows网络编程接口Winsock2模拟浏览器的Request操作,

不断地将请求发送至对方服务器,当有人抛课时,实现自动抢课。

 

2.浏览器登录及点击的工作原理。

我们用浏览器打开一个新的标签页,按F12进入开发者工具的Network选项,在地址栏中输入教务系统网站,进入教务系统登录界面。

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第1张图片

表面上看,我们输入教务系统的网址并跳转,浏览器就给出这个网址的界面了。

细节上,则是客户机(我们的电脑)给对方服务器(教务系统的服务器)提交了一个GET的请求。

(具体请求信息保存在Request Headers里面并发送给对方,包括对方服务器主机名、浏览器版本、cookie等信息)

对方服务器收到我们的请求,于是响应这一请求,也用一个Request Headers的方式发给我们,并为我们的请求提供服务

(例如网页界面的HTML代码、图片等)。

 

利用F12开发者工具的Network选项查看Request和Response的具体信息:

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第2张图片

 

接下来,我们填写上用户名、密码及验证码。

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第3张图片

如果用户名、密码和验证码均输入无误,点击“登录”按钮后,则可成功登录进入学生个人中心,如下图。

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第4张图片

我们一开始在浏览器中输入网址后跳转,请求方式为GET,作用是从服务器上获取登陆界面的数据内容。

而这一次,我们提交登录信息的请求类型为POST,作用是向服务器传送数据,包括用户名、密码和验证码等。

以下是这一POST请求以及服务器对这个请求的响应:

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第5张图片

 

2.利用Winsock2在程序中模拟上述登录及点击过程

由于本程序要利用模拟浏览器发送请求的原理,程序要获取到这些请求信息,因此我们必须预先完成以下工作:

①.打开浏览器 --> 打开开发者工具 

②.在浏览器中登录一次 --> 记录登录时的request headers(类型为POST)

③.进入一次选课系统  --> 记录打开选课系统时的request headers(类型为GET)

④.点击“选课”按钮选一门要抢的课 --> 记录选这一门课的request headers(类型为GET)

 

a.登录过程中发送的request headers如下:

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第6张图片

在代码中创建并初始化一个字符串变量request_login,用于保存以上请求信息,将上述Request Headers的信息原封不动地抄下来:

 

//登录POST请求(Request Headers)
string request_login ="POST /jsxsd/xk/LoginToXkLdap HTTP/1.1\r\n"
	"Host: jxgl.gdufs.edu.cn\r\n"
	"Connection: keep-alive\r\n"
	"Content-Length: 54\r\n"            //账号密码的长度不同,Content-Length的值也不同,登录不同账号时要注意
	"Cache-Control: max-age=0\r\n"
	"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"
	"Origin: http://jxgl.gdufs.edu.cn\r\n"
	"Upgrade-Insecure-Requests: 1\r\n"
	"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36\r\n"
	"Content-Type: application/x-www-form-urlencoded\r\n"
	"Referer: http://jxgl.gdufs.edu.cn/jsxsd/"
	"\r\n"
	"Accept-Encoding: gzip, deflate\r\n"
	"Accept-Language: zh-CN,zh;q=0.8\r\n"
	"Cookie: _gscu_526019281=882896108k64eh79; JSESSIONID=80E45C39F94A1EC5A40C9D1407FBFE32"     //重启浏览器后,cookie的值要及时更新
	"\r\n"
	"\r\n"
	"USERNAME=666666"   //填写学号,例如666666
	"&PASSWORD=999999"  //填写密码,例如999999
	"&RANDOMCODE=";     //验证码这里先不写,程序运行时再进行字符串拼接

b.进入选课界面,把选课界面的Request Headers抄下来(如果是新开一个弹窗,刷新一下即可),用一个string变量保存:

 

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第7张图片

 

//进入选课界面的GET请求信息(Request Headers)
string request_choose_course =
	"GET /jsxsd/xsxk/xsxk_index?jx0502zbid=0B2F353D2507431F9D45F18AA941AFE0 HTTP/1.1\r\n"    
	"Host: jxgl.gdufs.edu.cn\r\n"
	"Connection: keep-alive\r\n"
	"Cache-Control: max-age=0\r\n"
	"Upgrade-Insecure-Requests: 1\r\n"
	"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36\r\n"
	"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"
	"Referer: http://jxgl.gdufs.edu.cn/jsxsd/xsxk/xklc_list?Ves632DSdyV=NEW_XSD_PYGL"
	"\r\n"
	"Accept-Encoding: gzip, deflate, sdch\r\n"
	"Accept-Language: zh-CN,zh;q=0.8\r\n"
	"Cookie: _gscu_526019281=882896108k64eh79; JSESSIONID=80E45C39F94A1EC5A40C9D1407FBFE32"      //重启浏览器后,cookie的值要及时更新
	"\r\n";

c.在选修选课界面,找到我们需要抢的课(这里以编译原理为例),点击“选课”按钮:

 

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第8张图片

同理,新建一个string变量,保存刚刚点击编译原理“选课”按钮后,提交的Request Headers:

 

//点击编译原理“选课”按钮的GET请求信息(Request Headers)
string request_compilers_principles =
	"GET /jsxsd/xsxkkc/xxxkOper?jx0404id=201620172009027 HTTP/1.1\r\n"    //每门课的jx0404id都不同,编译原理这门课对应的是201620172009027 
	"Host: jxgl.gdufs.edu.cn\r\n"
	"Connection: keep-alive\r\n"
	"Accept: */*\r\n"
	"X-Requested-With: XMLHttpRequest\r\n"
	"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36\r\n"
	"Referer: http://jxgl.gdufs.edu.cn/jsxsd/xsxkkc/comeInXxxk"
	"\r\n"
	"Accept-Encoding: gzip, deflate, sdch\r\n"
	"Accept-Language: zh-CN,zh;q=0.8\r\n"
	"Cookie: _gscu_526019281=882896108k64eh79; JSESSIONID=80E45C39F94A1EC5A40C9D1407FBFE32"       //重启浏览器后,cookie的值要及时更新
	"\r\n";

 

 

 

 

 

这样一来,上述的三个Request Headers已经保存好了,下面开始用程序模拟登录及进入选课系统。

以下代码用于启动Winsock,创建socket,实现模拟登陆以及模拟进入选课界面:

 

	/*启动Winsock*/
	WSADATA wd;
	WSAStartup(MAKEWORD(2, 2), &wd);     
	//SOCKET temp = socket(AF_INET, SOCK_STREAM, 0);  

	/*创建socket*/
	sock = socket(AF_INET, SOCK_STREAM, 0);  
	if (sock == INVALID_SOCKET){
		cout << "建立socket失败! 错误码: " << WSAGetLastError() << endl;
		return;
	}

	/*绑定:将本地地址 附加到 套接字上 以便能够有效地标识套接字*/
	sockaddr_in sa = { AF_INET };    //套接字地址,AF _!NET ,表示该socket位于Internet域;
	int n = bind(sock, (sockaddr*)&sa, sizeof(sa));
	if (n == SOCKET_ERROR){
		cout << "绑定失败! 错误码: " << WSAGetLastError() << endl;
		return;
	}

	struct hostent *p = gethostbyname(host);
	if (p == NULL){
		cout << "主机无法解析出ip! 错误代码: " << WSAGetLastError() << endl;
		return;
	}
	sa.sin_port = htons(80);
	memcpy(&sa.sin_addr, p->h_addr, 4);//   with some problems ???

	/*连接:当客户机要与网络中的服务器建立连接时,需要调用connect 函数*/
	n = connect(sock, (sockaddr*)&sa, sizeof(sa));
	if (n == SOCKET_ERROR)	{
		cout << "连接失败! 错误码: " << WSAGetLastError() << endl;
		return;
	}

	if (isLogin == false){
		/*登录教务系统*/
		if(isCookie == false){
			isCookie = true;
		}

		string verification_code = "";                    //验证码
		cout<<"请输入验证码: ";
		cin>>verification_code;

		string request_login_full = request_login + verification_code;

		cout<<"开始将登录请求(Request Headers)发送至对方服务器..."< 0){
			cout<<"以下是服务器的响应信息(Response Headers)"< 0){
			cout<

用刚刚的浏览器打开教务系统登陆界面,运行这段代码,并输入浏览器中显示的验证码:

 

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第9张图片

输入验证码完毕,点击Enter,程序输出登录请求和选课请求的响应情况:

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第10张图片

观察程序输出的登录请求和选课请求的响应情况,发现和之前浏览器的两次响应情况相吻合(除了响应时间Date)。

这说明本次程序登陆成功:

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第11张图片

登录请求的Response Headers,和控制台的响应信息相吻合

 

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第12张图片

进入选课界面请求的Response Headers,和控制台的响应信息相吻合

 

这时候,我们可以试试在浏览器中输入上面蓝色方框的地址,也就是登陆成功后重定向跳转后的地址。

我们会发现,即使没有在浏览器没有填写用户名、密码等登录信息,我们也可以直接进入学生个人中心:

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第13张图片

这是因为我们的程序已经成功模拟了浏览器的登录过程。

 

3.利用循环语句模拟点击“选课”按钮实现自动抢课

 

//开始抢课
	init_sec = time(0);   //计时开始
	int count_num = 0;

	/*循环抢课*/
	while (true){
		//每一次都重新建立连接
		SOCKET temp = socket(AF_INET, SOCK_STREAM, 0);  
		sock = temp;
		if (sock == INVALID_SOCKET){
			cout << "建立socket失败! 错误代码: " << WSAGetLastError() << endl;
			return;
		}

		sockaddr_in sa = { AF_INET };   

		struct hostent *p = gethostbyname(host);
		if (p == NULL){
			cout << "主机无法解析出ip! 错误代码: " << WSAGetLastError() << endl;
			return;
		}
		sa.sin_port = htons(80);
		memcpy(&sa.sin_addr, p->h_addr, 4);

		/*连接:当客户机要与网络中的服务器建立连接时,需要调用connect 函数*/
		n = connect(sock, (sockaddr*)&sa, sizeof(sa));
		if (n == SOCKET_ERROR)	{
			cout << "连接失败! 错误代码: " << WSAGetLastError() << endl;
			return;
		}

		count_num++;
		tot_sec = time(0) - init_sec;
		tot_sec_main = time(0) - main_time;

		//抢课:编译原理
		cout<<"开始将编译原理选课请求(Request Headers)发送至对方服务器..."< 0){
				cout<

4.代码说明:

 

①.每一次循环都重新新建了一个名为temp的临时socket实例,用于避免错误代码10054。

产生错误代码10054的原因:现有连接被远程主机强制关闭。

解决办法:每一次循环都重新生成一个新的socket实例,无论上次连接成功与否,每一次都重新建立一次连接。

②.每一次循环结束前,调用closesocket()函数及时释放socket对象,以免系统资源耗尽。

③.点击“选课”按钮时,不同课的Request Headers的唯一不同点就是jx0404id,例如编译原理这门课对应的是jx0404id=201620172009027。如果要抢多门课,只需修改jx0404id,其余的Request Headers信息直接复制即可。

④.控制台输出乱码情况有待解决。

 

5.注意:

①.如果浏览器重启,或者电脑重启后,所有的Request Headers中的cookie信息都要重新输入。

②.在登录请求的Request Headers中,不同用户账号密码的长度不同,Content-Length的值也不同,不同账号登录时要注意。

 

6.本程序完整代码及运行截图:

 

#include 
#include 
#include
#include 
#include 
#include 
#define max 20480

using namespace std;
#pragma comment(lib, "ws2_32.lib")

//全局变量
char host[500]="jxgl.gdufs.edu.cn";
char buf[1024];

int num = 1;
string allHtml;

int ignoreTimes = 1000;     //设置消息接收频率,目前默认每发送1000次接受一次
SOCKET sock;

bool isLogin = false;
bool isCookie = false;

int main_time;
int init_sec;     //计时开始的初始时间 
int tot_sec;     //耗时
int tot_sec_main;     //耗时

char recv_buf[1024];

//登录请求(Request Headers)
string request_login ="POST /jsxsd/xk/LoginToXkLdap HTTP/1.1\r\n"
	"Host: jxgl.gdufs.edu.cn\r\n"
	"Connection: keep-alive\r\n"
	"Content-Length: 54\r\n"
	"Cache-Control: max-age=0\r\n"
	"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"
	"Origin: http://jxgl.gdufs.edu.cn\r\n"
	"Upgrade-Insecure-Requests: 1\r\n"
	"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36\r\n"
	"Content-Type: application/x-www-form-urlencoded\r\n"
	"Referer: http://jxgl.gdufs.edu.cn/jsxsd/"
	"\r\n"
	"Accept-Encoding: gzip, deflate\r\n"
	"Accept-Language: zh-CN,zh;q=0.8\r\n"
	"Cookie: _gscu_526019281=882896108k64eh79; JSESSIONID=80E45C39F94A1EC5A40C9D1407FBFE32"
	"\r\n"
	"\r\n"
	"USERNAME=666666"   //填写学号,例如666666
	"&PASSWORD=999999"  //填写密码,例如999999
	"&RANDOMCODE=";

//进入选课界面的请求信息(Request Headers)
string request_choose_course =
	"GET /jsxsd/xsxk/xsxk_index?jx0502zbid=0B2F353D2507431F9D45F18AA941AFE0 HTTP/1.1\r\n"
	"Host: jxgl.gdufs.edu.cn\r\n"
	"Connection: keep-alive\r\n"
	"Cache-Control: max-age=0\r\n"
	"Upgrade-Insecure-Requests: 1\r\n"
	"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36\r\n"
	"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"
	"Referer: http://jxgl.gdufs.edu.cn/jsxsd/xsxk/xklc_list?Ves632DSdyV=NEW_XSD_PYGL"
	"\r\n"
	"Accept-Encoding: gzip, deflate, sdch\r\n"
	"Accept-Language: zh-CN,zh;q=0.8\r\n"
	"Cookie: _gscu_526019281=882896108k64eh79; JSESSIONID=80E45C39F94A1EC5A40C9D1407FBFE32"
	"\r\n";

//要抢的课:编译原理
string request_compilers_principles =
	"GET /jsxsd/xsxkkc/xxxkOper?jx0404id=201620172009027 HTTP/1.1\r\n"
	"Host: jxgl.gdufs.edu.cn\r\n"
	"Connection: keep-alive\r\n"
	"Accept: */*\r\n"
	"X-Requested-With: XMLHttpRequest\r\n"
	"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36\r\n"
	"Referer: http://jxgl.gdufs.edu.cn/jsxsd/xsxkkc/comeInXxxk"
	"\r\n"
	"Accept-Encoding: gzip, deflate, sdch\r\n"
	"Accept-Language: zh-CN,zh;q=0.8\r\n"
	"Cookie: _gscu_526019281=882896108k64eh79; JSESSIONID=80E45C39F94A1EC5A40C9D1407FBFE32"
	"\r\n";

//抢课
void grabCourse(){
	/*启动Winsock*/
	WSADATA wd;
	WSAStartup(MAKEWORD(2, 2), &wd);     
	//SOCKET temp = socket(AF_INET, SOCK_STREAM, 0);  

	/*创建socket*/
	sock = socket(AF_INET, SOCK_STREAM, 0);  
	if (sock == INVALID_SOCKET){
		cout << "建立socket失败! 错误码: " << WSAGetLastError() << endl;
		return;
	}

	/*绑定:将本地地址 附加到 套接字上 以便能够有效地标识套接字*/
	sockaddr_in sa = { AF_INET };    //套接字地址,AF _!NET ,表示该socket位于Internet域;
	int n = bind(sock, (sockaddr*)&sa, sizeof(sa));
	if (n == SOCKET_ERROR){
		cout << "绑定失败! 错误码: " << WSAGetLastError() << endl;
		return;
	}

	struct hostent *p = gethostbyname(host);
	if (p == NULL){
		cout << "主机无法解析出ip! 错误代码: " << WSAGetLastError() << endl;
		return;
	}
	sa.sin_port = htons(80);
	memcpy(&sa.sin_addr, p->h_addr, 4);//   with some problems ???

	/*连接:当客户机要与网络中的服务器建立连接时,需要调用connect 函数*/
	n = connect(sock, (sockaddr*)&sa, sizeof(sa));
	if (n == SOCKET_ERROR)	{
		cout << "连接失败! 错误码: " << WSAGetLastError() << endl;
		return;
	}

	if (isLogin == false){
		/*登录教务系统*/
		if(isCookie == false){
			isCookie = true;
		}

		string verification_code = "";                    //验证码
		cout<<"请输入验证码: ";
		cin>>verification_code;

		string request_login_full = request_login + verification_code;

		cout<<"开始将登录请求(Request Headers)发送至对方服务器..."< 0){
			cout<<"以下是服务器的响应信息(Response Headers)"< 0){
			cout<h_addr, 4);

		/*连接:当客户机要与网络中的服务器建立连接时,需要调用connect 函数*/
		n = connect(sock, (sockaddr*)&sa, sizeof(sa));
		if (n == SOCKET_ERROR)	{
			cout << "连接失败! 错误代码: " << WSAGetLastError() << endl;
			return;
		}

		count_num++;
		tot_sec = time(0) - init_sec;
		tot_sec_main = time(0) - main_time;

		//抢课:编译原理
		cout<<"开始将编译原理选课请求(Request Headers)发送至对方服务器..."< 0){
				cout<

 

 

 

 

 

程序完整运行情况如下图:

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第14张图片

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第15张图片

 

7.提速小Tips

ignoreTimes对应消息接收的频繁程度,将ignoreTimes的值设为1000,表示每发送1000次请求,才接受一次响应消息。

如下图所示,将ignoreTimes的值调高,可以显著提高每秒执行次数。

毕竟发送一次就马上接收一次,肯定不够发送千次才接收一次运行的快。

【教你抢课】C++网络编程之使用Winsock2实现教务系统自动抢课程序_第16张图片

 

 

 

 

你可能感兴趣的:(C++)