第18章 P2P應(yīng)用編程(正稿第2稿20060119)_第1頁(yè)
第18章 P2P應(yīng)用編程(正稿第2稿20060119)_第2頁(yè)
第18章 P2P應(yīng)用編程(正稿第2稿20060119)_第3頁(yè)
第18章 P2P應(yīng)用編程(正稿第2稿20060119)_第4頁(yè)
第18章 P2P應(yīng)用編程(正稿第2稿20060119)_第5頁(yè)
已閱讀5頁(yè),還剩8頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第18章P2P應(yīng)用編程第18章P2P應(yīng)用編程PAGE12PAGE13PAGE1第18章P2P應(yīng)用編程近年來(lái),P2P的發(fā)展非常迅速。采用P2P方式實(shí)現(xiàn)的軟件也越來(lái)越多,涉及到通信、互動(dòng)游戲、媒體播放多種網(wǎng)絡(luò)應(yīng)用。目前人們普遍認(rèn)為,P2P在加強(qiáng)網(wǎng)絡(luò)上人的交流、文件交換、深度搜索、分布計(jì)算以及協(xié)同工作等方面大有前途。18.1P2P基礎(chǔ)知識(shí)P2P是Peer-to-Peer的縮寫,也叫對(duì)等互聯(lián)或點(diǎn)對(duì)點(diǎn)技術(shù)。與TCP、UDP不同,P2P并不是一種新的協(xié)議,而是利用現(xiàn)有網(wǎng)絡(luò)協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)或資源信息共享的技術(shù),它使用的不一定是TCP協(xié)議,也可能是UDP協(xié)議或者其他協(xié)議。使用P2P技術(shù),可以讓一臺(tái)計(jì)算機(jī)與另一臺(tái)計(jì)算機(jī)直接交換數(shù)據(jù),通過(guò)Internet直接使用對(duì)方的文件,而不必像傳統(tǒng)C/S模式全部通過(guò)服務(wù)器處理。P2P的特點(diǎn)主要有:1.對(duì)等模式與傳統(tǒng)的服務(wù)器/客戶端模式不同,使用P2P技術(shù)實(shí)現(xiàn)的每個(gè)計(jì)算機(jī)節(jié)點(diǎn)既是客戶端,也是服務(wù)器,其功能的提供是對(duì)等的,人們可以直接連接到安裝了相同的P2P軟件的其他用戶的計(jì)算機(jī)進(jìn)行數(shù)據(jù)交互,而不需要提供專門的服務(wù)器。2.分布式網(wǎng)絡(luò)數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)相對(duì)于目前流行的C/S、B/S的“集中式”網(wǎng)絡(luò)數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),P2P最大的特點(diǎn)在于“分散”。網(wǎng)絡(luò)中所有的計(jì)算、存儲(chǔ)和網(wǎng)絡(luò)連接能力都分布在非集中式網(wǎng)絡(luò)的“對(duì)等伙伴”上。例如,以前客戶端下載文件都是從服務(wù)器上下載,而P2P技術(shù)則改變了以服務(wù)器為中心的狀態(tài),每個(gè)節(jié)點(diǎn)可以各下載一部分,然后互相從對(duì)方或者其他節(jié)點(diǎn)下載。采用這種方式,大量用戶同時(shí)下載不但不會(huì)形成服務(wù)器網(wǎng)絡(luò)帶寬瓶頸,造成網(wǎng)絡(luò)堵塞,反而加快了下載速度。這種方法被認(rèn)為最能發(fā)揮互聯(lián)網(wǎng)的優(yōu)勢(shì)。分布式計(jì)算是繼“服務(wù)器/客戶端”結(jié)構(gòu)后新興的網(wǎng)絡(luò)應(yīng)用模式。在傳統(tǒng)的“服務(wù)器/客戶端”應(yīng)用系統(tǒng)中,客戶端與服務(wù)器有明確的分界,常常發(fā)生客戶端能力過(guò)剩,服務(wù)器能力不足或網(wǎng)路堵塞的現(xiàn)象。P2P系統(tǒng)中的使用者能同時(shí)扮演客戶端和服務(wù)器的多重角色,使兩個(gè)使用者之間能不通過(guò)服務(wù)器而直接進(jìn)行信息分享,以構(gòu)建具有自主、開放、異質(zhì)、延展等特性的分布式網(wǎng)際網(wǎng)絡(luò)應(yīng)用系統(tǒng)。從計(jì)算模式上看,P2P更加符合分布式計(jì)算的理念。其所倡導(dǎo)的計(jì)算能力邊緣化、計(jì)算資源共享等思想,剛好與網(wǎng)格技術(shù)不謀而合。通過(guò)P2P技術(shù),人們可以在不改變?cè)谢A(chǔ)設(shè)施的基礎(chǔ)上,實(shí)現(xiàn)對(duì)底層計(jì)算資源的控制和調(diào)用。P2P的設(shè)計(jì)模式可以分為兩大類:一種是單純型P2P架構(gòu),沒(méi)有專用的服務(wù)器;另一種是混合型P2P架構(gòu),即單純型和專用服務(wù)器相結(jié)合的架構(gòu)。單純型P2P架構(gòu)沒(méi)有中央服務(wù)器,各個(gè)節(jié)點(diǎn)之間直接交互信息。這種方式的優(yōu)點(diǎn)是使用方便,任何一臺(tái)計(jì)算機(jī)只要安裝了同一個(gè)P2P應(yīng)用軟件,就可以和其他安裝這個(gè)軟件的計(jì)算機(jī)直接通信。而正是由于沒(méi)有中央服務(wù)器參與協(xié)調(diào),這種方式的使用范圍就比較有限了。原因很簡(jiǎn)單,一臺(tái)計(jì)算機(jī)要和另一臺(tái)計(jì)算機(jī)連接,必須要知道對(duì)方的IP地址和監(jiān)聽端口,否則就無(wú)法向?qū)Ψ桨l(fā)送信息。而這個(gè)工作只能通過(guò)人來(lái)處理,即通過(guò)軟件提供的手工操作功能將對(duì)方的IP地址和端口加入到搜索范圍內(nèi),無(wú)法利用計(jì)算機(jī)自動(dòng)搜索擴(kuò)展。從原理上說(shuō),互聯(lián)網(wǎng)最基本的協(xié)議TCP/IP并沒(méi)有客戶機(jī)和服務(wù)器的概念,所有的設(shè)備都是通訊的平等的一端。起初,所有的互聯(lián)網(wǎng)上的系統(tǒng)都同時(shí)具有服務(wù)器和客戶機(jī)的功能。當(dāng)然,后來(lái)發(fā)展的軟件的確采用了客戶機(jī)/服務(wù)器的結(jié)構(gòu):瀏覽器和Web服務(wù)器,郵件客戶端和郵件服務(wù)器。但是,對(duì)于服務(wù)器來(lái)說(shuō),它們之間仍然是對(duì)等聯(lián)網(wǎng)的。以E-mail為例,互聯(lián)網(wǎng)上并沒(méi)有一個(gè)巨大的、惟一的郵件服務(wù)器來(lái)處理所有的E-mail,而是對(duì)等聯(lián)網(wǎng)的郵件服務(wù)器相互協(xié)作把E-mail傳送到相應(yīng)的服務(wù)器上去?;旌闲蚉2P架構(gòu)則是將P2P和客戶/服務(wù)器模式相結(jié)合,此時(shí)的中央服務(wù)器僅起到促成各節(jié)點(diǎn)協(xié)調(diào)和擴(kuò)展的功能。安裝了P2P軟件的各個(gè)計(jì)算機(jī)開始全部和索引服務(wù)器連接,以便告知自己監(jiān)聽的IP地址和端口,然后再通過(guò)索引服務(wù)器告知其他與自己連接的計(jì)算機(jī),每一臺(tái)計(jì)算機(jī)的連接和斷開連接都通過(guò)服務(wù)器通知網(wǎng)絡(luò)上有聯(lián)系的計(jì)算機(jī)。這樣就減輕了每臺(tái)計(jì)算機(jī)搜索其他計(jì)算機(jī)的負(fù)擔(dān),擴(kuò)展也比較方便,而真正的信息交互則仍然通過(guò)點(diǎn)對(duì)點(diǎn)直接完成。但帶來(lái)的缺點(diǎn)就是必須服務(wù)器正常工作才能搜索到其他計(jì)算機(jī)。1999年,Napster首先發(fā)掘了P2P在文件共享方面的潛力,推出面向全球互聯(lián)網(wǎng)用戶的MP3自由下載服務(wù)。僅1年間,其注冊(cè)會(huì)員就達(dá)到300萬(wàn)。Napster正是喚醒了深藏在互聯(lián)網(wǎng)背后的對(duì)等聯(lián)網(wǎng)。實(shí)際上,文件共享功能在局域網(wǎng)中是再平常不過(guò)的事情。但是Napster的成功促使人們認(rèn)識(shí)到把這種“對(duì)等聯(lián)網(wǎng)”拓展到整個(gè)互聯(lián)網(wǎng)范圍的可能性。事實(shí)上,網(wǎng)絡(luò)上現(xiàn)有的許多服務(wù)都可以歸入P2P的行列。即時(shí)通信系統(tǒng)例如ICQ、YahooPager、微軟的MSNMessenger以及國(guó)內(nèi)的OICQ、POPO等都是最流行的P2P應(yīng)用。目前比較流行的下載類P2P軟件的典型應(yīng)屬BT(BitTorrent),它采用一種結(jié)構(gòu)化網(wǎng)絡(luò)結(jié)構(gòu),利用分布式哈希表(DHT)技術(shù),使每個(gè)獨(dú)立節(jié)點(diǎn)都不需要維護(hù)整個(gè)網(wǎng)絡(luò)信息,只在節(jié)點(diǎn)中存儲(chǔ)其臨近的后繼節(jié)點(diǎn)信息,就可以有效地找到其他目標(biāo)節(jié)點(diǎn)。近年來(lái)悄然興起的互聯(lián)網(wǎng)視頻直播軟件也是P2P的應(yīng)用之一。這類軟件使用網(wǎng)狀結(jié)構(gòu),支持多種格式的流媒體文件,節(jié)點(diǎn)間動(dòng)態(tài)查找就近連接。其工作原理是創(chuàng)建系統(tǒng)內(nèi)部的虛擬組播技術(shù),利用用戶的剩余處理能力和帶寬為下游客戶提供客戶端代理,每一個(gè)授權(quán)用戶都可以將收到的媒體流進(jìn)行復(fù)制或分裂成多個(gè)流發(fā)送到其他用戶,網(wǎng)絡(luò)中每個(gè)用戶都可以是媒體的使用者同時(shí)也是媒體的分發(fā)者。用虛擬組播技術(shù)媒體流可以不斷的被增加和復(fù)制,并一個(gè)接一個(gè)地傳遞到網(wǎng)絡(luò)的其他用戶,從而在網(wǎng)絡(luò)邊界形成一個(gè)媒體流的虛擬交叉網(wǎng)絡(luò)。當(dāng)用戶需要播放某一頻道節(jié)目時(shí),組播系統(tǒng)就會(huì)將它加入到相應(yīng)組播組中,使用戶迅速得到服務(wù)。利用P2P的虛擬組播技術(shù)實(shí)現(xiàn)了在線用戶越多,視頻播放越流暢的特性。由此可見(jiàn),現(xiàn)有Internet網(wǎng)絡(luò)架構(gòu)下點(diǎn)播式的視頻業(yè)務(wù),使用P2P技術(shù)非常合適。隨著計(jì)算機(jī)技術(shù)的發(fā)展和寬帶網(wǎng)的進(jìn)一步普及,P2P應(yīng)用的優(yōu)勢(shì)越來(lái)越明顯。它可以充分利用互聯(lián)網(wǎng)的邊緣資源,即用戶的計(jì)算能力、存儲(chǔ)能力和帶寬,甚至每個(gè)計(jì)算機(jī)硬盤的內(nèi)容。由于P2P網(wǎng)絡(luò)的非中心化和自發(fā)組織的體系結(jié)構(gòu)特性,使其具有非常好的健壯性。也正是P2P網(wǎng)絡(luò)的靈活性和易操作性提升了互聯(lián)網(wǎng)用戶的共享和參與熱情,從而帶動(dòng)互聯(lián)網(wǎng)的進(jìn)一步發(fā)展。18.2P2P應(yīng)用舉例本節(jié)首先通過(guò)單純型P2P架構(gòu)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天程序,然后介紹通過(guò)P2P技術(shù)設(shè)計(jì)實(shí)際應(yīng)用程序的思路。利用這個(gè)程序,可以在網(wǎng)絡(luò)中向已知計(jì)算機(jī)發(fā)送、接收字符串信息。程序同時(shí)既作為服務(wù)器端,又作為客戶端。任何一臺(tái)計(jì)算機(jī)只要安裝了這個(gè)程序,就可以通過(guò)Internet與加入到好友列表中的任何一個(gè)好友即時(shí)聊天?!纠?8-1】使用P2P技術(shù)設(shè)計(jì)一個(gè)簡(jiǎn)易聊天程序,要求不使用專用的主服務(wù)器,只要將好友添加到好友列表中,就能檢測(cè)到好友是否在線,并相互發(fā)送聊天信息。(1)創(chuàng)建一個(gè)名為P2PExample的Windows應(yīng)用程序,將Form1.cs換名為FormP2P.cs,然后在該設(shè)計(jì)窗體內(nèi)設(shè)計(jì)圖18-1所示的界面。圖18-1FormP2P.cs的設(shè)計(jì)界面圖18-1FormP2P.cs的設(shè)計(jì)界面listViewMyFriendlistBoxMessagetextBoxLocalIptextBoxMyFriendIpAddresstextBoxSendMessagetextBoxLocalPorttextBoxMyFriendPorttextBoxTimeIntervaltextBoxTimerStatusbuttonAddFriendbuttonSendMessagebuttonStartTimerbuttonStopTimertimerSecond這是完成聊天功能的主界面。由于不存在主服務(wù)器,所以添加好友時(shí),需要提供好友所用計(jì)算機(jī)的IP地址和端口號(hào)。為了方便起見(jiàn),程序中自動(dòng)生成并顯示出本機(jī)當(dāng)前所用的IP地址和端口,如果程序運(yùn)行時(shí)IP地址沒(méi)有顯示出來(lái),就無(wú)法和別的計(jì)算機(jī)連接。(2)添加命名空間引用。usingSystem.IO;usingSystem.Net.Sockets;usingSystem.Net;usingSystem.Threading;(3)在構(gòu)造函數(shù)上方添加字段聲明,并在構(gòu)造函數(shù)中添加代碼。privateThreadmyThread;privateTcpListenertcpListener;privateIPAddressmyIPAddress;privateintmyPort;privateSystem.Diagnostics.StopwatchsecondWatch;publicFormP2P(){InitializeComponent();secondWatch=newSystem.Diagnostics.Stopwatch();ColumnHeaderipColumn=newColumnHeader();ipColumn.Text="IP地址";ipColumn.Width=136;ColumnHeaderportColumn=newColumnHeader();portColumn.Text="端口號(hào)";ColumnHeaderonlineColumn=newColumnHeader();onlineColumn.Text="是否在線";onlineColumn.Width=71;listViewMyFriend.View=View.Details;listViewMyFriend.Columns.AddRange(newColumnHeader[]{ipColumn,portColumn,onlineColumn});}(4)添加Load事件代碼。privatevoidFormP2P_Load(objectsender,EventArgse){//啟動(dòng)秒表secondWatch.Start();timerSecond.Enabled=true;buttonStartTimer.Enabled=false;buttonStopTimer.Enabled=true;//使用代理指定在線程上執(zhí)行的方法ThreadStartmyThreadStartDelegate=newThreadStart(Listening);//創(chuàng)建一個(gè)用于監(jiān)聽的線程對(duì)象,通過(guò)代理執(zhí)行線程中的方法myThread=newThread(myThreadStartDelegate);//啟動(dòng)線程myThread.Start();}(5)添加線程執(zhí)行的方法。//該方法是通過(guò)代理調(diào)用執(zhí)行的privatevoidListening(){Socketsocket=null;//獲取本機(jī)第一個(gè)可用IP地址myIPAddress=(IPAddress)Dns.GetHostAddresses(Dns.GetHostName()).GetValue(0);Randomr=newRandom();while(true){try{//隨機(jī)產(chǎn)生一個(gè)0-65535之間的端口號(hào)myPort=r.Next(65535);//創(chuàng)建TcpListener對(duì)象,在本機(jī)的IP和port端口監(jiān)聽連接到該IP和端口的請(qǐng)求tcpListener=newTcpListener(myIPAddress,myPort);tcpListener.Start();//顯示IP地址和端口ShowLocalIpAndPort();//在信息窗口中顯示成功信息ShowMyMessage(string.Format("嘗試用端口{0}監(jiān)聽成功",myPort));ShowMyMessage(string.Format("<message>[{0}]{1:h點(diǎn)m分s秒}開始在{2}端口監(jiān)聽與本機(jī)的連接",myIPAddress,DateTime.Now,myPort));break;}catch{//繼續(xù)while循環(huán),以便隨機(jī)找下一個(gè)可用端口號(hào),同時(shí)顯示失敗信息ShowMyMessage(string.Format("嘗試用端口{0}監(jiān)聽失敗",myPort));}}while(true){try{//使用阻塞方式接收客戶端連接,根據(jù)連接信息創(chuàng)建TcpClient對(duì)象//注意:AcceptSocket接收到新的連接請(qǐng)求才會(huì)繼續(xù)執(zhí)行其后的語(yǔ)句socket=tcpListener.AcceptSocket();//如果往下執(zhí)行,說(shuō)明已經(jīng)根據(jù)客戶端連接請(qǐng)求創(chuàng)建了套接字//使用創(chuàng)建的套接字接收客戶端發(fā)送的信息NetworkStreamstream=newNetworkStream(socket);StreamReadersr=newStreamReader(stream);stringreceiveMessage=sr.ReadLine();inti1=receiveMessage.IndexOf(",");inti2=receiveMessage.IndexOf(",",i1+1);inti3=receiveMessage.IndexOf(",",i2+1);stringipString=receiveMessage.Substring(0,i1);stringportString=receiveMessage.Substring(i1+1,i2-i1-1);stringmessageTypeString=receiveMessage.Substring(i2+1,i3-i2-1);stringmessageString=receiveMessage.Substring(i3+1);ShowMyMessage(ipString,portString,messageTypeString,messageString);}catch{//通過(guò)停止TcpListener使AcceptSocket()出現(xiàn)異常//在異常處理中關(guān)閉套接字并終止線程if(socket!=null){if(socket.Connected){socket.Shutdown(SocketShutdown.Receive);}socket.Close();}myThread.Abort();}}}在第一個(gè)while循環(huán)中,首先隨機(jī)生成一個(gè)端口號(hào),然后使用本機(jī)第一個(gè)IP地址和產(chǎn)生的端口進(jìn)行監(jiān)聽,如果不出現(xiàn)異常,說(shuō)明該IP地址和端口號(hào)可用,退出while循環(huán);否則繼續(xù)隨機(jī)產(chǎn)生下一個(gè)端口,直到找到可用的端口為止。在第二個(gè)while循環(huán)中,使用AcceptSocket方法接收到該端口的連接請(qǐng)求,在接收到連接請(qǐng)求之前,該方法一直處于阻塞方式,一旦接收到新的連接請(qǐng)求,就創(chuàng)建一個(gè)對(duì)應(yīng)的套接字對(duì)象,然后就可以利用這個(gè)套接字對(duì)象創(chuàng)建網(wǎng)絡(luò)流對(duì)象,再利用StreamReader從網(wǎng)絡(luò)流中一直讀取字符,直到遇到回車換行為止。注意,這里使用回車換行作為每條信息之間的分隔符。在介紹TCP協(xié)議時(shí),讀者已經(jīng)知道TCP協(xié)議是沒(méi)有消息邊界的,如果不考慮這個(gè)問(wèn)題,就無(wú)法保證發(fā)送的每條信息和接收的每條信息一一對(duì)應(yīng),例如可能會(huì)出現(xiàn)發(fā)送的幾條信息一塊接收的情況。考慮下面幾條語(yǔ)句:byte[]buffer=newbyte[socket.ReceiveBufferSize];inti=socket.Receive(buffer,buffer.Length,SocketFlags.None);stringreceiveMessage=System.Text.Encoding.UTF8.GetString(buffer,0,i);如果不仔細(xì)考慮,讀者可能會(huì)認(rèn)為這幾條語(yǔ)句和使用StreamReader對(duì)象從網(wǎng)絡(luò)流讀取方式完成的功能相同,其實(shí)這樣使用會(huì)出現(xiàn)問(wèn)題的。由于邊界問(wèn)題和網(wǎng)絡(luò)中傳輸?shù)挠绊懀褂胹ocket.Receive方法接收到的不一定剛好是發(fā)送的一條信息,可能是一部分,也可能是幾條信息都在緩沖區(qū)內(nèi),而這段代碼中只接收了一次,自然無(wú)法保證和發(fā)送的一條信息完全對(duì)應(yīng)。另外,如果在一臺(tái)機(jī)器上調(diào)試,由于沒(méi)有網(wǎng)絡(luò)傳輸?shù)挠绊?,自然也就無(wú)法發(fā)現(xiàn)這段代碼中存在的問(wèn)題。即使在一個(gè)局域網(wǎng)中調(diào)試,網(wǎng)絡(luò)傳輸?shù)挠绊懞苄?,也很難發(fā)現(xiàn)邊界問(wèn)題。而使用StreamReader對(duì)象的ReadLine方法,則可以一直讀取直到遇到回車換行為止,從而保證與發(fā)送的以回車換行結(jié)尾的每條信息對(duì)應(yīng)。接收到一條信息以后,根據(jù)發(fā)送時(shí)在字符串中插入的逗號(hào)分隔符,將其分開,分別得到對(duì)方的IP地址、端口號(hào)、信息類型以及信息。然后調(diào)用SendMyMessage方法分別處理。還有一點(diǎn)要注意,不能在第二個(gè)循環(huán)中試圖用一個(gè)布爾型變量作為是否退出循環(huán)的判斷標(biāo)準(zhǔn),這是因?yàn)槌绦驁?zhí)行到AcceptSocket方法時(shí)會(huì)處于阻塞方式,無(wú)法保證及時(shí)響應(yīng)該布爾值的變化,也就失去了判斷的意義。(6)添加代理及相應(yīng)的方法。delegatevoidShowMessageDelegate1(stringstr);privatevoidShowMyMessage(stringstr){//比較調(diào)用的線程和創(chuàng)建的線程是否同一個(gè)線程//如果不是,結(jié)果為trueif(this.listBoxMessage.InvokeRequired==true){//如果結(jié)果為true,則自動(dòng)通過(guò)代理執(zhí)行else中語(yǔ)句的功能(注意:是else,不是if)//這里只需要傳入?yún)?shù)str即可//但是執(zhí)行的功能會(huì)始終與else中的指定的功能相同,區(qū)別僅是通過(guò)代理完成ShowMessageDelegate1messageDelegate=newShowMessageDelegate1(ShowMyMessage);this.Invoke(messageDelegate,newobject[]{str});}else{//在這里指定如果是同一個(gè)線程需要完成什么功能//如果是不同線程,系統(tǒng)會(huì)自動(dòng)通過(guò)代理實(shí)現(xiàn)這里指定的功能listBoxMessage.Items.Add(str);}}delegatevoidShowMessageDelegate2(stringipString,stringportString,stringmessageTypeString,stringmessageString);privatevoidShowMyMessage(stringipString,stringportString,stringmessageTypeString,stringmessageString){if(this.listBoxMessage.InvokeRequired==true){ShowMessageDelegate2messageDelegate=newShowMessageDelegate2(ShowMyMessage);this.Invoke(messageDelegate,newobject[]{ipString,portString,messageTypeString,messageString});}else{//messageType的含義為://<connect>前綴:第次連接//<check>前綴:檢查接收者是否在線//<message>前綴:聊天信息intmyfriendIndex=CheckMyFriend(ipString);switch(messageTypeString){case"connect":if(myfriendIndex==-1){ListViewItemmyFriendItem=newListViewItem(newstring[]{ipString,portString,"是"});listViewMyFriend.Items.Add(myFriendItem);}listBoxMessage.Items.Add(string.Format("[{0}:{1}]說(shuō):{2}",ipString,portString,messageString));break;case"check":if(myfriendIndex==-1){ListViewItemmyFriendItem=newListViewItem(newstring[]{ipString,portString,"是"});listViewMyFriend.Items.Add(myFriendItem);}//不需要顯示break;case"message":listBoxMessage.Items.Add(string.Format("[{0}:{1}]說(shuō):{2}",ipString,portString,messageString));break;default:listBoxMessage.Items.Add(string.Format("什么意思呀:“{0}”",messageString));break;}}}delegatevoidShowIpAndPortDelegate();privatevoidShowLocalIpAndPort(){if(this.listBoxMessage.InvokeRequired){ShowIpAndPortDelegatemessageDelegate=newShowIpAndPortDelegate(ShowLocalIpAndPort);this.Invoke(messageDelegate);}else{textBoxLocalIp.Text=myIPAddress.ToString();textBoxLocalPort.Text=myPort.ToString();}}使用代理的目的是為了解決一個(gè)線程無(wú)法在托管模式下直接調(diào)用另一個(gè)線程的控件問(wèn)題。當(dāng)然,如果通過(guò)非托管模式,也可以直接調(diào)用,但可能會(huì)引起死鎖等問(wèn)題。(7)添加代碼,檢查好友是否在好友列表中。privateintCheckMyFriend(stringremoteIpString){//在myFriendListView中檢查指定的ip是否存在ListViewItemitem=listViewMyFriend.FindItemWithText(remoteIpString);if(item==null){return-1;}else{returnitem.Index;}}代碼中的FindWithText方法檢查L(zhǎng)istView中的每一項(xiàng)中是否包含指定的字符串,如果是,則返回該項(xiàng),否則返回null。由于好友列表中的其他列不可能有和指定的IP地址相同的內(nèi)容,所以這里也就相當(dāng)于檢查第一列是否有指定的IP。(8)添加SendMessage方法。privatevoidSendMessage(stringremoteIpString,stringremotePortString,stringstrType,stringstr){IPAddressremoteIP=IPAddress.Parse(remoteIpString);intremotePort=int.Parse(remotePortString);NetworkStreamnetworkstream=null;TcpClienttcpclient=null;try{tcpclient=newTcpClient(remoteIpString,remotePort);//得到一個(gè)用于發(fā)送和接收數(shù)據(jù)的網(wǎng)絡(luò)流networkstream=tcpclient.GetStream();//使用默認(rèn)編碼和緩沖區(qū)大小初始化StreamWriter對(duì)象StreamWriterstreamWriter=newStreamWriter(networkstream);//使用回車換行作為每次發(fā)送的分隔符streamWriter.WriteLine(string.Format("{0},{1},{2},{3}",myIPAddress,myPort,strType,str));//將緩沖區(qū)內(nèi)容全部發(fā)送出去streamWriter.Flush();if(strType!="check"){listBoxMessage.Items.Add(string.Format("向[{0}:{1}]發(fā)送成功,信息:{2}",remoteIpString,remotePortString,str));}}catch(Exceptionerr){inti=CheckMyFriend(remoteIP.ToString());if(i!=-1){listViewMyFriend.Items[i].SubItems[2].Text="否";}if(strType!="check"){listBoxMessage.Items.Add(string.Format("向[{0}:{1}]發(fā)送信息失敗,原因:{2}",remoteIpString,remotePortString,err.Message));}}finally{if(networkstream!=null){networkstream.Close();}if(tcpclient!=null){tcpclient.Close();}}}這部分代碼完成發(fā)送字符串功能,代碼中首先根據(jù)目標(biāo)IP和端口號(hào)創(chuàng)建一個(gè)TcpClient對(duì)象,然后利用該對(duì)象的GetStream方法創(chuàng)建網(wǎng)絡(luò)流。使用StreamWriter對(duì)象發(fā)送的每條信息均以回車換行結(jié)束,便于接收方區(qū)分是否到了一條信息的結(jié)尾。如果發(fā)送的信息類型為check,說(shuō)明僅僅是為了檢查對(duì)方是否在線,如果對(duì)方在線,發(fā)送中就不會(huì)出現(xiàn)異常,否則會(huì)出現(xiàn)異常,表明對(duì)方已經(jīng)斷開連接。對(duì)于發(fā)送信息為文件的情況,解決邊界問(wèn)題的最好辦法是先發(fā)送該文件的容量,即該文件占用的字節(jié)數(shù),然后再發(fā)送文件內(nèi)容。這樣,接收方就可以根據(jù)文件大小確定什么時(shí)候接收完畢。(9)雙擊【添加】按鈕,添加Click事件代碼。privatevoidbuttonAddFriend_Click(objectsender,EventArgse){IPAddressmyFriendIpAddress;if(IPAddress.TryParse(textBoxMyFriendIpAddress.Text,outmyFriendIpAddress)==false){MessageBox.Show("IP地址格式不正確!");return;}intmyFriendPort;if(int.TryParse(textBoxMyFriendPort.Text,outmyFriendPort)==false){MessageBox.Show("端口號(hào)格式不正確!");return;}else{if(myFriendPort<1000||myFriendPort>65535){MessageBox.Show("端口號(hào)范圍不正確!,必須在-65535之間");return;}}inti=CheckMyFriend(textBoxMyFriendIpAddress.Text);if(i!=-1){MessageBox.Show("該好友已經(jīng)在列表中");}else{ListViewItemfriendItem=newListViewItem(newstring[]{textBoxMyFriendIpAddress.Text,textBoxMyFriendPort.Text,"是"});listViewMyFriend.Items.Add(friendItem);//向?qū)Ψ桨l(fā)送連接信息SendMessage(textBoxMyFriendIpAddress.Text,textBoxMyFriendPort.Text,"connect","哈哈,我上線了");}}(10)雙擊【發(fā)送】按鈕,添加Click事件代碼。privatevoidbuttonSendMessage_Click(objectsender,EventArgse){if(listViewMyFriend.SelectedItems.Count>0){//可以群發(fā)for(inti=0;i<listViewMyFriend.SelectedItems.Count;i++){if(listViewMyFriend.SelectedItems[i].SubItems[2].Text=="是"){stringremoteIpString=listViewMyFriend.SelectedItems[i].SubItems[0].Text;stringremotePortString=listViewMyFriend.SelectedItems[i].SubItems[1].Text;SendMessage(remoteIpString,remotePortString,"message",textBoxSendMessage.Text);}}}else{MessageBox.Show("請(qǐng)先選擇發(fā)送的好友","提示");}}這段代碼中使用for循環(huán)依次向?qū)Ψ桨l(fā)送信息,相當(dāng)于實(shí)現(xiàn)了群發(fā)功能。(11)雙擊【啟動(dòng)刷新】按鈕,添加Click事件代碼。privatevoidbuttonStartTimer_Click(objectsender,EventArgse){secondWatch.Reset();inti;if(int.TryParse(textBoxTimeInterval.Text,outi)==true){if(i<=0){MessageBox.Show("刷新間隔必須是正整數(shù)","范圍不正確");}else{//secondTimer.Interval=i*1000;timerSecond.Enabled=true;secondWatch.Start();//timer1.Start();//timerStatusTextBox.Text="已經(jīng)啟動(dòng)定時(shí)刷新";buttonStartTimer.Enabled=false;buttonStopTimer.Enabled=true;}}else{MessageBox.Show("刷新間隔必須是整數(shù)","格式不正確");}}(12)雙擊【停止刷新】按鈕,添加Click事件代碼。privatevoidbuttonStopTimer_Click(objectsender,EventArgse){timerSecond.Enabled=false;secondWatch.Stop();textBoxTi

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論