image

 

image

伺服端

   1: using System.Net;           //匯入網路通訊協定相關函數
   2: using System.Net.Sockets;   //匯入網路插座功能函數
   3: using System.Threading;     //匯入多執行緒功能函數
   4: using System.Collections;   //匯入集合物件功能

image

   1: //公用變數宣告
   2: TcpListener Server;                //伺服端網路監聽器(相當於電話總機)
   3: Socket Client;                     //給客戶用的連線物件(相當於電話分機)
   4: Thread Th_Svr;                     //伺服器監聽用執行緒(電話總機開放中)
   5: Thread Th_Clt;                     //客戶用的通話執行緒(電話分機連線中)
   6: Hashtable HT = new Hashtable();    //客戶名稱與通訊物件的集合(雜湊表)(key:Name, Socket)

等待客戶端連線

   1: #region 接受客戶連線要求的程式
   2: private void ServerSub()
   3: {
   4:     //Server IP 和 Port
   5:     IPEndPoint EP = new IPEndPoint(IPAddress.Parse(TextBox1.Text), int.Parse(TextBox2.Text));
   6:     Server = new TcpListener(EP);
   7:     Server.Start(100);
   8:     while (true)
   9:     {
  10:         Client = Server.AcceptSocket();
  11:         Th_Clt = new Thread(Listen); //建立監聽這個客戶連線的獨立執行緒
  12:         Th_Clt.IsBackground = true; //設定為背景執行緒
  13:         Th_Clt.Start(); //開始執行緒的運作
  14:     }
  15: }
  16: #endregion

開始執行後會執行到 AcceptSocket()這一行,

等待客戶連線後才會繼續往下執行: 開啟一條客戶端執行緒 Th_Clt = new Thread(Listen)

並設定成背景執行, 方便Server後續隨時關閉連線

接著, 來看監聽客戶端訊息的程式 Listen()

   1: #region 監聽客戶訊息的程式
   2:         private void Listen()
   3:         {
   4:             Socket sck = Client;//複製Client通訊物件到個別客戶專用物件Sck
   5:             Thread Th = Th_Clt;//複製執行緒Th_Clt到區域變數Th
   6:             while (true) //持續監聽客戶傳來的訊息
   7:             {
   8:                 try //用 Sck 來接收此客戶訊息,inLen 是接收訊息的 Byte 數目
   9:                 {
  10:                     byte[] B = new byte[1023];    //建立接收資料用的陣列,長度須大於可能的訊息
  11:                     int inLen = sck.Receive(B); //接收網路資訊(Byte陣列)
  12:                     string Msg = Encoding.Default.GetString(B, 0, inLen); //翻譯實際訊息(長度inLen)
  13:                     string Cmd = Msg.Substring(0, 1); //取出命令碼 (第一個字)
  14:                     string Str = Msg.Substring(1);    //取出命令碼之後的訊息
  15:                     switch (Cmd)//依據命令碼執行功能
  16:                     {
  17:                         case "0"://有新使用者上線:新增使用者到名單中
  18:                             HT.Add(Str, sck); //連線加入雜湊表,Key:使用者,Value:連線物件(Socket)
  19:                             Listbox1.Items.Add(Str); //加入上線者名單
  20:                             break;
  21:                         case "9":
  22:                             HT.Remove(Str); //移除使用者名稱為Name的連線物件
  23:                             Listbox1.Items.Remove(Str); //自上線者名單移除Name
  24:                             Th.Abort(); //結束此客戶的監聽執行緒
  25:                             break;
  26:                     }
  27:                 }
  28:                 catch (Exception)
  29:                 {
  30:                     //有錯誤時忽略,通常是客戶端無預警強制關閉程式,測試階段常發生
  31:                 }
  32:             }
  33:         }
  34:         
  35:         #endregion
  36:         

每位客戶連線後個別執行新的獨立執行緒Th_Clt=new Thread(Listen),

在Listen()副程式中

(1) 讓client socket掛上server端socket, Socket sck = Client = Server.AcceptSocket()

(2) 複製執行緒Th_Clt到區域變數Th, Th= Th_Clt = new Thread(Listen);

然後while(true){ ….}就會不斷監控該客戶傳來的訊息

byte[] B = new byte[1023];                            //建立接收資料用的陣列,長度須大於可能的訊息
int inLen = sck.Receive(B);                           //接收網路資訊(Byte陣列)
--------------------------------------------------------------------------------------------------------------------------------------------

編碼 ByteToString                   

string Msg = Encoding.Default.GetString(B, 0, inLen); //翻譯實際訊息(長度inLen)

(1) 取出命令碼 (第一個字)

string Cmd = Msg.Substring(0, 1);                    

(2) 取出命令碼之後的訊息(user name)

string Str = Msg.Substring(1);                          


----------------------------------------------------------------------------------------------------------------------------------------------

依據命令碼Cmd執行功能

switch (Cmd)//依據命令碼執行功能

{
           case "0"://有新使用者上線:新增使用者到名單中
                  HT.Add(Str, sck); //連線加入雜湊表,Key:使用者,Value:連線物件(Socket)
                  Listbox1.Items.Add(Str); //加入上線者名單
                  break;
           case "9":
                  HT.Remove(Str);             //移除使用者名稱為Name的連線物件
                   Listbox1.Items.Remove(Str); //自上線者名單移除Name
                  Th.Abort();                 //結束此客戶的監聽執行緒
           break;
}

-----------------------------------------------------------------------------------------------------------------------------------------------

Client 端應用程式相對簡單些

image

image

   1: using System.Net;           //匯入網路通訊協定相關函數
   2: using System.Net.Sockets;   //匯入網路插座功能函數

宣告公用變數

   1: //公用變數
   2:     Socket T;           //通訊物件
   3:     string User;        //使用者

(1) 利用IPEndPoint開啟伺服器端點

IPEndPoint EP = new IPEndPoint(IPAddress.Parse(IP), Port);

(2) 建立可以雙向通訊的TCP連線
T = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);        

(3) 連上伺服器的端點EP(類似撥號給電話總機)

T.Connect(EP);

   1: string IP = TextBox1.Text;                                  //伺服器IP
   2: int Port = int.Parse(TextBox2.Text);                        //伺服器Port
   3: IPEndPoint EP = new IPEndPoint(IPAddress.Parse(IP), Port);  //伺服器的連線端點資訊
   4: //建立可以雙向通訊的TCP連線
   5: T = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
   6: User = TextBox3.Text;                                       //使用者名稱
   7: timer1.Enabled = true;
   8:             
   9:  try
  10:  {
  11:         T.Connect(EP);                  //連上伺服器的端點EP(類似撥號給電話總機)
  12:         Send("0" + User);               //連線後隨即傳送自己的名稱給伺服器
  13:  }
  14:  catch (Exception)
  15:  {
  16:        MessageBox.Show("無法連上伺服器!"); //連線失敗時顯示訊息
  17:        return;
  18:  }
  19:  
  20:             Button1.Enabled = false; //讓連線按鍵失效,避免重複連線
  21:             Button3.Enabled = true;

(1) Send()副程式傳送字串至伺服器

(2) Str2Byte: Encoding.Default.GetBytes()

   1: private void Send(string Str)
   2: {
   3:      byte[] B = Encoding.Default.GetBytes(Str);     //翻譯字串Str為Byte陣列B
   4:      int ret = T.Send(B, 0, B.Length, SocketFlags.None);      //使用連線物件傳送資料
   5:      //MessageBox.Show(ret.ToString());
   6: }
   7:    

 

加入一個timer1和button2 用來顯示連線狀態

   1: private void timer1_Tick(object sender, EventArgs e)
   2: {
   3:     try
   4:     {
   5:         string IP = TextBox1.Text;                                  //伺服器IP
   6:         int Port = int.Parse(TextBox2.Text);                        //伺服器Port
   7:         IPEndPoint EP = new IPEndPoint(IPAddress.Parse(IP), Port);  //伺服器的連線端點資訊
   8:         //建立可以雙向通訊的TCP連線
   9:         T = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  10:         T.Connect(EP);
  11:         if(Button2.BackColor != Color.Green)
  12:         Button2.BackColor = Color.Green;
  13:     }
  14:     catch
  15:     {
  16:         if (Button2.BackColor != Color.Red)
  17:         Button2.BackColor = Color.Red;
  18:     }
  19: }

連線成功:顯示綠燈

image

連線失敗或是伺服器關閉:顯示紅燈

image

 

-------------------------------------------------------------------------------------------------------------------------------------------------------

小結:

(1)關於EndPoint

伺服端:

server = new TcpListener(EP);

客戶端:

Socket T = new Socket(…);

T.Connect(EP);

(2)關於編碼

伺服端:    伺服端接收到Byte資料, 轉回String判讀

Encoding.Default.GetString(…)

客戶端:    傳送字串到伺服端, 須以Byte單位傳送

Encoding.Default.GetByte(…)

-------------------------------------------------------------------------------------------------------------------------------------------------------

範例程式

ServerAP: download

ClientAP: download

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 me1237guy 的頭像
    me1237guy

    天天向上

    me1237guy 發表在 痞客邦 留言(1) 人氣()