C#局域網聊天工具、消息推送實現思路與源碼
當前位置:點晴教程→知識管理交流
→『 技術文檔交流 』
C#局域網聊天工具怎么實現? 1. 網絡通訊編程的基礎便是協議,信息的發送常用的協議有面向連接的TCP協議,以及不面向連接的UDP協議 2. TCP:TransmissionControlProtocol傳輸控制協議,其是一種面向連接的、可靠的字節流服 務。面向連接意味著兩個使用TCP的應用(通常是一個客戶和一個服務器)在彼此交換數據之前必須先建立一個TCP連接。這一過程與打電話很相似,先撥號振鈴,等待對方摘機說“喂”,然后才說明是誰。 3. UDP:UserDatagramProtocol用戶數據報協議(RFC768),UDP傳送數據前并不與對方建立連 接,即UDP是無連接的,在傳輸數據前,發送方和接收方相互交換信息使雙方同步。 4. 系統也要定義自己的通訊協議,來完成一些系統的功能,如用戶上,下線的通知,都要定義 自己的通訊協議來完成相應的功能!也可以稱這種自定義的協議為“命令”. 5. 下面以著名的飛鴿傳書為例,說明其自定義的協議(命令) IPMSG_NOOPERATION不進行任何操作 IPMSG_BR_ENTRY用戶上線 IPMSG_BR_EXIT用戶退出 IPMSG_ANSENTRY通報在線 IPMSG_SENDMSG發送消息 IPMSG_RECVMSG通報收到消息 IPMSG_GETFILEDATA請求通過TCP傳輸文件 IPMSG_RELEASEFILES停止接收文件 IPMSG_GETDIRFILES請求傳輸文件夾以“IPMSG_BR_ENTRY用戶上線”和“IPMSG_ANSENTRY通報在線”為例說明命令處理流程:當程序啟動時,命令IPMSG_BR_ENTRY被廣播到網絡中,向所有在線的用戶提示一個新用戶的到達(即表示“我來了”);所有在線用戶將把該新上線用戶添加到自己的用戶列表中,并向該新上線用戶發送IPMSG_ANSENTRY命令(即表示“我在線”);該新上線用戶接收到IPMSG_ANSENTRY命令后即將在線用戶添加到自己的用戶列表中。 PS:根據本系統的特征,可以在聊天部分采用UDP協議,在文件傳輸,視頻,語音功能上采用TCP協議 6. 程序啟動就要發送廣播消息,如何發送廣播消息,以及C#如何實現廣播. 第一部分.什么是廣播地址,以及廣播地址怎么計算 1.1廣播地址是什么? 主機號全為1,用于向一個網絡內的所有主機發送信息的IP地址.如:受限的廣播地址是255.255.255.255。該地址用于主機配置過程中IP數據報的目的地址,此時,主機可能還不知道它所在網絡的網絡掩碼,甚至連它的IP地址也不知道。在任何情況下,路由器都不轉發目的地址為受限的廣播地址的數據報,這樣的數據報僅出現在本地網絡中。 PS:一般無特殊要求廣播地址選擇255.255.255.255即可. 1.2計算方法 首先計算網絡地址=IP地址邏輯與(&)子網掩碼 先把IP,子網掩碼轉為2進制,然后進行邏輯與運算,得出網絡地址 例: IP192.168.1.3子網掩碼255.255.0.0 IP轉二進制11000000.10100100.00000001.00000011 子網掩碼11111111.11111111.00000000.00000000 與運算后11000000.10100100.00000000.00000000 192.168.0.0這就是網絡地址,其中子網掩碼全1對應為網絡號,全0對應的是主機號,即192.168.0.0對應的網絡號為192.168,主機號為0.0.將網絡地址主機部分全取反后得到的地址便是廣播地址: 廣播地址11000000.10100100.11111111.11111111 換成10進制則為192.168.0.0 第二部分.C#利用UDP協議如何實現廣播 2.1如何實現UDP廣播,直接舉例說明: button1_Click時使用了UDP廣播向外發送了數據 RecData()在后臺接受UDP協議的消息 //UDP通過廣播實現群發功能 namespace BroadcastExample { public partial class Form1:Form { delegate void AppendStringCallback(stringtext); AppendStringCallback appendstringcallback; //使用的接收端口51008 ///<summary> ///端口號 ///</summary> private int port=51008; ///<summary> ///udp連接對象 ///</summary> private UdpClient udpclient; public Form1() { InitializeComponent(); appendstringcallback = new AppendStringCallback(AppendString); } ///<summary> ///委托對象的處理過程 ///</summary> ///<paramname="text"></param> private void AppendString(stringtext) { if(richtextBox2.InvokeRequired==true) { this.Invoke(appendstringcallback,text); } else { richtextBox2.AppendText(text+"\r\n"); } } ///<summary> ///在后臺運行的接收線程 ///</summary> private void RecData() { //本機指定端口接收 udpclient=new UdpClient(port); IPEndPoint remote=null; //接收從遠程主機發送過來的信息 while(true) { try { //關閉udpclient時此句會產生異常 byte[]bytes=udpclient.Receive(refremote); stringstr=Encoding.UTF8.GetString(bytes,0,bytes.Length); AppendString(string.Format("來自{0}:{1}",remote,str)); } catch { //退出循環,結束線程 break; } } } privatevoidForm1_Load(objectsender,EventArgse) { //創建一個線程接收接收遠程主機發來的信息 Thread mythread=new Thread(new ThreadStart(RecData)); //將線程設為后臺運行 mythread.IsBackground=true; mythread.Start(); } private void Form1_FormClosing(objectsender,FormClosingEventArgse) { udpclient.Close(); } private void button1_Click(objectsender,EventArgse) { UdpClient myUdpclient=newUdpClient(); try { IPEndPoint iep=new IPEndPoint(IPAddress.Broadcast,port); byte[]bytes=System.Text.Encoding.UTF8.GetBytes(textBox1.Text); myUdpclient.Send(bytes,bytes.Length,iep); textBox1.Clear(); myUdpclient.Close(); textBox1.Focus(); } catch(Exceptionerr) { MessageBox.Show(err.Message,"發送失敗"); } finally { myUdpclient.Close(); } } } } 啟動主程序時,同時啟動UDP的監聽,這時應該使用集合來做為消息隊列的緩存,以便用戶能在任何時候瀏覽到消息.這個集合一般在主程序中定義,而用戶接受消息,一般我們會彈出窗口給用戶來瀏覽消息,以及在新窗口中回復消息,那如何將主窗口中的消息,傳遞到消息顯示窗體中呢? 如何是Web(ASP.net)我們可以封裝到form中傳值,或者request傳值,甚至可以在URL中接參數直接傳值,而winform中窗體傳值以上方法就都不在能用了. 在windowsform之間傳值,我總結了有四個方法:全局變量、屬性、窗體構造函數和delegate。 第一個全局變量: 這個最簡單,只要把變量描述成static就可以了,在form2中直接引用form1的變量,代碼如下: 在form1中定義一個static變量publicstaticinti=9; Form2中的鈕扣按鈕如下: privatevoidbutton1_Click(objectsender,System.EventArgse) { textBox1.Text=Form1.i.ToString(); } 第二個方法是利用屬性: 假設我們需要點擊主窗體FMMain中的某一個按鈕時打開子窗體FMChild并將某一個值傳給子窗體FMChild,一般情況下,我們點擊按鈕顯示子窗體FMChild的代碼為: FMChildfmChild=newFMChild();fmChild.ShowDialog();fmChild.Dispose(); 如果我們需要將主窗體FMMain中的stringstrValueA的值傳給FMChild,那么我們首先對strValueA進行如下處理: privatestringstrValueA;publicstringStrValueA{get{returnstrValueA;}set{strValueA=value;}} 使其成為主窗體FMMain的一個屬性,接著修改顯示子窗體的代碼為以下兩種的其中一種。 方法一: FMChildfmChild=newFMChild();fmChild.ShowDialog(this);fmChild.Dispose(); 方法二: FMChildfmChild=newFMChild();FMChild.Owner=this;fmChild.ShowDialog();fmChild.Dispose(); 然后在修改子窗體FMChild中申明一個主窗體FMMain對象, FMMainfmMain; 在需要使用主窗體FMMain的stringstrValueA的地方加上如下代碼: fmMain=(FMMain)this.Owner; 這樣,就可以獲得主窗體FMMain中strValueA的值了。 這時,如果你需要將子窗體FMChild中的stringstrValueB傳給主窗體FMMain,同樣處理stringstrValueB. privatestringstrValueB;publicstringStrValueB{get{returnstrValueB;}set{strValueB=value;}} 那么你在關閉子窗體代碼fmChild.Dispose();后,可以寫一些代碼來保存或者處理FMChild的strValueB,例如: stringstrTmp=fmChild.StrValueB; 第三個方法是用構造函數: Form1的button按鈕這樣寫: privatevoidbutton1_Click(objectsender,System.EventArgse) { Form2temp=newForm2(9); temp.Show(); } Form2的構造函數這樣寫: publicForm2(inti) { InitializeComponent(); textBox1.Text=i.ToString(); } 第四個方法是用delegate,代碼如下: Form2中先定義一個delegate publicdelegatevoidreturnvalue(inti); publicreturnvalueReturnValue; form2中的button按鈕代碼如下: privatevoidbutton1_Click(objectsender,System.EventArgse) { if(ReturnValue!=null) ReturnValue(8); } Form1中的button按鍵如下: privatevoidbutton1_Click(objectsender,System.EventArgse) { Form2temp=newForm2(); temp.ReturnValue=newtemp.Form2.returnvalue(showvalue); temp.Show(); } privatevoidshowvalue(inti) { textBox1.Text=i.ToString(); } 點擊form2的button,form1中的textbox中的值就會相應變化。 在這四個方法中, 第一個是雙向傳值,也就是說,form1和form2改變i的值,另一方也會受到影響。 第二個方法可以單向也可以雙向傳值。 第三個方法是form1->form2單向傳值。 第四個方法是form2->form1單向傳值。 現在很多程序都有托盤功能,而我們的聊天工具更是如此,無論是QQ,旺旺,飛鴿傳書等等,都是 以托盤的形式工作在后臺,對消息進行監聽的.而VS2005給我們提供了現成的控件,來完成托盤的功能,下面我結合代碼講解下項目中可能用到的托盤技巧. 1.如何實現托盤功能: 在VS2005中直接添加notifyIcon控件,然后設置下icon屬性,給其設置個圖標即可,使用托盤功能. 但是托盤并不能實現我們要求的功能,具體的功能實現,需要我們手工添加代碼實現. 2.如何最小化時自動到托盤 private void Form1_Resize(objectsender,System.EventArgse) { if(this.WindowState==FormWindowState.Minimized) { this.Visible=false; this.notifyIcon1.Visible=true; } } 3.如何雙擊托盤恢復原狀 private void notifyIcon1_Click(objectsender,System.EventArgse) { this.Visible=true; this.WindowState=FormWindowState.Normal; this.notifyIcon1.Visible=false; } 4.實現托盤的閃爍功能(如QQ有消息時的閃爍) (1).首先我們在空白窗體中拖入一個NotifyIcon控件和定時控件 privateSystem.Windows.Forms.NotifyIconnotifyIcon1; privateSystem.Windows.Forms.Timertimer1; (2).其次,我們準備兩張ico圖片,用來顯示在任務欄,其中一張可用透明的ico圖片,分別叫做1.ico和2.ico;并且建立兩個icon對象分別用來存放兩個ico圖片; privateIconico1=newIcon("1.ico"); privateIconico2=newIcon("2.ICO");//透明的圖標 (3).在Form_load中初始化notifyicon: privatevoidForm1_Load(objectsender,System.EventArgse) { this.notifyIcon1.Icon=ico1;//設置程序剛運行時顯示在任務欄的圖標 this.timer1.Enable=true;//將定時控件設為啟用,默認為false; } (4).先設置一個全局變量i,用來控制圖片索引,然后創建定時事件,雙擊定時控件就可以編輯 inti=0; privatevoidtimer1_Tick(objectsender,System.EventArgse) { //如果i=0則讓任務欄圖標變為透明的圖標并且退出 if(i<1) { this.notifyIcon1.Icon=ico2; i++; return; } //如果i!=0,就讓任務欄圖標變為ico1,并將i置為0; else i=0; } 由于消息傳輸要求較低,而且為了簡化聊天的步驟,在局域網聊天中,采用UDP是非常好的選擇.因為UDP可以不用連接,在獲取用戶列表后,直接點擊用戶名就可以發送消息,減少了等待連接等繁瑣 1.UDP發送信息 namespaceXChat.SendMes { public class MsgSend { private UdpClient udp=null; private int PORT; private IPEndPoint endP=null; public MsgSend() { this.PORT=58888; } publicMsgSend(intport) { this.PORT=port; } ///<summary> ///發送信息 ///</summary> ///<paramname="hostName">要發送到的主機名</param> ///<paramname="message">要發送的信息</param> publicvoidSendMessage(stringhostName,stringmessage) { this.udp=newUdpClient(); endP=newIPEndPoint(Dns.GetHostEntry(hostName).AddressList[0],PORT); try { //byte[]b=Encoding.ASCII.GetBytes(hostName); byte[]b=Encoding.UTF8.GetBytes(message); udp.Send(b,b.Length,endP); } catch { System.Windows.Forms.MessageBox.Show("發送出錯!"); } finally { this.udp.Close(); } } } } 要使用時直接new MsgSend().SendMessage(主機名,消息); 2.UDP接收消息 namespaceXChat.SendMes { //設置消息到前臺的委托 public delegate voidSet Message(stringmes); public class MsgRecive { private int PORT; private UdpClient udp=null; private Thread recThread=null; private IPEndPoint ipep=null; private SetMessages etMes; public MsgRecive() { this.PORT=58888; } publicMsgRecive(intport) { this.PORT=port; } ///<summary> ///開啟后臺接受消息線程 ///</summary> ///<paramname="setMes">傳入設置消息的委托</param> publicvoidStartReciveMsg(SetMessagesetMes) { this.setMes=setMes; udp=newUdpClient(PORT); recThread=newThread(newThreadStart(ReciveMsg)); recThread.Start(); } ///<summary> ///關閉后臺消息接收線程 ///</summary> publicvoidCloseReciveMsg() { recThread.Abort(); //recThread.Join(); udp.Close(); } privatevoidReciveMsg() { while(true) //這句很重要,否則CPU很容易100% Thread.Sleep(500); byte[] b = udp.Receive(refipep); string message=Encoding.UTF8.GetString(b,0,b.Length); this.setMes(message); } } } } 在前臺private MsgRecive mr=null; public xchatFrm() { ?? this.mr=newMsgRecive(port); this.mr.StartReciveMsg(newSetMessage(GetMes)); ?? } 這幾天一直想寫一個類似QQ文件發送的東西,上網找了一些資料,都不是很理想,下面我把我的思路和基本實現代碼說下。 為了把問題說清楚,把一些變量都直接附值了,并沒有通過輸入附值 private string path = "F:\\SmartMovie.EXE"; //要發送的文件 private Socket s; private void listen() { string ip = "127.0.0.1"; //遠程IP 這里定義為自己的機器 IPAddress[] ih = Dns.GetHostAddresses(ip); //獲得IP列表 IPAddress newip = ih[0]; //獲取IP地址 int port = 6789; //定義端口 IPEndPoint Conncet = new IPEndPoint(newip, port); //構造結點 s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //初始化socket try { s.Connect(Conncet); //連接遠程服務器 if (s.Connected) //如果連接成功 s.Connected 則為true 否則為 false { Thread t = new Thread(new ThreadStart(set)); //創建進程 t.Start(); //開始進程 Console.WriteLine("發送完畢") } } catch(NullReferenceException e) { Console.WriteLine("{0}",e); } private void set() //創建set函數 { Console.WriteLine("開始發送數據"); byte[] b = new byte[10000000]; //創建文件緩沖區,這里可以認為文件的最大值 FileStream file = File.Open(path, FileMode.Open,FileAccess.Read); //創建文件流 int start = 0; int end = (int)file.Length; //獲取文件長度 文件傳送如果有需要超過int的范圍估計就要改寫FileStream類了 try { while (end != 0) { int count = file.Read(b, start, end); //把數據寫進流 start += count; end -= count; } while (start != 0) { int n = s.Send(b, end, start, SocketFlags.None); //用Socket的Send方法發送流 end += n; start -= n; } file.Close(); //關閉文件流 s.Close(); //關閉Socket } catch (NullReferenceException e) { Console.WriteLine("{0}", e); } 這樣文件發送的模型就實現了 接下去實現文件的接收,首先要確定對方發送文件的長度,其實上面的那段還要加入發送文件長度的功能,實現很簡單,就是發送int變量end ,然后要求接收代碼返回一個Boolean確定是否發送,這里為了更簡明的說清楚原理并沒有實現 private void get() { string path = "G:\\da.exe"; //接收的文件 FileStream file = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write); //寫入文件流 TcpListener listen = new TcpListener(6789); //監聽端口 Socket s1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //定義Socket并初始化 try { listen.Start(); //開始監聽 s1 = listen.AcceptSocket(); //獲取Socket連接 byte[] data = new byte[10000000]; //定義緩沖區 int longer = data.Length; int start = 0; int mid = 0; if (s1.Connected) //確定連接 { Console.WriteLine("連接成功"); int count = s1.Receive(data, start, longer, SocketFlags.None); //把接收到的byte存入緩沖區 mid += count; longer -= mid; while (count != 0) { count = s1.Receive(data, mid, longer, SocketFlags.None); mid += count; longer -= mid; } file.Write(data, 0, 1214134); //寫入文件,1214134為文件大小,可以用socket發送獲得,代碼前面已經說明。 s1.Close(); file.Close(); } } catch(NullReferenceException e) { Console.WriteLine("{0}",e); } } 該文章在 2016/12/23 18:45:29 編輯過 |
關鍵字查詢
相關文章
正在查詢... |