说是stun server,其实只是用了一下名字,和开源的stun server不是一回事,这里还加了一些其他功能。
功能:
1 用户通过客户端登录上来,返回它的公网ip和端口。
2 为了开发方便,同时记录下客户端的公网ip端口,以及它提交上来的本地ip和端口,以供查询。(这部分功能正常来讲应该单独做成一个服务,比如im server)
3 用户查询其他用户的公网ip和内网ip(用户拿到后可以尝试打洞)。
功能简单,实现也同样简单,采用前面文章提到的cudpsocket和cudpsession就可以了。简单代码说明:
static void onrecvfrom(int sockid, char *data, int len, int ip, int port, void* param)
{
cpackin pack;
pack.setcontent(data, len);
int ncmd;
pack >> ncmd;
switch(ncmd)
{
case cmd_get_self_public_ip_port:
{
if (len < sizeof(int)*2)
return;
int nid = 0;
pack >> nid;
if (nid < 1)
return;
cusermgr::instance().adduser(nid, ip, port);
cpackout* pack = new cpackout;
(*pack) << cmd_re_get_self_public_ip_port;
(*pack) << nid;
(*pack) << 0; //0 is success
(*pack) << ip;
(*pack) << port;
char* pbuf = null;
int nsize;
pack->getcontent(pbuf, nsize);
g_sess.send(pbuf, nsize, ip, port);
delete pack;
pack = null;
}
break;
case cmd_get_dest_public_ip_port:
{
if (len < sizeof(int)*2)
return;
int ndestid = 0;
pack >> ndestid;
if (ndestid < 1)
return;
cpackout* pack = new cpackout;
(*pack) << cmd_re_get_dest_public_ip_port;
(*pack) << ndestid;
cuser* puser = cusermgr::instance().finduser(ndestid);
if (puser)
{
(*pack) << 0; //0 is success
(*pack) << ip;
(*pack) << port;
}
else
{
(*pack) << 404;//err
}
char* pbuf = null;
int nsize;
pack->getcontent(pbuf, nsize);
g_sess.send(pbuf, nsize, ip, port);
delete pack;
pack = null;
}
break;
}
}
从代码上看,只处理了两条指令:cmd_get_self_public_ip_port和cmd_get_dest_public_ip_port。前一条是用户取到自己的公网ip和端口,用户先连接server,发送请求,recvfrom之后直接就拿到了它的公网ip和端口。用户自己的内网ip和端口可以在数据包里带上。然后server保存在map里。
后一条是取联系人的公网ip和端口。回复的时候,同可以把内网ip端口同时带上。用户拿到后,从公网ip可以看出是否同一网段,如果是的话,先用内网ip端口尝试打洞,不通再用公网的。打洞功能下篇文章在客户端实现,本文是讲server端。
g_sess.send(pbuf, nsize, ip, port);
这里,调用了cudpsession里的send ,在send里面,又包装了一包。这里绝对不能直接调用cudpsession::sendpacket,因为这个函数是直接发送裸数据的。不过可以简单修改一下把send和sendpacket结合起来用。这个小功能根据实际需要随时可做修改,本文只是演示。
server端记录user信息后,应该做一个保活检查,在一定时间内没有数据提交,认为用户下线,然后从 map里删除。这个功能代码没有实现,以后会完善的。不过很简单,有兴趣可以自己练习着加上。但这个定时器一定要注意,g_sess.m_ptimeoutcb = ontimeout;要从ontimeout这个回调函数里去执行,因为所有的map没有进行同步保护的。而 udpsession是单线程执行,由它的回调函数执行链表不会冲突,但由其他线程,比如自己再写个定时器来执行,map 一定要加锁了。
完整代码在:
因为本文主要调试网络,就把视频部分简单注释掉了。
见void cclientdlg::onrecvfrom(int sockid, char *data, int len, int ip, int port)
//if (sockid == m_nsessid)
//{
// trace("recv ret = %d\n", len);
// m_dec.decode_frame((byte*)data, len);
//}
目前主要是为了演示音视频的网络传输,为了方便,把指令都集成到了一起了。更清晰的做法是:
先做一个简单的im,进行指令传输,udp打洞等,然后再进行数据传输。
本文先集中讲音视频数据传输,用这些简单的代码演示功能,最终会出一个正式的商业级的im开源版本。目前这些代码可以看做是演示用的sample。
server版本是linux下的,先进行run 执行make,再cd.. 执行make,会生成stun_server的可执行文件。
不想自己编译的话,可以直接运行客户端,里面server的地址已经设好了: 115.28.170.118。
阅读(6799) | 评论(1) | 转发(2) |