【开源下载】c#编写的聊天程序微风IM 版本2 增加局域网P2P通信

新年第一天 恭祝大家新年快乐

一直有朋友问P2P相关的问题,最近有时间在微风IM的基础上,实现了P2P通信,共享给大家,希望大家批评指正。

源码下载 (只包含源码,无插入式广告:)  数据库下载   数据库与第一版相同没有变化

我们知道在网络通信中,如果所有的通信都通过服务器转发,会增加服务器的负担,如果实现了P2P,客户端之间直接通讯,比如聊天或者传送文件时不再通过服务器,而是客户端之间直接通信,将会有效的减轻服务器的负担,提高程序的效率。

本节相关的P2P,指的是通过TCP协议,在局域网中实现的P2P,广域网中的P2P暂时没有涉及。

本Demo基于来自英国的networkComms2.3.1开源通信框架

工作原理-通过服务器,在客户端之间建立P2P通道,之后客户端之间的通讯可以脱离服务器

流程如下:

NetworkComms通信框架的内在通信机制,使得我们实现P2P通信非常的简单。

(1):服务器开始监听

(2)  :客户端,开始连接服务器,然后也开始监听工作,其实成为一个服务器。连接的过程中,系统会给客户端随机分派一个端口,以便完成与服务器的通信。连接完成后,我们获取到客户端的IP和与服务器通信的端口,客户端在此端口上展开监听,也就是说每个客户端都会展开监听,具备作为服务器的所有特质。

模拟代码:

          ConnectionInfo connInfo = new ConnectionInfo("服务器IP", "服务器端口");
          //客户端与服务器进行连接
          Connection     newTcpConnection = TCPConnection.GetConnection(connInfo);
          //客户端与服务器连接成功后,开始监听本地端口,客户端也称为可以监听的服务器
          TCPConnection.StartListening(connInfo.LocalEndPoint);

(3):每个客户端需要维护一个“P2P通信的连接”表

我们用一个静态类来实现,具体可查看Common类

//字典中存储 用户ID 和相应的连接引用

public static Dictionary<string, Connection> UserConnList = new Dictionary<string, Connection>();

  public static void AddUserConn(string userID, Connection conn)
        {
            lock (dictLocker)
            {
                if (UserConnList.ContainsKey(userID))
                {
                    UserConnList.Remove(userID);
                }
                UserConnList.Add(userID,conn);
            }
        }

        public static bool ContainsUserConn(string userID)
        {
            lock (dictLocker)
            {
                if (UserConnList.ContainsKey(userID))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public static Connection  GetUserConn(string userID)
        {
            lock(dictLocker)
            {
                if(UserConnList.ContainsKey(userID))
                {
                    return UserConnList[userID];
                }
                else
                {
                    return null;
                }
            }
        }

        public static void RemoveUserConn(string userID)
        {
            lock (dictLocker)
            {
                if (UserConnList.ContainsKey(userID))
                {
                    UserConnList.Remove(userID);
                }

            }
        }

相关操作方法

(4):客户端成功登陆后,从服务器获取所有在线其他客户端用户的本地端点(IP和端口)(即在其他客户端在步骤一中展开监听的端点),并进行连接

《1》客户端甲与其他客户端逐个进行连接,连接成功后,客户端甲添加对方用户ID和连接引用到本地P2P通道字典中

《2》客户端甲发送一个消息类型为”setupP2PMessage"的消息,给对方,以便于对方添加相应的记录到对方的P2P字典

《3》客户端甲与其他用户进行连接时,客户端甲为“客户端”,其他的客户端为“服务器端”,所以在P2P通道的2端,总有一端为“客户端”,另一端为“服务器”。

配合NetworkComms通信框架,此种概念上的区分,并不影响P2P通道的通信。

客户端甲与其他客户端通信时,无论是作为”客户端“或者”服务器“均可,只要与对方存在TCP长连接即可。

《4》 这种由客户端之间彼此通信而建成的”服务器“,具备真正服务器的所有功能,会进行相应的”心跳检测“与”连接“维护等。

下面的代码:某客户端登陆后,获取所有已在线用户,并与之连接,连接完成后,发送”SetupP2PMessag"类型消息给对方。通过此过程,彼此双方的“P2P连接”都会建立完成。

 

private void GetP2PInfo()
        {
            //从服务器端,获取所有在线用户的信息 (用户ID,相对应的本地端点,在第一步中,客户端与服务器连接成功后,已经在此端点上开始监听了)
            IList<UserIDEndPoint> userInfoList = Common.TcpConn.SendReceiveObject<IList<UserIDEndPoint>>("GetP2PInfo", "ResP2pInfo", 5000, "GetP2P");
            //遍历所有的在线用户
            foreach (UserIDEndPoint userInfo in userInfoList)
            {
                try
                {
                    if (userInfo.UserID != Common.UserID)
                    {
                        //在根目录下写入日志
                        LogInfo.LogMessage("准备建立" + userInfo.UserID + ":" + userInfo.IPAddress + ":" + userInfo.Port.ToString(), "P2PInfo");
                        //创建连接信息类
                        ConnectionInfo connInfo = new ConnectionInfo(userInfo.IPAddress, userInfo.Port);
                        //把对方客户端当成服务器对应连接
                        Connection newTcpConnection = TCPConnection.GetConnection(connInfo);
                        Common.AddUserConn(userInfo.UserID, newTcpConnection);

                        SetUpP2PContract contract = new SetUpP2PContract();
                        contract.UserID = Common.UserID;

                        //P2p通道打通后,发送一个消息给对方用户,以便于对方用户收到消息后,建立P2P通道
                        newTcpConnection.SendObject("SetupP2PMessage", contract);

                        //在根目录下写入日志

                        LogInfo.LogMessage("已经建立" + userInfo.UserID + ":" + userInfo.IPAddress + ":" + userInfo.Port.ToString(), "P2PInfo");
                    }

                }
                catch
                {
                }

            }

        }

上面的代码中,我们把相关的P2P通道建立消息写入程序文件夹下“P2PINFO.txt文件”,以便于观察P2P消息通道的建立。和通过P2P通道发送消息

(5):通过P2P通道发送消息

客户端发送消息时,查看是否与对方存在 P2P通道,如果存在通过P2P连接发送消息,否则通过服务器发送

举例说明,发送聊天消息时,先查看是否有 p2p 通道

private void chatControl1_BeginToSend(string content)
        {
            this.chatControl1.ShowMessage(Common.UserName, DateTime.Now, content, true);

            //从客户端 Common中获取相应P2P通道
            Connection p2pConnection = Common.GetUserConn(this.friendID);

            if (p2pConnection != null)
            {
                ChatContract chatContract = new ChatContract();
                chatContract.UserID = Common.UserID;
                chatContract.UserName = Common.UserName;
                chatContract.DestUserID = this.friendID;
                chatContract.DestUserName = this.friendID;
                chatContract.Content = content;
                chatContract.SendTime = DateTime.Now;
                p2pConnection.SendObject("ClientChatMessage", chatContract);
                this.chatControl1.Focus();

                LogInfo.LogMessage("通过p2p通道发送消息,当前用户ID为"+Common.UserID, "P2PINFO");

            }
            else
            {
                ChatContract chatContract = new ChatContract();
                chatContract.UserID = Common.UserID;
                chatContract.UserName = Common.UserName;
                chatContract.DestUserID = this.friendID;
                chatContract.DestUserName = this.friendID;
                chatContract.Content = content;
                chatContract.SendTime = DateTime.Now;
                Common.TcpConn.SendObject("ChatMessage", chatContract);
                this.chatControl1.Focus();

                LogInfo.LogMessage("服务器转发消息", "P2PINFO");
            }

        }

(6)P2P通道的注销

当某个客户端掉线后,我们要把其从其他相应客户端的P2P通道注销掉。

方法:

服务器通过心跳检测,知道某连接掉线后,发送消息给其他所有客户端。

 private void UserStateNotify(string userID, bool onLine)
        {
            try
            {
                //用户状态契约类
                UserStateContract userState = new UserStateContract();
                userState.UserID = userID;
                userState.OnLine = onLine;

                IList<ShortGuid> allUserID;

                lock (syncLocker)
                {
                    //获取所有用户字典中的用户ID
                    allUserID = new List<ShortGuid>(userManager.Values);
                }

                //给所有用户发送某用户的在线状态
                foreach (ShortGuid netID in allUserID)
                {
                    List<Connection> result = NetworkComms.GetExistingConnection(netID, ConnectionType.TCP);

                    if (result.Count > 0 && result[0].ConnectionInfo.NetworkIdentifier == netID)
                    {
                        result[0].SendObject("UserStateNotify", userState);
                    }
                }
            }
            catch (Exception ex)
            {
                LogTools.LogException(ex, "MainForm.UserStateNotify");
            }
        }

服务器端代码,发送用户上线或下线消息

客户端代码:

  NetworkComms.AppendGlobalIncomingPacketHandler<UserStateContract>("UserStateNotify", IncomingUserStateNotify);

 private void IncomingUserStateNotify(PacketHeader header, Connection connection, UserStateContract userStateContract)
        {
            if (userStateContract.OnLine)
            {
                lock (syncLocker)
                {
                    //此部分,处理用户上线,与P2p通道无关
                    Common.GetDicUser(userStateContract.UserID).State = OnlineState.Online;
                }
            }
            else
            {
                lock (syncLocker)
                {
                    Common.GetDicUser(userStateContract.UserID).State = OnlineState.Offline;
                    //当某用户下线后,删除此用户相关的p2p 通道
                    Common.RemoveUserConn(userStateContract.UserID);
                }
            }
        }

P2P通信暂时介绍到这里,希望大家喜欢。

时间: 2024-11-03 05:29:10

【开源下载】c#编写的聊天程序微风IM 版本2 增加局域网P2P通信的相关文章

基于Tcp通信的聊天程序微风IM(c#开源) -技术分析(三) 客户端下线

在微风IM中,当某个客户端下线后,其他客户端能够感知到此用户已经下线,并把其头像图标变成灰色. 感知连接的掉线,是networkcomms框架内置的功能,服务器通过心跳检测得知某连接掉线,会从networkcomms内部维护的连接列表中删除此连接,并触发相应的委托. 我们要处理某连接掉线,只需要注册  NetworkComms.AppendGlobalConnectionCloseHandler 方法即可 服务器端代码如下: //如果某客户端离线,触发此方法 NetworkComms.Appen

基于Tcp通信的聊天程序微风IM(c#开源) -技术分析(一) 用户管理

在微风IM中,如果用户上线了,其他用户的用户列表中,此用户状态更新为上线状态,如果用户下线了,此用户的头像会变成灰色. 我们看一下相关的代码: 首先是客户端代码(1): UserInfo userInfo = new UserInfo(); userInfo.UserID = txtUserID.Text.Trim(); userInfo.Password = txtPassword.Text.Trim(); //发送契约类给服务器端,并获取返回的结果 UserLoginContract log

c#编写的基于TCP通信的微风IM 版本3 新年新UI

在微风 IM 版本2中我们实现了局域网内的p2p通信,具体见: [开源下载]c#编写的聊天程序微风IM 版本2 增加局域网P2P通信 前面有朋友说微风IM的UI有点朴素,也确实,于是到网上去淘了件新衣服. 新的UI来自于网上开源程序,由"翱翔的雄鹰"老师编写的完全开源的QQ2010.(c# WinForm).新的UI中有许多自定义控件,我从其中学到了很多Winfrom控件制作的知识. 比如,带边框的文本框  文本框 鼠标经过时,显示边框的按钮  按钮 鼠标经过时显示边框效果的Check

使用Xamarin开发手机聊天程序 -- 基础篇(大量图文讲解 step by step,附源码下载)

如果是.NET开发人员,想学习手机应用开发(Android和iOS),Xamarin 无疑是最好的选择,编写一次,即可发布到Android和iOS平台,真是利器中的利器啊!而且,Xamarin已经被微软收购并被大力推广,.NET开发人员将时间投资在Xamarin上,以应对移动开发的热潮,应该是值得的. 好了,废话不多说,就开始吧.本系列文章将详细介绍如何使用Xamarin开发出一个简单的即时通信IM聊天系统(文末有源码下载,可先睹为快),本文作为第一篇基础篇,将着重介绍Xamarin Andro

C#编写简单的聊天程序(转)

这是一篇基于Socket进行网络编程的入门文章,我对于网络编程的学习并不够深入,这篇文章是对于自己知识的一个巩固,同时希望能为初学的朋友提供一点参考.文章大体分为四个部分:程序的分析与设计.C#网络编程基础(篇外篇).聊天程序的实现模式.程序实现. 程序的分析与设计 1.明确程序功能 如果大家现在已经参加了工作,你的经理或者老板告诉你,“小王,我需要你开发一个聊天程序”.那么接下来该怎么做呢?你是不是在脑子里有个雏形,然后就直接打开VS2005开始设计窗体,编写代码了呢?在开始之前,我们首先需要

【开源下载】基于TCP网络通信的即时聊天系统(IM系统)(c#源码)

c#开源IM系统 把系统中集成的IM部分,提取出来,共大家参考,也可以作为大家学习networkcomms框架的一个demo 名称:微风IM 名称来源: 微 小的意思 是说系统比较简单 风 是由于UI使用的风绪老师多年前在网上开源的高仿真qq2008中的代码,在此表示特别感谢 数据库使用的是mssql 2005. c# 2.0开发 通信框架使用的是来自英国剑桥的c#开源通讯框架 networkcomms2.3.1  可以进入此页面下载 networkcomms网络通讯框架学习 使用network

基于Perfect用Swift语言编写Slack聊天机器人

基于Perfect用Swift语言编写Slack聊天机器人 本项目是专门为Slack聊天机器人定制的模板服务器. 完整的源代码下载在Github https://github.com/PerfectServers/SlackBot 在本项目模板中,一个聊天机器人可以加入授权频道,读取频道内所有用户发送的"曲奇"并记录在案,而且可以直接答复用户的有关曲奇饼干的问题. 预备知识 在您决定编译.测试或者部署您自己的基于Perfect软件框架体系的聊天机器人之前,以下基础知识??不可或缺??:

用c#开发安卓程序 (xamarin.android)系列之二 简单的聊天程序

networkcomm.net 网络通信框架来自于英国剑桥,其开源版本2.3.1 中自带了一个编写android的例子,可以很好的帮助我们入门. 此示例的功能,是在2个安卓手机上,输入对方的IP和端口,能够实现聊天功能. 把代码放上,供大家一览 using System; using Android.App; using Android.Content; using Android.Runtime; using Android.Views; using Android.Widget; using

Node.js + Web Socket 打造即时聊天程序嗨聊

前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前后端之间代码敲得飞起,从此由前端晋升为'前后端'. 图片来自G+ 本文将使用Node.js加web socket协议打造一个网页即时聊天程序,取名为HiChat,中文翻过来就是'嗨聊',听中文名有点像是专为寂寞单身男女打造的~ 其中将会使用到express和socket.io两个包模块,下面会有介绍