用Visual C#實現(xiàn)P2P應(yīng)用程序

2010-08-28 10:50:44來源:西部e網(wǎng)作者:

一.前言:

  P2P,即英文Peer-to-Peer的縮寫,中譯為對等互聯(lián)或點對點技術(shù)。講到P2P,人們就會想起Napster,Napster讓人們認(rèn)識到了P2P技術(shù)的威力,P2P技術(shù)也就通過Napster進入了大多數(shù)用戶的視野,Napster的音樂文件交換功能是P2P的一個主要應(yīng)用。P2P技術(shù)可以讓用戶可以直接連接到其他用戶的計算機,進行文件共享與交換。同時P2P在深度搜索、分布計算、協(xié)同工作等方面也大有用途。

  簡單地說,P2P就是一種用于不同PC用戶之間,不經(jīng)過中繼設(shè)備直接交換數(shù)據(jù)或服務(wù)的技術(shù),它允許Internet用戶直接使用對方的文件。每個人可以直接連接到其他用戶的計算機,并進行文件的交換,而不需要連接到服務(wù)器上再進行瀏覽與下載。因為消除了中間環(huán)節(jié),P2P技術(shù)使得網(wǎng)絡(luò)上的溝通變得更容易、更直接。P2P改變了Internet現(xiàn)在的以大網(wǎng)站為中心的狀態(tài)、重返"非中心化",并把權(quán)力交還給用戶。從某種意義上講,P2P體現(xiàn)了Internet的本質(zhì)。在網(wǎng)絡(luò)尚未發(fā)展成為現(xiàn)在的Web之前,網(wǎng)民就是利用所謂的"布告板"等渠道彼此直接交換信息和文件。

  目前Internet的存儲模式是"內(nèi)容位于中心",而P2P技術(shù)的運用將使Internet上的內(nèi)容向邊緣移動。這將帶來以下改變:首先,客戶不再需要將文件上傳到服務(wù)器,而只需要使用P2P與其他計算機進行共享;其次,使用P2P技術(shù)的計算機不需要固定的IP地址和永久的Internet連接,這使得占有極大比例的撥號上網(wǎng)用戶也可以享受P2P帶來的變革。

  理解P2P技術(shù)方面的最好方法是仔細(xì)觀察并理解一個實際的P2P應(yīng)用程序。C#作為微軟.Net戰(zhàn)略的重要棋子,對網(wǎng)絡(luò)編程提供了很好的支持和優(yōu)化。本文就通過一個程序,向大家介紹一下C#下的P2P編程的方法和實現(xiàn)機理。本文的這個程序雖然不是很有用,但卻很直觀地給出了P2P(點對點)編程以及套接口編程的一些基本知識和概念。它是建立在TcpListener以及TcpClient這兩個類基礎(chǔ)上的,除外還有相應(yīng)的輸入和輸出控制。實現(xiàn)的原理也比較簡單,但是用到了P2P技術(shù)重返"非中心化"的基本原則。簡言之,用這個程序可以在網(wǎng)絡(luò)中發(fā)送、接受信息,任何一臺計算機既可以作為服務(wù)器端,又可以作為客戶端。程序共用到了四個類:一個Listener類(用來監(jiān)聽新的連接)、一個Sender類(用來發(fā)送信息)、一個Inputhandler類(用來控制輸入)、一個Initialize類(用來完成初始化工作)。下面,我先給大家介紹一下這四個類,最后再給出程序的具體實現(xiàn)方法。

  二.基本類介紹:

  1.Listener類:

  Listener類是用來監(jiān)聽新的連接。當(dāng)它的一個對象被建立并開啟后,該對象就開始不斷監(jiān)聽來自網(wǎng)絡(luò)中的連接請求。一旦有了一個連接請求,該對象就設(shè)法建立連接并取得它的字節(jié)流進而轉(zhuǎn)化成字符串顯示在控制臺中。當(dāng)一個連接結(jié)束后,該對象就繼續(xù)進行監(jiān)聽來自網(wǎng)絡(luò)中的連接請求。

  代碼以及注釋如下:

namespace P2PTest
{
 using System;
 using System.Net.Sockets;
 using System.Threading;

 public class Listener
 {
  private Thread th;
  private TcpListener tcpl;
  public bool listenerRun = true;
  //listenerRun為true,表示可以接受連接請求,false則為結(jié)束程序

  public Listener()//構(gòu)造函數(shù)
  {
   th = new Thread(new ThreadStart(Listen));//新建一個用于監(jiān)聽的線程
   th.Start();//打開新線程
  }

  public void Stop()
  {
   tcpl.Stop();
   th.Abort();//終止線程
  }

  private void Listen()
  {
   try
   {
    tcpl = new TcpListener(5656);//在5656端口新建一個TcpListener對象
    tcpl.Start();
    Console.WriteLine("started listening..");

    while(listenerRun)//開始監(jiān)聽
    {
     Socket s = tcpl.AcceptSocket();
     string remote = s.RemoteEndPoint.ToString();
     Byte[] stream = new Byte[80];
     int i=s.Receive(stream);//接受連接請求的字節(jié)流
     string msg = "<" + remote + ">" + System.Text.Encoding.UTF8.GetString(stream);
     Console.WriteLine(msg);//在控制臺顯示字符串
    }
   }
   catch(System.Security.SecurityException)
   {
    Console.WriteLine("firewall says no no to application - application cries..");
   }
   catch(Exception)
   {
    Console.WriteLine("stoped listening..");
   }
  }
 }


  對Listen()函數(shù)的補充說明:

  這個函數(shù)是Listener類的核心部分。該函數(shù)首先被構(gòu)造函數(shù)調(diào)用。只要布爾值listenerRun為true,我們就可以在端口5656創(chuàng)建并開始一個Tcp監(jiān)聽對象TcpListener進行監(jiān)聽網(wǎng)絡(luò)中的連接請求,而一旦listenerRun被置為false,則表示程序結(jié)束了。在循環(huán)體內(nèi)部,我們先接受一個連接,用s.RemoteEndPoint獲得它的IP地址并獲得其字節(jié)流。根據(jù)獲得的字節(jié)流,我們用UTF8編碼將它轉(zhuǎn)化為字符串。最后,我們就在控制臺中顯示獲得的字符串。

  對于catch語句,第一個塊捕獲一個可能由防火墻引起的例外。因為對于防火墻而言,它可能認(rèn)為這是一個特洛依木馬或是儒蟲病毒什么的,所以就會拒絕通過。解決辦法就是重新配置防火墻。第二個塊用于捕獲一般的例外,比如當(dāng)我們調(diào)用了stop()函數(shù)后,我們銷毀了TcpListener對象,那就自然不可能再進行監(jiān)聽了。

  2.Sender類:

  Sender類就一個函數(shù),所以是相當(dāng)簡單的。

  代碼以及注釋如下:

namespace P2PTest
{
 using System;
 using System.IO;
 using System.Net.Sockets;

 public class Sender
 {
  public void Send(string[] aInput)
  {
   string stream = "";
   //獲得要發(fā)送的信息
   for(int i=2; i<aInput.Length; i++)
   {
    stream += aInput[i] + " ";
   }

  try
  {
   TcpClient tcpc = new TcpClient(aInput[1], 5656);
   //在5656端口新建一個TcpClient對象
   NetworkStream tcpStream = tcpc.GetStream();

   StreamWriter reqStreamW = new StreamWriter(tcpStream);
   reqStreamW.Write(stream);
   reqStreamW.Flush();//發(fā)送信息
   tcpStream.Close();
   tcpc.Close();
  }
  catch(Exception)
  {
   Console.WriteLine("connection refused by target computer");
  }
  }
 }


  對Send()函數(shù)的補充說明:

  Send(string[] aInput)函數(shù)將一個數(shù)組作為參數(shù)。數(shù)組的第一個元素Send(aInput[0])必須包含"send"這個字,否則Sender對象不會被創(chuàng)建(更多內(nèi)容在InputHandler類中);第二個元素包含了目標(biāo)計算機的IP地址;剩下的就是要發(fā)送的內(nèi)容信息了。

  在try塊中,我們根據(jù)遠程計算機的IP地址在端口5656(要確保端口號統(tǒng)一)創(chuàng)建了一個TcpClient對象。然后,我們建立一個NetworkStream和一個StremWriter對象來發(fā)送我們的信息。在catch塊中,我們用它來捕獲一般的例外,比如遠程計算機拒絕連接請求、網(wǎng)絡(luò)不通什么的。

  3.InputHandler類:

  InputHandler類主要用來控制用戶輸入。

  代碼以及注釋如下:

namespace P2PTest
{
 using System;

 public class InputHandler
 {
  public bool appRun = true;//當(dāng)appRun為false時,程序結(jié)束
  public InputHandler()
  {
   Console.WriteLine("type help for a list of commands.");
   Input();
  }

  private static Listener li;//一個靜態(tài)的Listener對象
  private string inparam;
  private string[] aInput;//數(shù)組aInput用于接受用戶輸入的信息

  public void Input()
  {
   while(appRun)
   {
    inparam = Console.ReadLine();
    aInput = inparam.Split(' ');
    //將inparam分割的目的是為了獲得字符串中的第一個字,從而執(zhí)行以下不同的命令
    switch(aInput[0])
    {
     case "send"://如果是"send",則新建一個Sender對象并發(fā)送信息
      Sender se = new Sender();
      se.Send(aInput);
      break;
     case "start"://如果是"start",則新的開始監(jiān)聽
      try
      {
       li.listenerRun = false;
       li.Stop();
      }
      catch(NullReferenceException)
      {
        ;
      }
      finally
      {  
       li = new Listener();
      }
      break;
     case "stop"://如果是"stop",則停止監(jiān)聽
      try
      {
       li.listenerRun = false;
       li.Stop();
      }
      catch(NullReferenceException)
      {
       ;
      }
      break;
     case "exit"://退出程序
      try
      {
       li.listenerRun = false;
       li.Stop();
      }
      catch(NullReferenceException)
      {
       ;
      }
      finally
      {
       appRun = false;
      }
      break;
     case "help"://顯示幫助信息
       Console.WriteLine("Commands:");
       Console.WriteLine("start: starts the listener");
       Console.WriteLine("stop: stops the listener if started");
       Console.WriteLine("send: send <IP> <message> sends a message");
       Console.WriteLine("exit: exits the application");
       Console.WriteLine("help: you already know");
       break;
     default:
      Console.WriteLine("Invalid command");
      break;
    }
   }
  }
 }
}

  對InputHandler類的補充說明:

  該類中有一個靜態(tài)的Listener對象li,一旦計算機運行此程序并執(zhí)行"start"操作,該計算機就可以成為網(wǎng)絡(luò)中的服務(wù)器來監(jiān)聽其他計算機的連接請求。而該類的核心部分是一個switch case語句系列,通過不同的操作,我們可以使計算機扮演不同的角色:"send"操作表明該計算機相對目的計算機而言成了客戶端;而"start"操作就將計算機自身置為服務(wù)器端,這正體現(xiàn)了P2P的既是服務(wù)器端又是客戶端的"非中心化"的原則;同時程序也提供了一些其他的輔助操作。

  4.Initialize類:

  Initialize類進行程序的初始化工作,它新建了一個InputHandler對象,只要該對象的布爾值appRun為true,就一直運行之,直到該值為false,程序退出。

  代碼以及注釋如下:

namespace P2PTest
{
 using System;

 public class Init
 {
  public static void Main()
  {
   InputHandler ih = new InputHandler();//新建一個InputHandler對象
   while(ih.appRun);//直到ih.appRun為false,程序退出
    Console.WriteLine("exiting..");
  }
 }


  到此為止,四個類已經(jīng)介紹完畢,我想大家也早已等不及了吧,下面就簡單給大家介紹一下具體實現(xiàn)程序的方法。


  三.實現(xiàn)方法:

  首先,打開Visual Studio.Net,新建一個名為P2Ptest的控制臺應(yīng)用程序的Visual C#項目,圖示如下:
\

圖1

  其次,將以上四個類分別保存為四個文件:listener.cs,sender.cs,inputHandler.cs,initialize.cs。然后將這四個文件添加到當(dāng)前的工程中,同時把原有的主文件刪除即可(因為在initialize.cs中已經(jīng)有主函數(shù)了)。

  最后,按Ctrl+F5即可執(zhí)行程序了。

  為了進行測試,我們需要打開兩個P2Ptest程序,一個作為服務(wù)器端,另一個作為客戶端。服務(wù)器端的圖示如下(此時已經(jīng)開始監(jiān)聽了):
\
圖2

  客戶端的圖示如下(輸入命令行:send 10.85.7.79 Hello,I'm Pitt.Can you hear me??):

\
圖3

  再看服務(wù)器端的情況,圖示如下:
\
圖4

  從圖示可以看到服務(wù)器端已經(jīng)收到消息了。同時,只要客戶端也開啟了監(jiān)聽功能,服務(wù)器端也就能向客戶端發(fā)送信息了。這樣它們的關(guān)系就不再是服務(wù)器-客戶機的關(guān)系了,而是Peer-to-Peer的關(guān)系了。

  四.總結(jié):

  現(xiàn)在一個很基本的P2P應(yīng)用程序以及完成,通過它,我們可以利用P2P技術(shù)的基本特性實現(xiàn)點對點通信。通過這個程序,我相信大家對C#下的P2P編程應(yīng)該有了大致的了解。對于這個程序,不足的一點是功能比較簡單,只可以發(fā)送、接受信息,而且還是基于控制臺的,讀者可以試著開發(fā)出功能更強大的基于Windows Forms的P2P應(yīng)用程序。

  最后,筆者希望能通過此文喚起大家對P2P技術(shù)的興趣。因為P2P身后所蘊藏著的無比的創(chuàng)造力使人們對未來互聯(lián)網(wǎng)充滿了美好的憧憬,現(xiàn)在世界范圍的P2P應(yīng)用熱潮也是一浪高過一浪。在可以預(yù)見的未來,隨著對P2P研究的進一步深入和關(guān)注P2P的群體逐漸增多,P2P必將進入一個飛速發(fā)展的新時期。

下載本文源代碼:http://p2p.tmn.cn/download/csharpp2pSource.rar
關(guān)鍵詞:C#