说是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。
阅读(1564) | 评论(0) | 转发(0) |