存档

文章标签 ‘sip’

近期飞信开发手记(群相关介绍)

八月 14th, {2010 22 条评论 11,394 人阅读过  

实验室项目中期检查结束,两天一夜的超负载工作换来了额外两天的休息时间,时间不敢随意浪费,飞信好久没有更新了,软件总不可能没有bug,用户的需求也不可能有止境,还是得赶紧把这么长时间以来网友提出的问题和要求给解决一下,用了近三天的时间完成了飞信i18n和飞信群功能,宅在宿舍里面写代码时间长了果然会觉得无聊,闲话少说,切入正题,写一下飞信群的开发过程。

另外,现在还不打算发布新版本,还想再测试两天看看,这里就截两个图先展示一下吧

image image

其实飞信群这个功能本来也没几个人在用,分析了一下协议,没有什么特别复杂的地方,很多东西都是套路,首先是在登录的时候获取一系统群相关的信息,如群列表,群成员列表,群详细信息,群个人信息,推荐群,群话题等等,一些没用的信息像推荐群,群话题这些在windows下都不会有人去点的信息就直接给忽略了,登录的过程中发送一系统的获取信息的信令,然后再统一根据 callid从服务器返回的信息中提取各自相关的信息,获取群列表信令如下:

S fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 3
Q: 1 S
N: PGGetGroupList
L: 27

<args><group-list /></args>

获取群详细信息信令如下:

S fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 5
Q: 1 S
N: PGGetGroupInfo
L: 150

<args><groups attributes=”all”><group uri=”sip:PG9777218@fetion.com.cn;p=12205″ /><group uri=”sip:PG31809932@fetion.com.cn;p=12207″ /></groups></args>

获取群成员列表信令如下:

S fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 12
Q: 1 S
N: PGGetGroupMembers
L: 229

<args><groups attributes=”member-uri;member-nickname;member-iicnickname;member-identity;member-t6svcid”><group uri=”sip:PG9777218@fetion.com.cn;p=12205″ member-list-major-version=”" member-list-minor-version=”" /></groups></args>

这些信息发送至服务器这后,服务器会将相关的请求信息推送过来,程序编写时在发送每一条信令都记住其相关的callid,再根据返回信息中的callid来判断其是什么信息,然后再将其解析。

这些信息获取之后就需要订阅每个群的Presence信息,信令如下:

SUB fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 2
Q: 2 SUB
N: PGPresence
L: 215

<args><subscription><groups><group uri=”sip:PG9777218@fetion.com.cn;p=12205″ /></groups><presence><basic attributes=”all” /><member attributes=”all” /><management attributes=”all” /></presence></subscription></args>

之后服务器会将群成员的Presence信息推送过来,信令格式大致如下:

BN 916098834 SIP-C/4.0
N: PresenceV4
I: 1
L: 159
Q: 55 BN
<events><event type=”PresenceChanged”><contacts><c id=”464933706″><pr di=”PCCL030524118392″ b=”400″ d=”" dt=”PC” dc=”137″></pr></c></contacts></event></events>

至此,群相关的信息都已经获取到了,已经可以在界面上展示飞信群了,但要发送群信息还不够,必须还要发起群会话邀请,也就是向sip服务器发送Invite信令,格式如下:

I fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 10
Q: 1 I
T: sip:PG31809932@fetion.com.cn;p=12207
K: text/html-fragment
K: multiparty
K: nudge
K: share-background
K: fetion-show
L: 21

s=session m=message

服务器返回100 trying,之后返回200 OK,收到OK之后再向服务器发送Ack信令:

A fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 9
Q: 1 A
T: sip:PG9777218@fetion.com.cn;p=12205

这样会话邀请就已经完成了,可以发送群信息了。但在这之后我发送群信息的时候却出了一点小问题,每次发送到服务器的Message信息格式完全正确,但服务器返回的是请求失败的信令,想了好久才想明白,其实发起会话邀请这一步并不是做给服务器看的,而是要通过它来注册一个callid,Invite信令的callid必须与Message信令的callid一致,消息才能发送成功,这表示发送的群消息是属于这个会话的,而标识正是在发起邀请时所用的callid,群消息发送的信令如下:

M fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 9
Q: 2 M
T: sip:PG31809932@fetion.com.cn;p=12207
C: text/html-fragment
K: SaveHistory
L: 60

<Font Face=” Color=’-16777216′ Size=’10′>hello</Font>

然后就是群短信:

M fetion.com.cn SIP-C/4.0
F: ×××××××××
I: 25
Q: 1 M
T: sip:PG31809932@fetion.com.cn;p=12207
N: PGSendCatSMS
L: 12

hello fetion

群短信这里的callid就无所谓啦,可以让它使用递增的全局变量,当然也只有群的超级管理员才可以发送群短信,连管理员也不能发,所以我觉得这个功能限制也太多啦。

其实关于飞信群做的事情也就这些,像其它的一些邀请好友加入群,群管理之类的功能我都没做,本来就没有几个人在用这个功能,而且linux下用的就更少了,而且我也确实拿不出再多的时间去整了,就先把基本功能都弄好了让过来提示需求的网友先凑合着吧,如果将来飞信群用的人多了我再想办法后时间来完善它。

另外,我弄这个群比飞信的官方软件里面的群帅的地方就是群成员可以显示头像,当然啦,这些也都是浮云。

UDP并发服务器的设计

四月 7th, {2010 没有评论 10,553 人阅读过  

任务目的:设计一个代理服务器,客户端与该服务器使用承载在UDP之上的SIP信令进行交互,SIP消息体中包裹着FTP信令,由代理服务器代为进行FTP业务连接,将返回数据同样再用SIP信令封装返回组客户端。

客户端发送INVITE信令到服务器,服务器收到INVITE后新建FTP连接,并将FTP 连接socket和SIP dialogId做为一个结构体推入队列,相当于会话ID与FTP连接绑定,来自该会话的信令经过解析后都会通过对应的socket发送到FTP服务器,取回数据后再通过该dialogId回传给客户端。
暂时想到的解决方案:

方法一是参考TFTP服务器的实现,TFTP服务器收到文件下载信令后,会fork一个子进程(或者创建一个thread,结节就先不管了),子进程来负责从磁盘上读出数据,并新创建一个socket绑定到一个ephemeral端口,通过这个端口将数据发送回客户端,由UDP无需创建conneciton就可以直接接收数据,所以收到的文件数据除了传输层的端源端口号不一样外,对客户端来说别无二致。

每收到一条FTP信令新建一个线程或者fork一个子进程来对信令进行处理,这里的FTP信令是指用承载在UDP之上的由SIP封装过的FTP信令,处理完后成在线程或者子进程内部将封装后的数据回传给客户端经。

优点是实现简单。

缺点是该过程不同于TFTP服务器,业务过程中会有信令频繁进行交互,势必会造成线程的大量创建,无形中浪费了巨大的服务器资源,得不偿失。

另一个方法是在服务器中创建一条事件队列,定义一个结构体:

1
2
3
4
struct resource{
	int dialogId;
	int socketId;
};

创建一条resource链表(散列表最好,考虑实现简单暂时先用链表),服务器主线程进行信令监听,每收到一条信令随即放入事件队列中,继续进行信令监听,主线程所做的工作只是将收到的信令推入队列,这样不会造成主线程的阻塞,另外新建一条处理线程,该线程循环地读取队列中的数据,若为空则不做处理,若不为空则处理队头数据,从resource链表中取出对应的ftp socket,通过此socket从FTP服务器取回数据,封装后通过dialogId发送回客户端。

优点是一方面防止服务器监听线程阻塞,另一方面又避免了方法一中的线程大量创建所造成的资源消耗。

缺点是在子线程内部可能会造成阻塞,当收到大量信令,处理线程来不及处理时,客户端便会一直等待服务器返回数据,造成阻塞。

如何在UDP代理服务器中寻找一中折衷的方法是一个难题。通过UDP代理进行连接TCP连接着实是一个问题!!!理想的办法是在首次收到FTP连接请求时创建一个TCP请求,以后的信令解析后扔给这个线程处理,问题是想直接扔给线程是不可能的,给线程传递数据只能通过创建线程时传递线程执行函数的入口参数。

因此只有通过共享数据的方式来达到目的。

解决方法可以参照上面的全局消息队列的方法,将方法二改进定下,把一条处理线程改为多条处理线程,每个线程都与一个FTP连接一一对应,第一次收到FTP连接相关的信令后创建FTP线程,每收到一条连接的信令便创建一个FTP线程,这些线程都时刻检查队列,如果发现其中有交给自己的信令,便取出来处理,
处理完成后将返回数据封装后直接通过队列消息中的dialogId发送回客户端(由于客户端和代理服务器的协议是UDP,所以不需要建立连接)。其实Linux内核中进程调度也是用了类似的方法,只不过我这个简单的多了。

需要注意的一个问题是线程同步问题 ,Linux内核中进程对共享资源的并发访问需要加锁,这里应该也不例外,主线程对消息队列中有写的权限,FTP子线程对消息队列有读写权限,这样就必然要做好线程同步工作,加锁的过程以后再考虑,今天还有别的事。

分类: Protocol 标签: , ,

关于库文件的搜索路径问题

三月 29th, {2010 1 条评论 9,237 人阅读过  

项目需要用到sip协议,虽然以前折腾飞信的时候用的也是sip,但不是标准的sip,自己去写一套标准的sip协议栈既浪费时间,又没有什么意思,于是想到了osip和exosip。

装了osip和exosip这两个库,在linux下安装下来就非常简单了,典型的三步曲就可以搞定。

写了个小程序测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <eXosip2/eXosip.h>
#include <netinet/in.h>
 
int main(int argc , char* argv[])
{
	int i;
	int port = 5060;
	TRACE_INITIALIZE(6 , stdout);
 
	i = eXosip_init();
 
	if(i != 0)
		return -1;
	i = eXosip_listen_addr(IPPROTO_TCP , NULL , port , AF_INET , 0);
 
	if(i != 0 )
	{
		eXosip_quit();
		fprintf(stderr , "can not initialize transport layer");
		return -1;
	}
	return 0;
}

编译:gcc -o exosip exosip.c -leXosip2 -DENABLE_TRACE

编译通过了,执行./exosip的时候却会报错:

./exosip: error while loading shared libraries: libeXosip2.so.4: cannot open shared object file: No such file or directory

./exosip: error while loading shared libraries: libosipparser2.so.4: cannot open shared object file: No such file or directory

./exosip: error while loading shared libraries: libosip2.so.4: cannot open shared object file: No such file or directory

很显然,程序找不到需要的这三个共享库,查了一个/usr/lib和/lib这两个目录里面果然没有,再查了一个/usr/local/lib,果然是安装在了那里面,以往都会在编译时设置环境变量LD_LIBRARY_PATH或者直接在/usr/lib目录下创建相关库文件的符号链接,想想太麻烦了,干嘛不设置一下库文件的搜索路径。

库文件搜索路径是在/etc/ld.so.conf中定义的,只需要在最后面加上一行/usr/local/lib就可以了

实际上程序在运行时并不是通过检查这个文件来定位所需要的头文件的,而是通过/etc/ld.so.cache这个文件来定位的,这个文件并不是明文的文本文件,它是ldconfig命令通过检查/etc/ld.so.conf生成的缓存文件,如果通过检查ld.so.conf中的路径来搜索库文件,执行效率未免太低,于是就生成一个缓存文件,通过它来定位所需要库文件。

ld.so.conf修改完之后必须用ldconfig来更新ld.so.cache,否则ld.so.conf改了也没用,另外ldconfig命令需要用root权限执行。

分类: Linux 标签: , ,