点晴MIS内部交流论坛加入收藏
新手上路
论坛搜索
 您的位置:点晴MIS系统问题答疑『 技术文档交流 』浏览当前帖子  
登 陆注 册论坛帮助  

  网站搜索
  搜索范围: 搜索方式: 关键词(可用空格分开)  

  作者及文章信息: 您是本文的第 178 位读者 
admin

积分:86215
等级:网站管理员
文数:7720
注册:2004-7-20

  信 息   留 言   主 页   编 辑   引 用    

楼 顶 

 C# p2p UDP穿越NAT,UDP打洞源码


思路如下(参照源代码):

  1、 frmServer启动两个网络侦听,主连接侦听,协助打洞的侦听。

  2、 frmClientA和frmClientB分别与frmServer的主连接保持联系。

  3、 当frmClientA需要和frmClientB建立直接的udp连接时,首先连接frmServer的协助打洞端口,并发送协助连接申请,同时在该端口号上启动侦听。

     4、  frmServer的协助打洞连接收到frmClientA的申请后通过主连接通知frmClientB,并将frmClientA经过NAT-A转换后的公网IP地址和端口等信息告诉frmClientB。

  5、 frmClientB收到frmServer的连接通知后首先与frmServer的协助打洞端口连接,发送一些数据后立即断开,目的是让frmServer能知道frmClientB经过NAT-B转换后的公网IP和端口号。

  6、 frmClientB尝试与frmClientA的经过NAT-A转换后的公网IP地址和端口进行connect,不同的路由器会有不同的结果,多数路由器对未知不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-A会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即frmClientB向frmClientA打了一个洞,下次frmClientA就能直接连接到frmClientB刚才使用的端口号了。

  7、 客户端frmClientB打洞的同时在相同的端口上启动侦听。frmClientB在一切准备就绪以后通过与frmServer的主连接回复消息“可以了,已经准备”,frmServer在收到以后将frmClientB经过NAT-B转换后的公网IP和端口号告诉给frmClientA。

  8、 frmClientA收到frmServer回复的frmClientB的公网IP和端口号等信息以后,开始连接到frmClientB公网IP和端口号,由于在步骤6中frmClientB曾经尝试连接过frmClientA的公网IP地址和端口,NAT-A纪录了此次连接的信息,所以当frmClientA主动连接frmClientB时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的udp连接建立起来了。

  • frmClientB

客户端核心代码:

private void Run()
        {
            try
            {
                byte[] buffer;//接受数据用
                while (true)
                {
                    buffer = _client.Receive(ref _remotePoint);//_remotePoint变量返回当前连接的用户IP地址

                    object msgObj = ObjectSerializer.Deserialize(buffer);
                    Type msgType = msgObj.GetType();
                    DoWriteLog("接收到消息:" + msgType.ToString() + " From:" + _remotePoint.ToString());

                    if (msgType == typeof(S2C_UserListMessage))
                    {
                        // 更新用户列表
                        S2C_UserListMessage usersMsg = (S2C_UserListMessage)msgObj;
                        _userList.Clear();

                        foreach (User user in usersMsg.UserList)
                            _userList.Add(user);

                        this.DisplayUsers(_userList);
                    }
                    else if (msgType == typeof(S2C_UserAction))
                    {
                        //用户动作,新用户登录/用户登出
                        S2C_UserAction msgAction = (S2C_UserAction)msgObj;
                        if (msgAction.Action == UserAction.Login)
                        {
                            _userList.Add(msgAction.User);
                            this.DisplayUsers(_userList);
                        }
                        else if (msgAction.Action == UserAction.Logout)
                        {
                            User user = _userList.Find(msgAction.User.UserName);
                            if (user != null) _userList.Remove(user);
                            this.DisplayUsers(_userList);
                        }
                    }
                    else if (msgType == typeof(S2C_HolePunchingMessage))
                    {
                        //接受到服务器的打洞命令
                        S2C_HolePunchingMessage msgHolePunching = (S2C_HolePunchingMessage)msgObj;

                        //NAT-B的用户给NAT-A的用户发送消息,此时UDP包肯定会被NAT-A丢弃,
                        //因为NAT-A上并没有A->NAT-B的合法Session, 但是现在NAT-B上就建立了有B->NAT-A的合法session了!
                        P2P_HolePunchingTestMessage msgTest = new P2P_HolePunchingTestMessage(_LocalUserName);
                        this.SendMessage(msgTest, msgHolePunching.RemotePoint);
                    }
                    else if (msgType == typeof(P2P_HolePunchingTestMessage))
                    {
                        //UDP打洞测试消息
                        //_HoleAccepted = true;
                        P2P_HolePunchingTestMessage msgTest = (P2P_HolePunchingTestMessage)msgObj;
                        UpdateConnection(msgTest.UserName, _remotePoint);

                        //发送确认消息
                        P2P_HolePunchingResponse response = new P2P_HolePunchingResponse(_LocalUserName);
                        this.SendMessage(response, _remotePoint);
                    }
                    else if (msgType == typeof(P2P_HolePunchingResponse))
                    {
                        //_HoleAccepted = true;//打洞成功
                        P2P_HolePunchingResponse msg = msgObj as P2P_HolePunchingResponse;
                        UpdateConnection(msg.UserName, _remotePoint);

                    }
                    else if (msgType == typeof(P2P_TalkMessage))
                    {
                        //用户间对话消息
                        P2P_TalkMessage workMsg = (P2P_TalkMessage)msgObj;
                        DoWriteLog(workMsg.Message);
                    }
                    else
                    {
                        DoWriteLog("收到未知消息!");
                    }
                }
            }
            catch (Exception ex) { DoWriteLog(ex.Message); }
        }

  • frmClientA

服务端核心代码:

private void Run()
        {
            byte[] msgBuffer = null;

            while (true)
            {
                msgBuffer = _server.Receive(ref _remotePoint); //接受消息
                try
                {
                    //将消息转换为对象
                    object msgObject = ObjectSerializer.Deserialize(msgBuffer);
                    if (msgObject == null) continue;

                    Type msgType = msgObject.GetType();
                    DoWriteLog("接收到消息:" + msgType.ToString());
                    DoWriteLog("From:" + _remotePoint.ToString());

                    //新用户登录
                    if (msgType == typeof(C2S_LoginMessage))
                    {
                        C2S_LoginMessage lginMsg = (C2S_LoginMessage)msgObject;
                        DoWriteLog(string.Format("用户’{0}’已登录!", lginMsg.FromUserName));

                        // 添加用户到列表
                        IPEndPoint userEndPoint = new IPEndPoint(_remotePoint.Address, _remotePoint.Port);
                        User user = new User(lginMsg.FromUserName, userEndPoint);
                        _userList.Add(user);

                        this.DoUserChanged(_userList);

                        //通知所有人,有新用户登录
                        S2C_UserAction msgNewUser = new S2C_UserAction(user, UserAction.Login);
                        foreach (User u in _userList)
                        {
                            if (u.UserName == user.UserName) //如果是自己,发送所有在线用户列表
                                this.SendMessage(new S2C_UserListMessage(_userList), u.NetPoint);
                            else
                                this.SendMessage(msgNewUser, u.NetPoint);
                        }
                    }
                    else if (msgType == typeof(C2S_LogoutMessage))
                    {
                        C2S_LogoutMessage lgoutMsg = (C2S_LogoutMessage)msgObject;
                        DoWriteLog(string.Format("用户’{0}’已登出!", lgoutMsg.FromUserName));

                        // 从列表中删除用户
                        User logoutUser = _userList.Find(lgoutMsg.FromUserName);
                        if (logoutUser != null) _userList.Remove(logoutUser);

                        this.DoUserChanged(_userList);

                        //通知所有人,有用户登出
                        S2C_UserAction msgNewUser = new S2C_UserAction(logoutUser, UserAction.Logout);
                        foreach (User u in _userList)
                            this.SendMessage(msgNewUser, u.NetPoint);
                    }
                    else if (msgType == typeof(C2S_HolePunchingRequestMessage))
                    {
                        //接收到A给B打洞的消息,打洞请求,由客户端发送给服务器端
                        C2S_HolePunchingRequestMessage msgHoleReq = (C2S_HolePunchingRequestMessage)msgObject;

                        User userA = _userList.Find(msgHoleReq.FromUserName);
                        User userB = _userList.Find(msgHoleReq.ToUserName);

                        // 发送打洞(Punching Hole)消息
                        DoWriteLog(string.Format("用户:[{0} IP:{1}]想与[{2} IP:{3}]建立对话通道.",
                        userA.UserName, userA.NetPoint.ToString(),
                        userB.UserName, userB.NetPoint.ToString()));

                        //由Server发送消息给B,将A的IP的IP地址信息告诉B,然后由B发送一个测试消息给A.
                        S2C_HolePunchingMessage msgHolePunching = new S2C_HolePunchingMessage(_remotePoint);
                        this.SendMessage(msgHolePunching, userB.NetPoint); //Server->B
                    }
                    else if (msgType == typeof(C2S_GetUsersMessage))
                    {
                        // 发送当前用户信息
                        S2C_UserListMessage srvResMsg = new S2C_UserListMessage(_userList);
                        this.SendMessage(srvResMsg, _remotePoint);
                    }
                }
                catch (Exception ex) { DoWriteLog(ex.Message); }
            }
        }

  • frmServer 


该文章在 2018-2-10 10:08:37 编辑过

  离 线  2018-2-10 10:07:04 
  本文章共有 0 页, 0 张回文,每页有 10 张回文 >> [ ]
页码:
Copyright 2003-2018 ClickSun All Rights Reserved