用c#写的一个局域网聊天客户端 类似小飞鸽
用c#写的一个局域网聊天客户端 类似小飞鸽
摘自:
http://www.cnblogs.com/yyl8781697/archive/2012/12/07/csharp-socket-udp.html
1:软件开启的时候先新开一个线程,该线程充当服务器端,一直死循环监听
2:开了新线程了,调用广播的方法
3:此时如果局域网内已经有有其它主机打开了这个软件,将会监听到这个广播,收到这个广播后将返回自己的主机,并且将监听到的主机添加自己的在线列表中,当然,发起广播的软件也能收到其它软件的回信,收到他们的主机后也加入自己的在线列表,这样各自的列表就都能建立起来,并且将当前的列表加入一个静态的泛型列表中(用于以后和其它用户的通信,维护他们的状态)
4:通信 发送消息:双击一个主机列表后 得到该主机host 传到交谈窗体 并查询出他主机的endpoint,这样就可以进行本机和向该endpoint点发送消息了
5:下线 下线之前软件会发一个下线的广播,其它的软件接到该广播的时候将会将该主机从自己的在线列表中移除
整体思路就这样,下面可以结合代码具体看一下
复制代码
#region Field
///
        /// 
///
        private delegate void MyInvoke(string host);
///
        /// 
private SocketUdpServer udpServer;
///
        /// 
private SocketUdpClient udpClient;
#endregion
#region Contructor
///
        /// 
public FrmUser()
        {
            InitializeComponent();
            init();
        }
#endregion
#region Method
///
        /// 
private void init()
        {
            LanList.CurrentLanList = new List();
            this.Text = "当前局域网内在线用户";
            this.udpServer = SocketUdpServer.Instance;
            this.udpServer.OnLineComplete += new SocketUdpServer.OnCompleteHander(this.OnLine_Event);
            this.udpServer.DownLineComplete += new SocketUdpServer.OnCompleteHander(this.DownLine_Event);
            this.udpServer.ErrorAppear += new SocketUdpServer.OnCompleteHander(this.Error_Event);
            this.udpServer.Listen();
            this.udpClient = new SocketUdpClient();
            this.udpClient.Broadcast(DatagramType.OnLine);
}
///
        /// 
/// 用户主机
        private void AddUser(string host)
        {
            this.ilbUserList.Items.Add(host, 0);
        }
///
        /// 
/// 用户主机在列表的序号 懒了以下 应该将回调的委托参数定义为int的,这里用了string 到程序需要转化为Int
        private void RemoveUser(string hostIndex)
        {
            this.ilbUserList.Items.RemoveAt(Convert.ToInt32(hostIndex));
        }
#endregion
#region Event
///
        /// 
///
        ///
        private void OnLine_Event(SocketUdpServer socket, EventArgs e)
        {
            string host = socket.Message;
            //如果该上线的用户在局域网列表中不存在
            if (!LanList.CurrentLanList.Exists(x => x.Host == host))
            {
                while (!this.IsHandleCreated) ;
                this.ilbUserList.Invoke(new MyInvoke(this.AddUser), host);
                //将上线用户添加进静态的局域网列表
                LanList.CurrentLanList.Add(new LanInfo()
                {
                    Host = host,
                    State = TalkState.Waiting,
                    RemoteEndPoint = socket.RemoteEndPoint
                });
            }
        }
///
        /// 
///
        ///
        private void DownLine_Event(SocketUdpServer socket, EventArgs e)
        {
            string host = socket.Message;
            if (LanList.CurrentLanList.Exists(x => x.Host == host))
            {
                ///判断是否是自己的主机下线 如果是自己的 则不需要操作
                if (string.Compare(Dns.GetHostName(), host) != 0)
                {
                    this.ilbUserList.Invoke(new MyInvoke(this.RemoveUser), LanList.CurrentLanList.FindIndex(x => x.Host == host).ToString());
                    //将该用户从局域网列表中移除
LanList.CurrentLanList.RemoveAll(x => x.Host == host);
                }
            }
        }
///
        /// 
///
        ///
        private void Error_Event(SocketUdpServer socket, EventArgs e)
        {
            XtraMessageBox.Show(socket.Message);
        }
private void ilbUserList_DoubleClick(object sender, EventArgs e)
        {
            //XtraMessageBox.Show(ilbUserList.SelectedItem.ToString());
            string host = ilbUserList.SelectedItem.ToString();
            ///打开窗口 设置为正在交谈
            LanList.SetTalkState(host, TalkState.Talking);
            (new FrmTalk(host)).Show();
        }
///
        /// 
///
        ///
        private void FrmUser_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.udpClient.Broadcast(DatagramType.DownLine);
            this.udpServer.Stop();
            Application.Exit();
        }
///
        /// 
///
        ///
        private void btnRefresh_Click(object sender, EventArgs e)
        {
            //刷新  情况列表中的数据  重新上线广播
            this.ilbUserList.Items.Clear();
            LanList.CurrentLanList.Clear();
            this.udpClient.Broadcast(DatagramType.OnLine);
        }
#endregion
复制代码
该页面主要是在线用户列表页面,同时监听其它软件发来的上线,下线,获取主机信息等数据报,维护当前在线的用户 和聊天状态
复制代码
#region Field
///
        /// 
private SocketUdpServer selfSocket = null;
        ///
        /// 
private SocketUdpClient tallSocket = null;
        ///
        /// 
private LanInfo talkLan = null;
        ///
        /// 
private string currentUserHost = "";
///
        /// 
private delegate void MyInvoke(string user,string message);
#endregion
#region Constructor
///
        /// 
///
        public FrmTalk(string host)
        {
            InitializeComponent();
            if (this.talkLan == null)
            {
                this.talkLan = LanList.CurrentLanList.Find(x => x.Host == host);
            }
this.currentUserHost = Dns.GetHostName();
            this.Text = "正在和" + host + "聊天中";
            this.Initializion();
        }
///
        /// 
///
        public FrmTalk(EndPoint remotePoint)
        {
            this.talkLan = LanList.CurrentLanList.Find(x => string.Compare(x.RemoteEndPoint.ToString(), remotePoint.ToString()) == 0);
            (new FrmTalk(talkLan.Host)).Show();
        }
#endregion
#region Method
///
        /// 
private void Initializion()
        {
            this.selfSocket = SocketUdpServer.Instance;
            ///绑定收到信息事件
            this.selfSocket.OnChatComplete += new SocketUdpServer.OnCompleteHander(this.ReceiveEvent);
            //给谈话的socket初始化endpoint
            this.tallSocket = new SocketUdpClient(this.talkLan.RemoteEndPoint);
}
///
        /// 
private void LoadUnReadMessage()
        {
            Queue queque = QueueMessage.GetAndRemove(talkLan.Host);
            MessageInfo messageInfo=null;
            if (queque != null)
            {
                while (queque.Count > 0)
                {
                    //出队列
                    messageInfo = queque.Dequeue();
                    this.lbxMessage.Items.Add(talkLan.Host + ":" + messageInfo.ReceiveTime.ToString("yyyy-MM-dd HH:mm:ss"));
                    this.lbxMessage.Items.Add(messageInfo.Message);
                }
            }
        }
///
        /// 
/// 显示的用户
        /// 消息
        private void AddLine(string name,string message)
        {
            this.lbxMessage.Items.Add(name+ ":" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            this.lbxMessage.Items.Add(message);
        }
///
        /// 
private void SendMessage()
        {
            try
            {
                string message = this.memInput.Text;
                if (string.IsNullOrEmpty(message))
                {
                    XtraMessageBox.Show("发送信息不能为空");
                }
                else
                {
                    this.tallSocket.Send(message);
                    this.AddLine("我", message);
                    this.memInput.Text = "";
                }
            }
            catch (Exception ex)
            {
                XtraMessageBox.Show(ex.Message);
            }
        }
#endregion
#region Event
///
        /// 
///
        ///
        private void FrmTalk_Load(object sender, EventArgs e)
        {
            this.LoadUnReadMessage();
        }
///
        /// 
///
        ///
        private void ReceiveEvent(SocketUdpServer socket, EventArgs e)
        {
            //判断 远端的网络端点是否是当前的 打开的窗体
            if (string.Compare(this.talkLan.RemoteEndPoint.ToString(), socket.RemoteEndPoint.ToString()) == 0)
            {
                this.lbxMessage.Invoke(new MyInvoke(this.AddLine), this.talkLan.Host, socket.Message);
            }
        }
///
        /// 
///
        ///
        private void btnSend_Click(object sender, EventArgs e)
        {
            this.SendMessage();
        }
private void FrmTalk_FormClosed(object sender, FormClosedEventArgs e)
        {
            //将其设置为非交谈状态
            LanList.SetTalkState(talkLan.Host, TalkState.Waiting);
        }
private void memInput_KeyDown(object sender, KeyEventArgs e)
        {
            ///按下回车事件
            if (e.KeyCode == Keys.Enter)
            {
                this.SendMessage();
            }
        }
#endregion
复制代码
该页面就是聊天页面,主要是对相应的host进行通信聊天,发送和接收聊天信息,根据聊天窗口设置状态啊之类的
复制代码
#region Method
#region 停止当前监听和断开线程
        ///
        /// 
public void Stop()
        {
            this.listenThread.Abort();
            this.listenSocket.Close();
        }
        #endregion
#region 监听
///
        /// 
public void Listen()
        {
            ThreadStart method = new ThreadStart(this.ListenMethod);
            this.listenThread = new Thread(method);
            this.listenThread.Start();
        }
///
        /// 
private void ListenMethod()
        {
            try
            {
                this.listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                IPEndPoint ipep = new IPEndPoint(IPAddress.Any, this.port);
                this.listenSocket.Bind(ipep);//定义一个网络端点
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);//定义要发送的计算机的地址
                EndPoint remote = (EndPoint)(sender);//远程
///持续监听
                while (true)
                {
                    byte[] data = new byte[1024];
//准备接收
                    int recv = this.listenSocket.ReceiveFrom(data, ref remote);
                    string stringData = Encoding.UTF8.GetString(data, 0, recv);
                    //将接收到的信息转化为自定义的数据报类
                    Datagram recvicedataGram = Datagram.Convert(stringData);
                    this.message = recvicedataGram.Message;
                    string remotePoint = remote.ToString();
                    string remoteip = remotePoint.Substring(0, remotePoint.IndexOf(":"));
                    remote = new IPEndPoint(IPAddress.Parse(remoteip), this.port);
                    this.remoteEndPoint = remote;
                    this.Action(recvicedataGram.Type);
}
            }
            catch (Exception ex)
            {
                this.message = ex.Message;
                this.ErrorAppear(this, new EventArgs());
            }
        }
///
        /// 
/// 数据报的类型
        private void Action(DatagramType type)
        {
            switch (type)
            {
                case DatagramType.OnLine:
                    Datagram sendDataGram = new Datagram
                    {
                        Type = DatagramType.GiveInfo,
                        FromAddress = "",
                        ToAddress = "",
                        Message = Dns.GetHostName()
                    };
                    //告诉对方自己的信息
                    this.listenSocket.SendTo(Encoding.UTF8.GetBytes(sendDataGram.ToString()), this.remoteEndPoint);
                    this.OnLineComplete(this, new EventArgs());
                    break;
                case DatagramType.GiveInfo:
                    ///执行添加上线用户事件
                    this.OnLineComplete(this, new EventArgs());
                    break;
                case DatagramType.DownLine:
                    ///执行用户下线事件
                    ///如果是自己下线
                    if (string.Compare(Dns.GetHostName(), message) == 0)
                    {
                        System.Windows.Forms.Application.Exit();
                    }
                    else
                    {
                        this.DownLineComplete(this, new EventArgs());
                    }
                    break;
                case DatagramType.Chat:
                    //得到当前要交谈的用户
                    LanInfo lanInfo = LanList.CurrentLanList.Find(x => string.Compare(this.remoteEndPoint.ToString(), x.RemoteEndPoint.ToString()) == 0);
                    //如果有查询到该用户在自己这边登记过
                    if (lanInfo != null)
                    {
if (lanInfo.State == TalkState.Talking)
                        {
                            //正在交谈 直接打开这次窗口
                            this.OnChatComplete(this, new EventArgs());
                        }
                        else {
                            //没有交谈 将窗口加入信息的队列
                            MessageInfo messageInfo = new MessageInfo()
                            {
                                Message = this.message,
                                ReceiveTime = DateTime.Now,
                                RemoteEndPoint = this.remoteEndPoint
                            };
                            QueueMessage.Add(lanInfo.Host, messageInfo);
                        }
                    }
                    break;
            }
        }
#endregion
#endregion
复制代码
充当服务器的 socket的监听,定义一些监听事件,在form里面使用该事件就可以了
复制代码
#region Delegate Event
///
        /// 
///
        ///
        public delegate void OnCompleteHander(SocketUdpServer sender, EventArgs e);
        ///
        /// 
public event OnCompleteHander OnLineComplete;
        ///
        /// 
public event OnCompleteHander DownLineComplete;
        ///
        /// 
public event OnCompleteHander OnChatComplete;
        ///
        /// 
public event OnCompleteHander ErrorAppear;
#endregion
复制代码
用户上线事件,下线事件,或者主机事件,chat聊天事件,再服务器接收到信息后 感觉信息分类执行不同的事件
在CHAT类型的数据报重要注意的是,当有数据过来接收到 但是该主机窗口并未打开时,要将要收到信息加入一个未读的信息队列中,当再次开发对该用户的聊天窗口时先要加载相应的未读信息队列,这样可以简单的实现离线信息的发送
接下来看下信息数据报的格式
复制代码
/*****************************************************************
     * 定义广播的数据格式
     * Type=OnLine,FromAdress=xxx,ToAdress=zzz,Message=mmm
     * 类型为上线广播  从xxx主机到zzz主机  信息是mmm
     * CHAT这个就是我的信息我的信息 可能有各种=,的字符串
     * 这种就直接将CHAT去掉后 后面的都为mmm
    *****************************************************************/
///
    /// 
public class Datagram
    {
        #region Property
///
        /// 
public DatagramType Type
        {
            get;
            set;
        }
///
        /// 
public string FromAddress
        {
            get;
            set;
        }
///
        /// 
public string ToAddress
        {
            get;
            set;
        }
///
        /// 
public string Message
        {
            get;
            set;
        }
///
        /// 
public int Length
        {
            get {
                return this.Message.Length;
            }
        }
#endregion
#region Method
///
        /// 
///
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Type={0},", this.Type.ToString());
            sb.AppendFormat("FromAddress={0},", this.FromAddress.ToString());
            sb.AppendFormat("ToAddress={0},", this.ToAddress.ToString());
            sb.AppendFormat("Message={0}", this.Message.ToString());
            return sb.ToString();
        }
///
        /// 
///
        ///
        public static Datagram Convert(string str)
        {
            Datagram data = new Datagram();
            //前面不是CHAT主要是建立连接 取消连接等信号传送
            if (!str.StartsWith("CHAT"))
            {
                IDictionary idict = new Dictionary();
string[] strlist = str.Split(',');
                for (int i = 0; i
    /// 数据报的类型
    /// 
public enum DatagramType
    {
        ///
        /// 
OnLine=1,
        ///
        /// 
DownLine,
        ///
        /// 
///
        /// 
Chat,
        ///
        /// 
GiveInfo
}
#endregion
复制代码
简单的定义一下发送的数据报的格式  可能发送的几种类型:
上线:主要用于软件刚刚开启时向局域网内发送上线广播
下线:软件在关闭之前再向局域网内发送一次下线广播
给出主机信息:用于收到上线广播后 再返回一个自己主机信息给对方,让让对方知道局域网中这台主机是上线的
聊天:就是平常的通信 这里特别注意的是,为考虑到聊天中也会出来,= 这两个协定的字符串,所以 开头加CHAT 表示纯粹聊天的数据报
复制代码
///
    /// 
public class SocketUdpClient
    {
        #region Feild
///
        /// 
private Socket broadcastSocket;
///
        /// 
private int port;
///
        /// 
private EndPoint remoteEndPoint = null;
        ///
        /// 
private Socket client = null;
#endregion
#region Constructor
///
        /// 
public SocketUdpClient(EndPoint point)
        {
            this.client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            this.remoteEndPoint = point;
        }
///
        /// 
public SocketUdpClient()
        {
            this.port = 9050;
        }
#endregion
#region 进行广播
        ///
        /// 
/// 广播中发送的信息
        public void Broadcast(DatagramType type)
        {
              this.broadcastSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
              IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, this.port);
              this.broadcastSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
              Datagram dataGram = new Datagram
              {
                  Type = type,
                  FromAddress = "",
                  ToAddress = "",
                  Message = Dns.GetHostName()
              };
//将要发送的信息改为字节流
              byte[] data = Encoding.ASCII.GetBytes(dataGram.ToString());
              this.broadcastSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
              this.broadcastSocket.SendTo(data, iep);
              //this.broadcastSocket.Close();
        }
#endregion
#region Method
///
        /// 
/// 当前的数据
        public void Send(string message)
        {
            byte[] data = Encoding.UTF8.GetBytes("CHAT" + message);
int i = client.SendTo(data, this.remoteEndPoint);
        }
#endregion
}
复制代码
socket的client代码  实现广播 发送信息
以上简单的逻辑设计+代码就基本完成了这个简单的客户端聊天软件
说了那么多,接下来看下效果图:
本机这边的效果
局域网中另一端的效果
可以实现简单的 通讯
下面是源码下载:猛击我去下载它
大家在下载包中可以发现 有两个项目 一个是ITalk,他是我最初在写的时候使用的,窗体时继承dev的,效果稍微好一点
为考虑到各大读者可能没有安装dev,所以又一模一样的改了一个ITalkTradition,传统的winform
用c#写的一个局域网聊天客户端 类似小飞鸽的更多相关文章
- 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)
		搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ... 
- nodeJS+express+Jade写一个局域网聊天应用(node基础)
		为了复习一下nodeJS, 而且socketIO这东西听起来就好高端有木有, 而且有人写过了open, 也可以作为自己的参考有木有, 点击下载源代码: express是4.x的版本, 跟以前的配置有些 ... 
- 用Socket做一个局域网聊天工具(转)
		原文:http://www.cnblogs.com/technology/archive/2010/08/15/1799858.html 程序设计成为简单的服务端和客户端之间的通信, 但通过一些方法可 ... 
- 写了一个常规性生成merge 的小脚本
		现在使用数据库来写存储过程,动不动参数就会用到xml ,当然罗,优势也很明显,参数相对固定,而且灵活,如果要修改或者什么的,中间接口层也不需要做变化,只需要修改封装的存储过程以及程序传参就ok了. 随 ... 
- 初学socket,c语言写的简单局域网聊天
		在客户端所在的目录新建一个IP.bwj的文件,写上服务端的IP,不要带空格,保存.双方都打开一个客户端和一个服务端就可以聊天了,(可以写自己的IP,自己跟自己聊..)没有第三方服务器,服务端所在的电脑 ... 
- 【局域网聊天客户端篇】基于socket与Qt
		前言 暑假把linux下的高级编程和网络编程学习了一遍,学习很重要,但是也得有个练手的地方,所以必须做做项目来认识下自己所学习的知识. 能够找到小伙伴一起做项目也是一件很快乐的事情的,很幸运的有两个小 ... 
- 写了一个web使用向导的小插件
		运行效果: 引入插件: <link rel="stylesheet" href="ez-guide.css"> <script src=&qu ... 
- 闲着无聊时写的一个调用天气 API 的小 Demo
		分为两个部分--调用以及实现,并且由于不想折腾,直接使用了 Console 来调用. 通过firefox直接调用 Main 入口,调用以及输出 调用部分没什么好说的,主要是针对 dynamic 类型的 ... 
- 用Go语言写了一个电脑搜索文件的小东西
		package main import ( "bytes" "fmt" "os" "os/exec" "pat ... 
随机推荐
- Porter/Duff,图片加遮罩setColorFilter
			同步发表于http://avenwu.net/2015/02/03/porterduff Fork on github https://github.com/avenwu/support 经常会遇到给 ... 
- Navi.Soft30.框架.WinForm.开发手册
			阅读导航 Navi.Soft30.Core类库.开发手册 http://www.cnblogs.com/xiyang1011/p/5709489.html Navi.Soft30.框架.WinForm ... 
- VS2010+Opencv+SIFT以及出现的问题-关于代码sift_3_c的说明
			http://blog.sina.com.cn/s/blog_a6b913e30101dvrt.html 一.前提 安装Opencv,因该版本的SIFT是基于Opencv的. 下载SIFT源码,见Ro ... 
- ORACLE 10g 64位下载地址
			http://download.oracle.com/otn/nt/oracle10g/10201/102010_win64_x64_database.zip http://download.orac ... 
- 文件上传限制大小 dotnet/C#
			private void Button1_Click(object sender, System.EventArgs e) { if (File1.PostedFile != null) { //上传 ... 
- Android软件安全开发实践(下)
			Android开发是当前最火的话题之一,但很少有人讨论这个领域的安全问题.本系列将分两期,探讨Android开发中常见的安全隐患和解决方案.第一期将从数据存储.网络通信.密码和认证策略这三个角度,带你 ... 
- android 监听软键盘的收起与打开
			参考: http://toughcoder.net/blog/2015/10/09/android-trick-detect-soft-keyboard-show-slash-hide/ packag ... 
- 电商O2O-11种最佳运营模式
			免费模式,是在这种矛盾下应运而生的新型模式.免费模式在未来的几年中,将会不断的渗透到各个行业中,这不单单是加速了行业内部的洗牌速度,更是加速了行业之间的洗牌速度. 未来,免费模式会让行业之间的界限变得 ... 
- Ubuntu下的杀毒
			Ubuntu 11.04 杀毒软件ESET NOD32 Linux防病毒,并不代表没有病毒,只是数量过少.因技术精英发现漏洞后即时打补丁,病毒很少. 有兴趣的同学可以尝试下Ubuntu 11. ... 
- [C++] memset 和sizeof 的使用注意
			因为使用C++写小题目时经常需要清除数组,这里记录下Memset函数的sizeof运算符的使用注意. memset的特点是:将给定地址后连续的内存(包括给定地址),逐个byte初始化为参数中指明的值. ... 
