libjingle是gtalk客户端所使用的基础库,主要分为以下几个模块:
base // 一些网络方面的基础库的封装
xmllite // 因为xmpp协议的内容是以xml为基础的,所以这个模块是对xml解析库(expat)的一些封装
xmpp // xmpp协议的实现
p2p // p2p的实现,包括一些nat和firewall穿越的技术
session // gtalk对于声音和文件等的实现
example // 几个例子
由于自己以前对于c++网络库了解甚少,所以我打算从例子出发,然后通过例子的程序的前后调用关系将各个类串联起来。
下面是对login example的一个分析:
1. login_main.cc
这个是程序的入口。 主要是开一个子线程(XmppThread)来获得用户输入的信息登录,最后如果输入quit就结束,这边一般主线程结束了子线程也会结束。
int main(int argc, char **argv) {
printf("Auth Cookie: ");
fflush(stdout);
char auth_cookie[256];
scanf("%s", auth_cookie);
char username[256];
scanf("%s", username);
// Start xmpp on a different thread
XmppThread thread;
thread.Start();
buzz::XmppClientSettings xcs;
xcs.set_user(username);
xcs.set_host("gmail.com");
xcs.set_use_tls(false);
xcs.set_auth_cookie(auth_cookie);
xcs.set_server(talk_base::SocketAddress("talk.google.com", 5222));
thread.Login(xcs);
// Use main thread for console input
std::string line;
while (std::getline(std::cin, line)) {
if (line == "quit")
break;
}
return 0;
}
XmppThread:
这个类继承了base的Thread,XmppPumpNotify,MessageHandler
class XmppThread:
public talk_base::Thread, XmppPumpNotify, talk_base::MessageHandler {
public:
XmppThread();
~XmppThread();
buzz::XmppClient* client() { return pump_->client(); }
void ProcessMessages(int cms);
void Login(const buzz::XmppClientSettings & xcs);
void Disconnect();
private:
XmppPump* pump_;
// implements XmppPumpNotify
void OnStateChange(buzz::XmppEngine::State state);
// implements MessageHandler
void OnMessage(talk_base::Message* pmsg);
};
#endif // _XMPPTHREAD_H_
Thread: 包装了win32 的thread和posix的thread来统一接口。继承自一个MessageQueue, 因此具有循环处理消息的机制,主要函数如下:
bool Thread::ProcessMessages(int cmsLoop) {
uint32 msEnd;
if (cmsLoop != kForever)
msEnd = GetMillisecondCount() + cmsLoop;
int cmsNext = cmsLoop;
while (true) {
Message msg;
if (!Get(&msg, cmsNext))
return false;
Dispatch(&msg);
if (cmsLoop != kForever) {
uint32 msCur = GetMillisecondCount();
if (msCur >= msEnd)
return true;
cmsNext = msEnd - msCur;
}
}
}
用户可以使用Message::Queue继承下来的Post()方法来Post消息到内部的message queue, 然后内部的message queue就Get到这个消息并且Dispatch它。
virtual void Post(MessageHandler *phandler, uint32 id = 0,
MessageData *pdata = NULL, bool time_sensitive = false);
这边Post消息的时候需要传入一个messagehandler的指针来处理消息,而dispatch在默认的实现中正是去回调messagehandler的消息处理函数。
了解了这些之后我们回到例子,来看看XmppThread的几个函数:
Login和Disconnect等命令函数将消息Post到Thread的message queue,并且制定了handler为this指针。 然后Thread分派消息后XmppThread::OnMessage就对不同消息进行处理,最后委托给XmppPump去完成相应的任务。
void XmppThread::Login(const buzz::XmppClientSettings& xcs) {
Post(this, MSG_LOGIN, new LoginData(xcs));
}
void XmppThread::Disconnect() {
Post(this, MSG_DISCONNECT);
}
void XmppThread::OnMessage(talk_base::Message* pmsg) {
if (pmsg->message_id == MSG_LOGIN) {
assert(pmsg->pdata);
LoginData* data = reinterpret_cast<LoginData*>(pmsg->pdata);
pump_->DoLogin(data->xcs, new XmppSocket(false), new XmppAuth());
delete data;
} else if (pmsg->message_id == MSG_DISCONNECT) {
pump_->DoDisconnect();
} else {
assert(false);
}
}
XmppSocket:
在pump登录的时候创建了一个XmppSocket。XmppSocket主要是对AsyncSocket的一个封装,它将获得当前的Thread,并且在当前Thread的中建立一个AsyncSocket。其实Thread也是一个SocketServer:
class SocketServer : public SocketFactory {
public:
// Sleeps until:
// 1) cms milliseconds have elapsed (unless cms == kForever)
// 2) WakeUp() is called
// While sleeping, I/O is performed if process_io is true.
virtual bool Wait(int cms, bool process_io) = 0;
// Causes the current wait (if one is in progress) to wake up.
virtual void WakeUp() = 0;
};
} // namespace talk_base
socket server是个创建异步socket的factory,并且自己能够不断地去wait()网络异步事件。因为是客户端,所以主要是connected,read,write等网络异步事件。WakeUp是那种致命药丸的概念,即吃一下就停了(挂了)。 当然还有一些singnal/slot机制等,详细的在以后的文章中会解释。
所以XmppSocket类主要是可以写一些回调函数来处理网络异步事件。
XmppPump:
这是一个fsm状态机的子类。
在libjingle中最基础的fsm被称为一个Task:
class Task {
public:
Task(Task *parent);
virtual ~Task() {}
int32 get_unique_id() { return unique_id_; }
void Start();
void Step();
int GetState() const { return state_; }
bool HasError() const { return (GetState() == STATE_ERROR); }
bool Blocked() const { return blocked_; }
bool IsDone() const { return done_; }
int64 ElapsedTime();
......
Task具有父子结构,TaskRunner 也是一个Task,但是他是Task的最原始的父亲
Task::Task(Task *parent)
: state_(STATE_INIT),
parent_(parent),
blocked_(false),
done_(false),
aborted_(false),
busy_(false),
error_(false),
child_error_(false),
start_time_(0),
timeout_seconds_(0),
timeout_time_(0),
timeout_suspended_(false) {
children_.reset(new ChildSet());
runner_ = ((parent == NULL) ?
reinterpret_cast<TaskRunner *>(this) :
parent->GetRunner());
......
class TaskRunner : public Task, public sigslot::has_slots<> {
public:
TaskRunner();
virtual ~TaskRunner();
virtual void WakeTasks() = 0;
// This method returns the current time in 100ns units since 1/1/1601. This
// Is the GetSystemTimeAsFileTime method on windows.
virtual int64 CurrentTime() = 0 ;
void StartTask(Task *task);
void RunTasks();
void PollTasks();
void UpdateTaskTimeout(Task *task);
// dummy state machine - never run.
virtual int ProcessStart() { return STATE_DONE; }
......
所以TaskRunner是没有parent的,他的runner是自己,而其他子的Task都是runner都是最顶层的TaskRunner。
在这里XmppPump就是一个顶层的TaskRunner,他创建一个XmppClient的Task并且双方加入到父子结构中,而XmppClient的Task就是Xmpp协议的一个实现了,当然需要传入socket和其他一些回调参数。 最后启动Task,将runner wakeup。
void XmppPump::WakeTasks() {
talk_base::Thread::Current()->Post(this);
}
因为handler为this,所以handle消息启动runner。
void XmppPump::OnMessage(talk_base::Message *pmsg) {
RunTasks();
}
总结:
以上是Login example的基本处理过程,这边主要有以下几个概念:
1. Thread 蕴含一个消息处理机制。
2. Socket事件的异步处理。 Thread是一个SocketServer, 会创建一个Socket,并且对其进行异步网络事件的回调处理。
3. fsm的机制,主要是task和taskRunner的一些概念和关系。
后续:
接下来的学习历程会比较深入地解析以上三个概念。 [email protected]。希望有兴趣的朋友和我联系,多多学习,呵呵!