用c#写的一个局域网聊天客户端 类似小飞鸽
最后在公司實習,新人不給活干,就自己隨便看看,了解一些DevExpress控件啊,編碼規(guī)范啊之類的,自己就尋思著寫一點點小東西練習練習
出于自己對c# socket這塊不熟,就選擇了這塊,順便可以進一步了解委托 代理。
閑話不說,先說下這次做的東西:一個局域網(wǎng)聊天的小軟件 ?主要基于udp的通信,如果讀者還不知道udp or tcp ?那請度娘一下。。。
基本思路(這也都是網(wǎng)上查的,還查了飛鴿傳書的 基本原理,在此感謝網(wǎng)上的各位高手哈):
1:軟件開啟的時候先新開一個線程,該線程充當服務器端,一直死循環(huán)監(jiān)聽
2:開了新線程了,調(diào)用廣播的方法
3:此時如果局域網(wǎng)內(nèi)已經(jīng)有有其它主機打開了這個軟件,將會監(jiān)聽到這個廣播,收到這個廣播后將返回自己的主機,并且將監(jiān)聽到的主機添加自己的在線列表中,當然,發(fā)起廣播的軟件也能收到其它軟件的回信,收到他們的主機后也加入自己的在線列表,這樣各自的列表就都能建立起來,并且將當前的列表加入一個靜態(tài)的泛型列表中(用于以后和其它用戶的通信,維護他們的狀態(tài))
4:通信 發(fā)送消息:雙擊一個主機列表后 ?得到該主機host 傳到交談窗體 并查詢出他主機的endpoint,這樣就可以進行本機和向該endpoint點發(fā)送消息了
5:下線 下線之前軟件會發(fā)一個下線的廣播,其它的軟件接到該廣播的時候?qū)⒃撝鳈C從自己的在線列表中移除
?
整體思路就這樣,下面可以結(jié)合代碼具體看一下
View Code #region Field/// <summary>/// 在非主線程是對控件操作/// </summary>/// <param name="host"></param>private delegate void MyInvoke(string host);/// <summary>/// 充當服務器 進行監(jiān)聽接收/// </summary>private SocketUdpServer udpServer;/// <summary>/// 充當客戶端/// </summary>private SocketUdpClient udpClient;#endregion#region Contructor/// <summary>/// 構(gòu)造函數(shù)/// </summary>public FrmUser(){InitializeComponent();init();}#endregion#region Method/// <summary>/// 初始化數(shù)據(jù)/// </summary>private void init(){LanList.CurrentLanList = new List<LanInfo>();this.Text = "當前局域網(wǎng)內(nèi)在線用戶";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);}/// <summary>/// 上線增加用戶/// </summary>/// <param name="host">用戶主機</param>private void AddUser(string host){this.ilbUserList.Items.Add(host, 0);}/// <summary>/// 下線減少用戶/// </summary>/// <param name="host">用戶主機在列表的序號 懶了以下 應該將回調(diào)的委托參數(shù)定義為int的,這里用了string 到程序需要轉(zhuǎn)化為Int</param>private void RemoveUser(string hostIndex){this.ilbUserList.Items.RemoveAt(Convert.ToInt32(hostIndex));}#endregion#region Event/// <summary>/// 上線事件/// </summary>/// <param name="socket"></param>/// <param name="e"></param>private void OnLine_Event(SocketUdpServer socket, EventArgs e){string host = socket.Message;//如果該上線的用戶在局域網(wǎng)列表中不存在if (!LanList.CurrentLanList.Exists(x => x.Host == host)){while (!this.IsHandleCreated) ;this.ilbUserList.Invoke(new MyInvoke(this.AddUser), host);//將上線用戶添加進靜態(tài)的局域網(wǎng)列表LanList.CurrentLanList.Add(new LanInfo(){Host = host,State = TalkState.Waiting,RemoteEndPoint = socket.RemoteEndPoint});}}/// <summary>/// 下線事件/// </summary>/// <param name="socket"></param>/// <param name="e"></param>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());//將該用戶從局域網(wǎng)列表中移除LanList.CurrentLanList.RemoveAll(x => x.Host == host);}}}/// <summary>/// 出現(xiàn)錯誤的事件/// </summary>/// <param name="socket"></param>/// <param name="e"></param>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();}/// <summary>/// 窗體關閉事 進行下線廣播/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void FrmUser_FormClosed(object sender, FormClosedEventArgs e){this.udpClient.Broadcast(DatagramType.DownLine);this.udpServer.Stop();Application.Exit();}/// <summary>/// 刷新按鈕/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnRefresh_Click(object sender, EventArgs e){//刷新 情況列表中的數(shù)據(jù) 重新上線廣播this.ilbUserList.Items.Clear();LanList.CurrentLanList.Clear();this.udpClient.Broadcast(DatagramType.OnLine);}#endregion?
該頁面主要是在線用戶列表頁面,同時監(jiān)聽其它軟件發(fā)來的上線,下線,獲取主機信息等數(shù)據(jù)報,維護當前在線的用戶 和聊天狀態(tài)
View Code #region Field/// <summary>/// 自己的socket 充當服務器 接收消息/// </summary>private SocketUdpServer selfSocket = null;/// <summary>/// 對話的socket/// </summary>private SocketUdpClient tallSocket = null;/// <summary>/// 談話對方的局域網(wǎng)信息/// </summary>private LanInfo talkLan = null;/// <summary>/// 當前用戶主機/// </summary>private string currentUserHost = "";/// <summary>/// 對控件操作 在非主線程下需要調(diào)用此代理/// </summary>private delegate void MyInvoke(string user,string message);#endregion#region Constructor/// <summary>/// 通過遠端主機名打開窗體/// </summary>/// <param name="host"></param>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();}/// <summary>/// 通過遠端 端點打開窗體/// </summary>/// <param name="remotePoint"></param>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/// <summary>/// 初始化方法/// </summary>private void Initializion(){this.selfSocket = SocketUdpServer.Instance;///綁定收到信息事件this.selfSocket.OnChatComplete += new SocketUdpServer.OnCompleteHander(this.ReceiveEvent);//給談話的socket初始化endpointthis.tallSocket = new SocketUdpClient(this.talkLan.RemoteEndPoint);}/// <summary>/// 加載未讀的信息/// </summary>private void LoadUnReadMessage(){Queue<MessageInfo> 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);}}}/// <summary>/// 添加一行 在listboxcontrol中/// </summary>/// <param name="name">顯示的用戶</param>/// <param name="message">消息</param>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);}/// <summary>/// 發(fā)送信息 由鍵盤回車和發(fā)送按鈕調(diào)用/// </summary>private void SendMessage(){try{string message = this.memInput.Text;if (string.IsNullOrEmpty(message)){XtraMessageBox.Show("發(fā)送信息不能為空");}else{this.tallSocket.Send(message);this.AddLine("我", message);this.memInput.Text = "";}}catch (Exception ex){XtraMessageBox.Show(ex.Message);}}#endregion#region Event/// <summary>/// 表單加載/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void FrmTalk_Load(object sender, EventArgs e){this.LoadUnReadMessage();}/// <summary>/// 接收信息回調(diào)事件/// </summary>/// <param name="socket"></param>/// <param name="e"></param>private void ReceiveEvent(SocketUdpServer socket, EventArgs e){//判斷 遠端的網(wǎng)絡端點是否是當前的 打開的窗體if (string.Compare(this.talkLan.RemoteEndPoint.ToString(), socket.RemoteEndPoint.ToString()) == 0){this.lbxMessage.Invoke(new MyInvoke(this.AddLine), this.talkLan.Host, socket.Message);}}/// <summary>/// 信息發(fā)送按鈕/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnSend_Click(object sender, EventArgs e){this.SendMessage();}private void FrmTalk_FormClosed(object sender, FormClosedEventArgs e){//將其設置為非交談狀態(tài)LanList.SetTalkState(talkLan.Host, TalkState.Waiting);}private void memInput_KeyDown(object sender, KeyEventArgs e){///按下回車事件if (e.KeyCode == Keys.Enter){this.SendMessage();}}#endregion?
該頁面就是聊天頁面,主要是對相應的host進行通信聊天,發(fā)送和接收聊天信息,根據(jù)聊天窗口設置狀態(tài)啊之類的
?
View Code #region Method#region 停止當前監(jiān)聽和斷開線程/// <summary>/// 停止當前服務器的監(jiān)聽和斷開線程/// </summary>public void Stop(){this.listenThread.Abort();this.listenSocket.Close();}#endregion#region 監(jiān)聽/// <summary>/// 開始監(jiān)聽/// </summary>public void Listen(){ThreadStart method = new ThreadStart(this.ListenMethod);this.listenThread = new Thread(method);this.listenThread.Start();}/// <summary>/// 監(jiān)聽的方法/// </summary>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);//定義一個網(wǎng)絡端點IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);//定義要發(fā)送的計算機的地址EndPoint remote = (EndPoint)(sender);//遠程///持續(xù)監(jiān)聽while (true){byte[] data = new byte[1024];//準備接收int recv = this.listenSocket.ReceiveFrom(data, ref remote);string stringData = Encoding.UTF8.GetString(data, 0, recv);//將接收到的信息轉(zhuǎn)化為自定義的數(shù)據(jù)報類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());}}/// <summary>/// 收到數(shù)據(jù)報后的動作/// </summary>/// <param name="type">數(shù)據(jù)報的類型</param>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:///執(zhí)行添加上線用戶事件this.OnLineComplete(this, new EventArgs());break;case DatagramType.DownLine:///執(zhí)行用戶下線事件///如果是自己下線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的監(jiān)聽,定義一些監(jiān)聽事件,在form里面使用該事件就可以了
View Code #region Delegate Event/// <summary>/// 完成一個socket的代理/// </summary>/// <param name="sender"></param>/// <param name="e"></param>public delegate void OnCompleteHander(SocketUdpServer sender, EventArgs e);/// <summary>/// 完成收到一個主機信息 即上線事件/// </summary>public event OnCompleteHander OnLineComplete;/// <summary>/// 完成下線事件/// </summary>public event OnCompleteHander DownLineComplete;/// <summary>/// 完成一次談話 就一條信息/// </summary>public event OnCompleteHander OnChatComplete;/// <summary>/// 有錯誤出現(xiàn)/// </summary>public event OnCompleteHander ErrorAppear;#endregion?
用戶上線事件,下線事件,或者主機事件,chat聊天事件,再服務器接收到信息后 感覺信息分類執(zhí)行不同的事件
? ? ?在CHAT類型的數(shù)據(jù)報重要注意的是,當有數(shù)據(jù)過來接收到 ?但是該主機窗口并未打開時,要將要收到信息加入一個未讀的信息隊列中,當再次開發(fā)對該用戶的聊天窗口時先要加載相應的未讀信息隊列,這樣可以簡單的實現(xiàn)離線信息的發(fā)送
接下來看下信息數(shù)據(jù)報的格式
View Code /****************************************************************** 定義廣播的數(shù)據(jù)格式* Type=OnLine,FromAdress=xxx,ToAdress=zzz,Message=mmm* 類型為上線廣播 從xxx主機到zzz主機 信息是mmm * CHAT這個就是我的信息我的信息 可能有各種=,的字符串* 這種就直接將CHAT去掉后 后面的都為mmm*****************************************************************//// <summary>/// 定義數(shù)據(jù)報里面的幾個字段/// </summary>public class Datagram{#region Property/// <summary>/// 數(shù)據(jù)報的類型 ,/// </summary>public DatagramType Type{get;set;}/// <summary>/// 發(fā)送者的網(wǎng)絡地址/// </summary>public string FromAddress{get;set;}/// <summary>/// 接收者網(wǎng)絡地址/// </summary>public string ToAddress{get;set;}/// <summary>/// 數(shù)據(jù)報的信息/// </summary>public string Message{get;set;}/// <summary>/// 信息 Message的長度/// </summary>public int Length{get {return this.Message.Length;}}#endregion#region Method/// <summary>/// 重寫下ToString/// </summary>/// <returns></returns>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();}/// <summary>/// 將有效字符串轉(zhuǎn)化成數(shù)據(jù)報/// </summary>/// <param name="str"></param>/// <returns></returns>public static Datagram Convert(string str){Datagram data = new Datagram();//前面不是CHAT主要是建立連接 取消連接等信號傳送if (!str.StartsWith("CHAT")){IDictionary<string, string> idict = new Dictionary<string, string>();string[] strlist = str.Split(',');for (int i = 0; i < strlist.Length; i++){//數(shù)據(jù)報字符串的各個鍵值對放進字典類string[] info = strlist[i].Split('=');idict.Add(info[0], info[1]);}data.Type = (DatagramType)Enum.Parse(typeof(DatagramType), idict["Type"]);data.FromAddress = idict["FromAddress"];data.ToAddress=idict["ToAddress"];data.Message = idict["Message"];}else {data.Type = (DatagramType)Enum.Parse(typeof(DatagramType), "Chat");data.Message = str.Substring(4);}return data;}#endregion}#region Enum/// <summary>/// 數(shù)據(jù)報的類型/// </summary>public enum DatagramType{ /// <summary>/// 上線 一應一答/// </summary>OnLine=1,/// <summary>/// 下線 一應/// </summary>DownLine,/// <summary>/// 確認收到 一應/// </summary>/// <summary>/// 正常聊天 一應一答/// </summary>Chat,/// <summary>/// 給予個人的信息/// </summary>GiveInfo}#endregion簡單的定義一下發(fā)送的數(shù)據(jù)報的格式 ?可能發(fā)送的幾種類型:
上線:主要用于軟件剛剛開啟時向局域網(wǎng)內(nèi)發(fā)送上線廣播
下線:軟件在關閉之前再向局域網(wǎng)內(nèi)發(fā)送一次下線廣播
給出主機信息:用于收到上線廣播后 ?再返回一個自己主機信息給對方,讓讓對方知道局域網(wǎng)中這臺主機是上線的
聊天:就是平常的通信 ?這里特別注意的是,為考慮到聊天中也會出來,= ?這兩個協(xié)定的字符串,所以 開頭加CHAT ?表示純粹聊天的數(shù)據(jù)報
View Code /// <summary>/// udp的客戶端 主要用戶發(fā)送數(shù)據(jù)/// </summary>public class SocketUdpClient{#region Feild/// <summary>/// 廣播的socket/// </summary>private Socket broadcastSocket;/// <summary>/// 服務器的端口/// </summary>private int port;/// <summary>/// 遠端的端點/// </summary>private EndPoint remoteEndPoint = null;/// <summary>/// 當前客戶端/// </summary>private Socket client = null;#endregion#region Constructor/// <summary>/// 構(gòu)造函數(shù)/// </summary>public SocketUdpClient(EndPoint point){this.client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);this.remoteEndPoint = point;}/// <summary>/// 無參構(gòu)造函數(shù)/// </summary>public SocketUdpClient(){this.port = 9050;}#endregion#region 進行廣播/// <summary>/// 進行廣播 上線或者下線/// </summary>/// <param name="msg">廣播中發(fā)送的信息</param>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()};//將要發(fā)送的信息改為字節(jié)流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/// <summary>/// 發(fā)送數(shù)據(jù)/// </summary>/// <param name="message">當前的數(shù)據(jù)</param>public void Send(string message){byte[] data = Encoding.UTF8.GetBytes("CHAT" + message);int i = client.SendTo(data, this.remoteEndPoint);}#endregion}socket的client代碼 ?實現(xiàn)廣播 發(fā)送信息 ?
?
以上簡單的邏輯設計+代碼就基本完成了這個簡單的客戶端聊天軟件
說了那么多,接下來看下效果圖:
本機這邊的效果
局域網(wǎng)中另一端的效果
?可以實現(xiàn)簡單的 ?通訊
下面是源碼下載:猛擊我去下載它
大家在下載包中可以發(fā)現(xiàn) ?有兩個項目 ?一個是ITalk,他是我最初在寫的時候使用的,窗體時繼承dev的,效果稍微好一點
為考慮到各大讀者可能沒有安裝dev,所以又一模一樣的改了一個ITalkTradition,傳統(tǒng)的winform
還有源碼里面有可能socket的tcp ?這塊,這個主要是因為我剛剛開始的想法是使用tcp,但是后來發(fā)現(xiàn)請求在線用戶,聊天之類都比較麻煩,就改用了udp,不過 ?本人有精力的話 ?還是要去試著寫寫tcp傳輸文件 這塊
?
這篇文章大致就這樣啦,如果大家有什么疑問或者建議趕快留言吧,還有本源碼和軟件僅供交流學習,用于商業(yè)或者實際應用了出了問題概不負責哈
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/yyl8781697/archive/2012/12/07/csharp-socket-udp.html
總結(jié)
以上是生活随笔為你收集整理的用c#写的一个局域网聊天客户端 类似小飞鸽的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: day21-python模块
- 下一篇: 用shell编写一个三角形图案