webQQ之关键:poll机制,线程&异步通信

webQQ之关键:poll机制,线程&异步通信

1)当Login2成功登陆后,立刻兴冲冲的想通过webQQ发信,结果却让很多人失望:即使没返回错误信息,发送也是不成功的,因为还缺了一步,那就是webQQ的POLL。
一切都源于webQQ保持连线的机制:Login2成功登陆后,要保持webQQ不断线,就需要不间断的poll,来获取服务器传过来的消息;即使你对返还的信息不感兴趣,你也得不断的poll,因为大约数分钟后,服务器如果没有收到用户的poll请求,会认为QQ用户已经下线或断线,继而会停掉一切相关的webQQ服务。
抓包:
POST http://d.web2.qq.com/channel/poll2 HTTP/1.1
Referer:http://d.web2.qq.com/proxy.html
r={"clientid":"【clientid】","psessionid":"【psessionid】","key":0,"ids":[]}&clientid=【clientid】&psessionid=【psessionid】

C++ (with libcurl)源程序
string WebQQ_poll()
{
 // 提取QQ登录信息:
 char buf[1024];
 size_t found,found2;
 string clientid,psessionid;
 ifstream QQLoginfile("D:\\SparkHo\\QQLogin.txt"); 
 if(!QQLoginfile)
 {
  QQLoginfile.close();
  clientid = "";
  psessionid = "";
 }
 else
 {
  QQLoginfile.getline(buf,1024);
  string QQLogin = (string) buf;
  QQLoginfile.close();
  found  = QQLogin.find("ClientID");
  found2 = QQLogin.find(";",found+9,1);
  clientid = QQLogin.substr(found+9,found2-found-9);
  found  = QQLogin.find("psessionid");
  found2 = QQLogin.find(";",found+11,1);
  psessionid = QQLogin.substr(found+11,found2-found-11);
 }
 // 初始化libcurl
 CURLcode return_code;
 return_code = curl_global_init(CURL_GLOBAL_WIN32);
 if (CURLE_OK != return_code) return "";
 // 获取easy handle
 CURL *easy_handle = curl_easy_init();
 if (NULL == easy_handle)
 {  
  curl_global_cleanup();
  return "";
 }
 string buffer;
 string post_url = "http://d.web2.qq.com/channel/poll2";
 string referer_url = "http://d.web2.qq.com/proxy.html";
 string base = "{\"clientid\":\""+clientid+"\",\"psessionid\":\""+psessionid+"\",\"key\":0,\"ids\":[]}";
 string urlencode = curl_easy_escape(easy_handle,base.c_str(),0);
 string fields = "r=" + urlencode + "&clientid=" + clientid + "&psessionid=" + psessionid;
 // 设置easy handle属性
 curl_easy_setopt(easy_handle, CURLOPT_URL, post_url.c_str());
 curl_easy_setopt(easy_handle, CURLOPT_REFERER, referer_url.c_str());
 curl_easy_setopt(easy_handle, CURLOPT_POST, 1);
 curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, fields.c_str());
 curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, writer);
 curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, &buffer);
 // 提交保存的cookie 
 curl_easy_setopt(easy_handle, CURLOPT_COOKIEFILE,"D:\\SparkHo\\cookie_login.txt");
 // 执行数据请求
 return_code = curl_easy_perform(easy_handle);
  if (CURLE_OK != return_code)
    buffer = "";
 // 释放资源
 curl_easy_cleanup(easy_handle);
 curl_global_cleanup();
 return buffer;
}
poll的返回值,也是一个JSON结构,如:{"retcode":103,"errmsg":""} {"retcode":121,"t":"0"} {"retcode":100006,"errmsg":""}
返回103、121,代表连接不成功,需要重新登录;
返回102,代表连接正常,此时服务器暂无信息;
返回0,代表服务器有信息传递过来:包括群信、群成员给你的发信,QQ好友给你的发信。
另外,此程序必须考虑另一种情况:如果网络断线,curl_easy_perform()将返回CURLE_COULDNT_RESOLVE_HOST,返回值buffer为了避免无效赋值:必须赋值为空字符串。
发生错误或有信息到来之时,poll马上返回相应的JSON,占用的时间很短;
但没有信息过来的时候,poll占用的时间一般比较长,不确定,时快时慢。

2)如果等poll获得返回值之后,再去响应用户其他的动作,会感觉有点卡。于是我们另开一个线程,循环不断的poll,而不影响主程序的正常响应,这就是所谓的异步通信;如获得有效信息,即可通过SendMessage消息机制传回主窗口。

线程程序:
DWORD WINAPI ThreadOverPoll(LPVOID n)
{
 size_t found;
 HWND hfwnd = FindWindow(NULL,"webQQ专用");
 while(thread_run)
 {
  string poll = WebQQ_poll();
  if(poll == "")
  {
   // 网络断线,暂停5秒
   Sleep(5000);
  }
  else
  {
   found = poll.find(",",11,1);
   string ret = poll.substr(11,found-11);   
   int i = atoi(ret.c_str());
   // webQQ断线重连
   if(i == 103 || i == 121 || i == 100006)
    WebQQ_restart();
   // 把有效的信息,传回主窗口
   if(atoi( i == 0 )
    SendMessage(hfwnd,WM_QPOLL,(WPARAM)poll.c_str(),0);
  }  
 }
 return (DWORD)0;
}
注:
1)当网络断线的时候,WebQQ_poll()返回空字符串,线程不作处理,暂停5秒;
2)当网络再度连上的时候,可能webQQ已经断线,WebQQ_poll()返回121或103,这时候启动WebQQ_restart(),进行重连;
3)有一种情况,需要窗口去处理,当QQ用户在其他地方登陆的时候,返回值:
{"retcode":0,"result":[{"poll_type":"kick_message","value":{"way":"poll","show_reason":1,"reason":"您的账号在另一地点登录,您已被迫下线。如有疑问,请登录 safe.qq.com 了解更多。"}}]}

线程的启用:
全局变量int thread_run初始赋值为0
......
  thread_run=1;
  hThread1 = CreateThread(NULL, 0, ThreadOverPoll, (LPVOID)1, 0, &thread_ID); 
......
线程的关闭:
......
thread_run=0;
for (;;)
{
  GetExitCodeThread(hThread1, &exitCode1);
  GetExitCodeThread(hThread2, &exitCode2);
  if ( exitCode1 != STILL_ACTIVE && exitCode2 != STILL_ACTIVE )
     break;
}
CloseHandle(hThread1);
CloseHandle(hThread2);
......

附录一:
重新登录:C++ (with libcurl)源程序
string WebQQ_restart()
{
size_t found;
char userno[100],pass[100];
// 从主窗口获取用户登录信息
HWND hfwnd = FindWindow(NULL,"webQQ专用");
GetDlgItemText(hfwnd,Edit_User,userno,100);
GetDlgItemText(hfwnd,Edit_Pass,pass,100);
string VeriCode = WebQQ_check(userno);
// 获取验证码后,变更主窗口信息
SetWindowText(GetDlgItem(hfwnd,Edit_Veri),VeriCode.c_str());
string PassCode = qqMD5(pass,VeriCode);
string ptwebqq = WebQQ_login(userno,PassCode,VeriCode);
string LogJson = WebQQ_login2();
found = LogJson.find(",",11,1);
string ret = LogJson.substr(11,found-11);
if(atoi(ret.c_str())==0)
{
string follow1 = WebQQ_buddy();
string follow2 = WebQQ_group();
//string follow3 = WebQQ_gface_sig();
fstream QQsendfile("D:\\SparkHo\\alert_today.txt",ios::out|ios::trunc);
QQsendfile.close();
alert_no = 0;
}
return LogJson;
}

附录二:
上面提到的webQQ被踢的情况(用户在其他地方登陆的时候),这时候poll的返回值:
{"retcode":0,"result":[{"poll_type":"kick_message","value":{"way":"poll","show_reason":1,"reason":"您的账号在另一地点登录,您已被迫下线。如有疑问,请登录 safe.qq.com 了解更多。"}}]}
该信息,通过SendMessage消息机制传回主窗口,窗口相应的处理程序(启动重新登录):
......
switch (message)
{
  case WM_QPOLL: // 收到了自定义的信息,来自POLL
  {
   MessageBeep(MB_ICONINFORMATION);
   string poll = (char *)wParam; 
   found  = poll.find("poll_type");
   found2 = poll.find("\":\"",found+1,3);
   found3 = poll.find("\"",found2+3,1);
   string ret = poll.substr(found2+3,found3-found2-3);

   if(ret.compare("kick_message")==0)
   {
      Sleep(60000);  //暂停60秒
      string relogin = WebQQ_restart();
   }
   if(ret.compare("message")==0) 
   {
     //处理QQ好友的来信 ... 
   }
   if(ret.compare("group_message")==0) 
   {
     //处理QQ群的来信 ...
   }
   ......
  }
  ......
}

......


你可能感兴趣的:(webQQ之关键:poll机制,线程&异步通信)