本文接上一篇文章《张小方:从零实现一款12306抢票软件(一)》。
当然,这里需要说明一下的就是,由于全国的火车站点信息文件比较大,我们程序解析起来时间较长,加上火车站编码信息并不是经常变动,所以,我们我们没必要每次都下载这个station_name.js,所以我在写程序模拟这个请求时,一般先看本地有没有这个文件,如果有就使用本地的,没有才发http请求向12306服务器请求。这里我贴下我请求站点信息的程序代码(C++代码):
/**
* 获取全国车站信息
* @param si 返回的车站信息
* @param bForceDownload 强制从网络上下载,即不使用本地副本
*/
bool GetStationInfo(vector<stationinfo>& si, bool bForceDownload = false);
#define URL_STATION_NAMES "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9053"
bool Client12306::GetStationInfo(vector<stationinfo>& si, bool bForceDownload/* = false*/)
{
FILE* pfile;
pfile = fopen("station_name.js", "rt+");
//文件不存在,则必须下载
if (pfile == NULL)
{
bForceDownload = true;
}
string strResponse;
if (bForceDownload)
{
if (pfile != NULL)
fclose(pfile);
pfile = fopen("station_name.js", "wt+");
if (pfile == NULL)
{
LogError("Unable to create station_name.js");
return false;
}
CURLcode res;
CURL* curl = curl_easy_init();
if (NULL == curl)
{
fclose(pfile);
return false;
}
//URL_STATION_NAMES
curl_easy_setopt(curl, CURLOPT_URL, URL_STATION_NAMES);
//响应结果中保留头部信息
//curl_easy_setopt(curl, CURLOPT_HEADER, 1);
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
//设定为不验证证书和HOST
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
res = curl_easy_perform(curl);
bool bError = false;
if (res == CURLE_OK)
{
int code;
res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
if (code != 200)
{
bError = true;
LogError("http response code is not 200, code=%d", code);
}
}
else
{
LogError("http request error, error code = %d", res);
bError = true;
}
curl_easy_cleanup(curl);
if (bError)
{
fclose(pfile);
return !bError;
}
if (fwrite(strResponse.data(), strResponse.length(), 1, pfile) != 1)
{
LogError("Write data to station_name.js error");
return false;
}
fclose(pfile);
}
//直接读取文件
else
{
//得到文件大小
fseek(pfile, 0, SEEK_END);
int length = ftell(pfile);
if (length < 0)
{
LogError("invalid station_name.js file");
fclose(pfile);
}
fseek(pfile, 0, SEEK_SET);
length++;
char* buf = new char[length];
memset(buf, 0, length*sizeof(char));
if (fread(buf, length-1, 1, pfile) != 1)
{
LogError("read station_name.js file error");
fclose(pfile);
return false;
}
strResponse = buf;
fclose(pfile);
}
/*
返回结果为一个js文件,
var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2"
*/
//LogInfo("recv json = %s", strResponse.c_str());
OutputDebugStringA(strResponse.c_str());
vector<string> singleStation;
split(strResponse, "@", singleStation);
size_t size = singleStation.size();
for (size_t i = 1; i < size; ++i)
{
vector<string> v;
split(singleStation[i], "|", v);
if (v.size() < 6)
continue;
stationinfo st;
st.code1 = v[0];
st.hanzi = v[1];
st.code2 = v[2];
st.pingyin = v[3];
st.simplepingyin = v[4];
st.no = atol(v[5].c_str());
si.push_back(st);
}
return true;
}
这里用了一个站点信息结构体stationinfo,定义如下:
//var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2
struct stationinfo
{
string code1;
string hanzi;
string code2;
string pingyin;
string simplepingyin;
int no;
};
因为我们这里目的是为了模拟http请求做买火车票相关的操作,而不是技术方面本身,所以为了快速实现我们的目的,我们就使用curl库。这个库是一个强大的http相关的库,例如12306服务器返回的数据可能是分块的(chunked),这个库也能帮我们组装好;再例如,服务器返回的数据是使用gzip格式压缩的,curl也会帮我们自动解压好。所以,接下来的所有12306的接口,都基于我封装的curl库一个接口:
/**
* 发送一个http请求
*@param url 请求的url
*@param strResponse http响应结果
*@param get true为GET,false为POST
*@param headers 附带发送的http头信息
*@param postdata post附带的数据
*@param bReserveHeaders http响应结果是否保留头部信息
*@param timeout http请求超时时间
*/
bool HttpRequest(const char* url, string& strResponse, bool get = true, const char* headers = NULL, const char* postdata = NULL, bool bReserveHeaders = false, int timeout = 10);
函数各种参数已经在函数注释中写的清清楚楚了,这里就不一一解释了。这个函数的实现代码如下:
bool Client12306::HttpRequest(const char* url,
string& strResponse,
bool get/* = true*/,
const char* headers/* = NULL*/,
const char* postdata/* = NULL*/,
bool bReserveHeaders/* = false*/,