-
Notifications
You must be signed in to change notification settings - Fork 25
Tutorial Primer
本教程用一个简单的命令行示例,演示快速搭建一个自己的QQ客户端, 并且讲解WebQQ的登录流程
首先需要编译好lwqq库,并且安装到系统目录下.
一般lwqq的调用函数是没有顺序的.可以随意使用.不过登录过程必须 严格按照顺序来.登录成功就可以了.
引入头文件 <lwqq/lwqq.h>
然后创建一个LwqqClient:
#include <lwqq/lwqq.h> int main(int argc,char * argv) { LwqqClient* lc = lwqq_client_new("<username>", "<password>"); }
当然,用户名密码还请填自己的.
在继续写之前需要分清楚同步和异步.lwqq完全支持2种方式. 同步方式的请求和处理响应 都在同一段函数中.逻辑比较清晰,适合Demo程序.请求过程是阻塞的,一次只能发出一个请 求. 异步方式的处理响应是在回调函数中进行的,可以做到在等待网络传输时执行其它代 码.效率更高.
两种方式可以动态切换.一般推荐使用异步的书写方式,因为lwqq可以做到在不更改代码的 情况下,将异步方式转换为同步方式.但反之是不行的,因为同步方式的请求和处理是在同一 段函数中,无法分离.
虽然是在设计上提供两种方式, 但是同步工作模式维护比较少, 问题比较多. 推荐简单程 序可以使用同步模式. 复杂程序还是使用异步模式.
lwqq在初始化的时候使用异步,使用下面的方式来设置为同步:
LWQQ_SYNC_BEGIN(lc); <sync code area> LWQQ_SYNC_END(lc);
如果不结束同步方式的话,那么整个程序都是同步的了.
为了能够方便的调试,所以通过不同的输出等级来控制输出的信息的量.有0-5这 几个等级.每个等级的具体含义如下:
- 不输出额外的信息
- 输出poll轮循的响应
- 输出所有请求的响应
- 输出所有http请求的url和post
- 输出图像传输时候的详细信息
- 预留
通常推荐使用3或者是4的等级.使用下面的方式来设置输出等级:
lwqq_log_set_level(4);
然后就可以登录了,使用:
LwqqErrorCode err = LWQQ_EC_OK; lwqq_login(lc, LWQQ_STATUS_ONLINE, &err);
其中第二个参数是设置登录后为在线状态.第三个参数为错误代码.
这里err登录错误会有很多种类.其中需要特殊处理的是需要验证码的错误. 所以在这里用下面的方式来处理:
char vcode[5] = {0}; switch(err){ case LWQQ_EC_LOGIN_NEED_VC: lwqq_util_save_img(lc->vc->data, lc->vc->size, "verify.jpg", NULL); printf("Input Verify:"); scanf("%s",vcode); lc->vc->str = s_strdup(vcode); break; case LWQQ_EC_OK: printf("login successful\n"); break; default: printf("login failed\n"); break; }
这里需要注意的是,使用了 lwqq_util_save_img
来保存验证码,这个函数很好用的
然后提示你输入它,最后需要重新使用 lwqq_login
函数再次登录
于是我们可以用 goto label
或者是外面加一个while循环:
while(1){ <login> <process error code> }
时刻注意我们是在写C程序,而不是其它,所以要注意内存的释放.:
if(lwqq_client_logined(lc)) lwqq_logout(lc, NULL); lwqq_client_free(lc);
最后所有代码如同 :download:`login.c <example/login.c>` 所示.
简单的登录过程先说到这里,下面用以下的命令来编译:
$ gcc -o login_demo login.c -llwqq
执行 ./login_demo
就可以看到登录成功的提示啦:
[login stage 1:get webqq version] https://ssl.ptlogin2.qq.com/check?uin=<qqnum>&appid=1003903 [Jul 26 12:23:40] ERROR[11263]: type.c:507 lwqq_set_cookie: No this cookie:confirmuin Get response verify code: ptui_checkVC('1','1c4dfb254597d9f944068adf2a8ba4dbca9159327cacf18c','\x00\x00\x00\x00\x95\x1a\x82\x5c'); We need verify code image: 1c4dfb254597d9f944068adf2a8ba4dbca9159327cacf18c [Jul 26 12:23:40] WARNING[11263]: login.c:666 login_stage_3: Need to enter verify code Input Verify:sksa [login stage 1:get webqq version] https://ssl.ptlogin2.qq.com/login?u=<qqnum>&p=348BD6881195F3DE3DC9CCB022837DF6&verifycode=sksa&webqq_type=10&remember_uin=1&aid=1003903&login2qq=1&u1=http%3A%2F%2Fweb.qq.com%2Floginproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=4-15-15865&mibao_css=m_webqq&t=1&g=1&js_ver=10034 [Jul 26 12:23:53] ERROR[11263]: type.c:507 lwqq_set_cookie: No this cookie:superuin [Jul 26 12:23:53] ERROR[11263]: type.c:507 lwqq_set_cookie: No this cookie:superkey [Jul 26 12:23:53] ERROR[11263]: type.c:507 lwqq_set_cookie: No this cookie:ETK [Jul 26 12:23:53] ERROR[11263]: type.c:507 lwqq_set_cookie: No this cookie:airkey ptuiCB('0','0','https://ssl.ptlogin2.qq.com/pt4_302?u1=http%3A//ptlogin4.web2.qq.com/check_sig%3Fpttype%3D1%26uin%3D<qqnum>%26service%3Dlogin%26nodirect%3D0%26ptsig%3D4WnYCeKCWEjjcs6UWXNoyu6bFbg5pTJyc4lEkLrMTQM_%26s_url%3Dhttp%253a%252f%252fweb.qq.com%252floginproxy.html%253flogin2qq%253d1%2526webqq%255ftype%253d10%26f_url%3D%26ptlang%3D2052%26ptredirect%3D100%26aid%3D1003903%26daid%3D164%26j_later%3D0%26low_login_hour%3D0%26regmaster%3D0','0','登录成功!', 'd3dd'); r=%7B%22status%22%3A%22online%22%2C%22ptwebqq%22%3A%22a54ff83bd9a03825b36837735b2cad136184f28253caa3ad32475a2d847ad6bc%22%2C%22passwd_sig%22%3A%22%22%2C%22clientid%22%3A%2247795973%22%2C%20%22psessionid%22%3Anull%7D {"retcode":0,"result":{"uin":<qqnum>,"cip":1858033426,"index":1075,"port":48293,"status":"online","vfwebqq":"205b9fb2165f80abd7936316b4da66d2ac4339a39c21ea19d9bed03070a9bc40d1c6113a3fe9a0bc","psessionid":"8368046764001f636f6e6e7365727665725f7765627171403137322e32332e3133342e32313600007d5e00000686026e04005c821a956d0000000a40707949306d696c44706d00000028205b9fb2165f80abd7936316b4da66d2ac4339a39c21ea19d9bed03070a9bc40d1c6113a3fe9a0bc","user_state":0,"f":0}} login successful
初始信息最重要的是三个list:friends list, group list, discu list以及QQ号.QQ号非 常的麻烦,因为webqq在获取的初始信息中没有提供QQ号,需要对每一个好友发送一个请求获 取QQ号.所以如果好友特别多的话,就产生了大量的网络通讯.并且腾讯服务器对于QQ号的获 取频率有限制,很快就会超过限制,从而获取失败.通常使用本地的数据库来缓存QQ号,好在 lwqq已经内置了数据库的支持,可以方便的完成这个功能
大部分网络请求都需要获取错误号,以便判断是否成功的执行,如果错误那么错误又是什么.
在旧的API接口中,通常使用一个 LwqqErrorCode *err
的参数来获取错误号.但是新的API
接口中没有相应的参数.这里需要使用异步的书写方式,才能成功获取到,因为此处没有使用
LWQQ_SYNC_END(lc)
来关闭同步方式.所以虽然这里书写方式是异步的,但是其实依然是同
步调用.
那么这里为什么需要首先介绍这种混合模式而不直接开启异步呢?因为这里和异步方式设计 的历史原因有关系.
一开始lwqq是同步的,但是对于pidgin这种的明显是不行的.所以需要异步支持.细分为请求 过程和处理过程.通常UI程序的主线程都有一个事件循环,所有的UI事件都在循环中运行.于 是这里就可以把请求过程和处理过程都放在这个事件循环中.当请求时,这个事件就被移到 了队列的末尾,当收到服务器的响应后,再去执行对应的处理过程.所以到此为止,已经是异 步化了.
但是问题不仅如此,在一些系统的libcurl中,如ubuntu,在查询dns的时候依然是同步的.也 就是说,在查询结束之前,都会一直阻塞.因此这里就阻塞了事件循环,使得UI事件不能及时 的处理,从而出现了卡UI的现象了.那么这里怎么办?通过创建一个新的线程,在上面建立另 外一个事件循环,如libev,然后把网络请求的过程全部放在新线程中.这样,就算dns查询阻 塞了,也不会阻塞主线程,因此就解决了卡UI的问题了.
接下来,新问题是处理过程应该是放在新线程中还是主线程中?答案是前者,因为一些事件循 环,如gtk的,要求一些操作UI的代码必须在主线程中执行,在别的线程中执行会出现竞争问 题,也就是说,它不是线程安全的.处理过程经常涉及到更新UI,所以必须放在主线程中执行.
到此,整个异步设计就完成了.需要注意的是,设计中要求必须有两个事件循环分别运行在主 线程,和子线程中.
因为这里主线程还没有建立事件循环,当请求过程在子线程中执行时,主线程没有什么代码 执行的了,于是就结束了.整个程序就退出了.因此这里还不能简单的打开异步.
使用如下的代码可以获取好友列表:
LwqqAsyncEvent* ev = lwqq_info_get_friends(lc,NULL,NULL);
其中第二个参数是hash函数,设置为NULL来使用内置的版本.因为hash函数经常变动嘛,尝试
使用多种版本的hash函数就交给调用者来实现了.
很多lwqq的函数都会返回一个 LwqqAsyncEvent
结构.这个结构非常的好用.当收到服务器
的响应之后,其中result的结果就可用了.
- 如果
ev->result==0
那么表示没有错误发生. - 如果
ev->result>0
表示webqq服务器发回的错误号.例如50就表示hash错误.还有102,118 表示掉线等等. - 如果
ev->result<0
表示lwqq的错误,例如网络不可用啦,或者是超时等等.
可以通过错误号来判断是否执行成功:
if(ev->result != LWQQ_EC_OK){ if(ev->result == LWQQ_EC_HASH_WRONG){ printf("hash wrong\n"); exit(0); }else{ printf("could not get friends info\n"); exit(-1); } }
要是所有错误号都处理的话是很辛苦的.所以一般只处理常见的错误就可以了.
最后,需要使用特殊的 lwqq_async_event_finish(ev)
来正确的释放内存.
当然,上面的方式完全没有体现出 LwqqAsyncEvent
的威力,所以在下面使用另外的一种方
式来调用:
ev = lwqq_info_get_group_name_list(lc, NULL); lwqq_async_add_event_listener(ev, _C_(p,common_error_process,ev)); ev = lwqq_info_get_discu_name_list(lc); lwqq_async_add_event_listener(ev, _C_(p,common_error_process,ev)); static void common_error_process(LwqqAsyncEvent* ev) { if(ev->result!=LWQQ_EC_OK){ printf("get group list failed\n"); } }
lwqq_async_add_event_listener
为一个事件添加了回调函数.其中回调函数使用 _C_
宏
封装了一下. _C_
宏被称为不定参回调,它的好用不亲自尝试一下是体会不到的:).它的第
一个参数是回调函数模板,例如p表示只有一个指针,即 (void*)
,2p表示有两个指针作为参
数,即 (void*,void*)
,2pi表示两个指针后一个int参数.最多到4p.其实展开之后是对应的
vp_func_##
所以要是你觉得参数还不够的话,可以自己写一个 vp_func_2pl3i
的参数模
板.表示 (void*,void*,long,int,int,int)
的参数列表.因为指针的长度都是一样的,所以
无论是 LwqqAsyncEvent*
还是 LwqqClient*
都可以用等数量的p
_C_
宏的第二个参数是回调函数的入口,剩下的就是依次排列的实参列表了.要是你用了4p
但是后面只写了2个指针,或者用了2p但是写了3个指针会怎么样呢?那就只能呵呵了.因为没
有编译器检查(不知道该怎么写,就像printf一样的检查参数).所以不会报错,但是运行的时
候就会出错了.而且都是非常诡异的错误,基本看不出来是怎么回事.所以写的时候一定要匹
配.否则很难发现错误了.
另外 LwqqAsyncEvent
还可以绑定多个回调函数.另外使用了`add_event_listener`之后就
不用管内存了,会正确的释放内存的.
最后,就可以开启poll线程,接受webqq服务器发过来的消息了.此时会创建一个新的线程.所 以同样的,需要让主线程也定期的执行检查过程,否则就直接退出程序了:
LwqqRecvMsgList *l = lc->msg_list; l->poll_msg(l,0);
此时就会新开一个线程用于poll轮循.然后在主函数中继续执行检查循环,如下:
while(1){
LwqqRecvMsg *recvmsg;
pthread_mutex_lock(&l->mutex);
if (TAILQ_EMPTY(&l->head)) {
/* No message now, wait 100ms */
pthread_mutex_unlock(&l->mutex);
usleep(100000);
continue;
}
recvmsg = TAILQ_FIRST(&l->head);
TAILQ_REMOVE(&l->head,recvmsg, entries);
pthread_mutex_unlock(&l->mutex);
if(lwqq_mt_bits(recvmsg->msg->type) == LWQQ_MT_MESSAGE){
LwqqMsgMessage* msg = (LwqqMsgMessage*)recvmsg->msg;
LwqqMsgContent* c;
TAILQ_FOREACH(c, &msg->content, entries){
if(c->type == LWQQ_CONTENT_STRING){
printf("%s",c->data.str);
}
}
printf("\n");
}
lwqq_msg_free(recvmsg->msg);
s_free(recvmsg);
}
从第2-12行是使用互斥锁来获取一个RecvMsg对象,第13行检查recvmsg的类型是否是Message类型, 然后强制转换结构体并取得Content列表.最后把所有是字符串的打印出来.
最后所有代码如同 :download:`info.c <example/info.c>` 所示.