受到微信、QQ等聊天工具的其他,结合着自己所学的系统、网络编程,我打算实现一个自己的聊天系统。
一、来先爬到巨人的肩上
当今发达的互联网,总能找到可以拿来学习和借鉴的各种工具,我调研了QQ的实现和微信的实现,用来实现我自己的聊天系统。
腾讯旗下QQ底层运用UDP协议实现传输。使用UDP协议的原因是因为UDP无连接,传输速率快,占用系统开销小,但是UDP协议的缺陷是不可靠,它没有TCP的可靠性保障,那么为什么还要用它呢?
因为QQ的用户量之大,它的服务器设计是海量的应用,有时候一台服务器可能要容纳几十万并发链接,这时UDP的高效性就展现了很大的优势,而它的可靠性则有一些特定的算法来保证。
来看看QQ的聊天机制:
1.我们平常下载的qq只是一个客户端,QQ的服务端是24小时开着的。服务端的ip是固定的,并且有固定的端口号来等待接受消息。
2.登录的时候,客户端会把你的ip和端口号发给服务端,然后服务器端把你的ip和端口广播给所有你的好友,通知你已上线。
3.好友之间的聊天就是点对点,因为服务器已经把你的ip和端口号发给你的好友,那么你的好友就直接能接收你的消息了。
UDP协议只管发送,不管能否接收到,那么你的好友怎么知道你下线了?
下线的时候会给服务器端发送一个消息,服务器端就会把你的ip从列表中删除,并且广播给你的好友,通知他们你已经下线。
腾讯QQ如何保证可靠性呢?
腾讯采用上层保证了可靠性。客户端发送一个数据包被服务器收到后,服务器会模拟TCP发送一个包来确认收到,由此来确认发送消息的可靠性。因为这样,我们聊天的时候经常会看到消息发送失败,但是后面却会受到消息。
二、思维导图
大体的流程我在下面画图阐述:
使用两个线程,一个用来读取client发送来的数据,一个从数据池里拿出数据广播给每个客户端。
这两个线程遵循互斥、同步关系。(用信号量来保持)
数据池实现:用环形队列。(这里是生产者消费者模型的应用)
我也想实现好友信息。这时候用到了STL库中提供的容器map很合适,拿ip当键值(key)在合适不过了。
客户端是认为是一对一,服务器端认为是一对多。
还有一点是发送的时候还可能包含其他信息,不然就只有匿名的消息,这样太不好了 。(用Jsoncpp库序列化这些字符串)。
三、调研各个模块所需数据
1.首先调研一个Jsoncpp库,(JavaScriptObjectNation):
由于它易于理解和编写,而且也易于机器解析和生成,称为了数据交换的理想语言。
对象是一个无序的“名称/值”的集合,以’{‘开始, 以’}’结束中间以 逗号隔开 string = value。
值(value)
可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。
字符串(string)
是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。
详细请查看:http://blog.csdn.net/zhounanzhaode/article/details/50477776?locationNum=3&fps=1
序列函数:
Json ::Fastwrite Write;
string& Write.write(Json::value& val)
f反序列化函数:
Json::Reader reader;
string& reader.parse(string& out,Json::value,bool falg);
2.多线程同步互斥问题,生产者消费者模型
要实现多线程同步、互斥问题,可以加入信号量。
这里我们复习一下信号量
#include< semaphore.h>头文件
使用信号量之前先要初始化 int sem_init(sem_t *sem,int pshared ,unsignal int value);
参数说明:sem_t* sem 信号量的地址。pshared参数为0,说明为同一个进程内部的线程,非0 则不是同一进程内的线程。value为初值。
对信号量进行P操作,使用函数 int sem_post(sem_t*sem);
对信号量进行V操作,使用函数int sem_wait(sem_t *sem);
附加说明:这两个操作都是原子的。
最后销毁信号量,使用函数int sem_destory(sem_t * sem);
3.STL中容器map
map是STL中的一个容器类,底层有红黑树实现,节点可以表示为pair< key,value>的形式,由于查找快,不冗余,常常被用作存储查询信息。
map的用法可以参照博主的一篇博客:http://blog.csdn.net/weiwang1996/article/details/73742560
这里就不一一赘述。
4.ncurses库的简单界面
主要使用函数:
box(win,ch1,ch2): 自动画方框.
ch1: 画方框时垂直方向所用字元.
ch2: 画方框时水平方向所用字元.
echochar(ch): 显示某个字元.
addch(ch): 显示某个字元.
mvaddch(y,x,ch): 在(x,y) 上显示某个字元. 相当於呼叫move(y,x);addch(ch);
addstr(str): 显示一串字串.
mvaddstr(y,x,str): 在(x,y) 上显示一串字串. 相当於呼叫move(y,x);addstr(str);
printw(format,str): 类似 printf() , 以一定的格式输出至萤幕.
mvprintw(y,x,format,str): 在(x,y) 位置上做 printw 的工作. 相当於呼叫move(y,x);printw(format,str);
move(y,x): 将游标移动至 x,y 的位置.
getyx(win,y,x): 得到目前游标的位置. (请注意! 是 y,x 而不是&y,&x )
清屏函数 :clear() and erase(): 将整个萤幕清除. (请注意配合refresh() 使用)
调研完毕开始动工: