[C#.NET] 簡易TcpListener/TcpClient使用方式

  • 118766
  • 0
  • 2010-10-24

[C#.NET] 簡易TcpListener/TcpClient使用方式

把最近學到的技巧再拿來分享一下,希望有新手遇到類似的問題也能夠得到幫助

這次的目標是利用socket的方式建立兩台機器的TCP/IP連線,然後再利用這個連線做檔案傳輸的動作。首先先了解一下待會所會用到的物件,第一個要介紹的就是IPEndPoint,我們可以把IPEndPoint想成是IP+Port所組成的一個物件。第二個就是主角socket了,socket是在1983年從柏克萊大學所釋出的函式庫,socket把檔案視為串流的方式讓其可在網路架構上流通。這次選擇使用TcpListener/TcpClient的方式,因為該物件已經把socket包起來,所以使用上相對簡易。首先我們先來看看海角點部落裡提供的TCP socket連線流程圖,再來看看這次我們想要達成的目標:

 

接下來看看這次方案裡的三個專案:

  1. ConsoleServer - 主機
  2. ConsoleClient - 客戶端
  3. CommunicationBase - 主機和用戶端都需要用到傳送及接收的功能,所以把它抽出來讓兩邊都可以加入參考使用

先看看主機端長什麼樣:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;


namespace ConsoleServer
{
    /// <summary>
    /// Server class
    /// </summary>

    public class Server
    {
        /// <summary>
        /// 等待客戶端連線
        /// </summary>

        public void ListenToConnection()
        {
            //取得本機名稱
            string hostName = Dns.GetHostName();
            Console.WriteLine("本機名稱=" + hostName);

            //取得本機IP
            IPAddress[] ipa = Dns.GetHostAddresses(hostName);
            Console.WriteLine("本機IP=" + ipa[0].ToString());

            //建立本機端的IPEndPoint物件
            IPEndPoint ipe = new IPEndPoint(ipa[0],1234);

            //建立TcpListener物件
            TcpListener tcpListener = new TcpListener(ipe);

            //開始監聽port
            tcpListener.Start();
            Console.WriteLine("等待客戶端連線中... \n" );

            TcpClient tmpTcpClient;
            int numberOfClients = 0;
            while (true)
            {
                try
                {
                    //建立與客戶端的連線
                    tmpTcpClient = tcpListener.AcceptTcpClient();

                    if (tmpTcpClient.Connected)
                    {
                        Console.WriteLine("連線成功!");
                        HandleClient handleClient = new HandleClient(tmpTcpClient);
                        Thread myThread = new Thread(new ThreadStart(handleClient.Communicate));
                        numberOfClients += 1;
                        myThread.IsBackground = true;
                        myThread.Start();
                        myThread.Name = tmpTcpClient.Client.RemoteEndPoint.ToString();
                    }

                }

                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.Read();
                }

            }
// end while
        }
// end ListenToConnect()
    }
// end class
}
// end namespace

 這邊有幾個要注意的重點:

  1. 建立IPEndPoint端點需要IPAddress跟Port
  2. TcpListener是主機專用 - 專門用來聽port是否有從客戶端來的連線request
  3. 在while裡有使用到thread,因為我們假設主機host起來之後可能會有一個以上的client連線進來,thread的使用方式可以參考msdn

接著看看主機端裡的HandleClient長什麼樣:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using SocketDemo;


namespace ConsoleServer
{
    public class HandleClient
    {
        /// <summary>
        /// private attribute of HandleClient class
        /// </summary>

        private TcpClient mTcpClient;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="_tmpTcpClient">傳入TcpClient參數</param>

        public HandleClient(TcpClient _tmpTcpClient)
        {
            this.mTcpClient = _tmpTcpClient;
        }


        /// <summary>
        /// Communicate
        /// </summary>

        public void Communicate()
        {
            try
            {
                CommunicationBase cb = new CommunicationBase();
                string msg = cb.ReceiveMsg(this.mTcpClient);
                Console.WriteLine(msg+"\n");
                cb.SendMsg("主機回傳測試", this.mTcpClient);
            }

            catch
            {
                Console.WriteLine("客戶端強制關閉連線!");
                this.mTcpClient.Close();
                Console.Read();
            }

        }
// end HandleClient()
    }
// end Class
}
// end namespace

HandleClient裡面只有一個Communicate()的方法,是用來傳送及接收訊息,而在稍早提到因為傳送及接收的功能主機及客戶端都會使用到,所以在這裡需先new一個CommunicationBase的物件再使用其傳送及接收的方法:

  1. cb.ReceiveMsg()
  2. cb.SendMsg()

 再來看看CommunicationBase長什麼樣:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;


namespace SocketDemo
{
    /// <summary>
    /// CommunicationBase給客戶端和主機端共用,可傳送接收訊息
    /// </summary>

    public class CommunicationBase
    {
        /// <summary>
        /// 傳送訊息
        /// </summary>
        /// <param name="msg">要傳送的訊息</param>
        /// <param name="tmpTcpClient">TcpClient</param>

        public void SendMsg(string msg, TcpClient tmpTcpClient)
        {
            NetworkStream ns = tmpTcpClient.GetStream();
            if (ns.CanWrite)
            {
                byte[] msgByte = Encoding.Default.GetBytes(msg);
                ns.Write(msgByte, 0, msgByte.Length);
            }

        }


        /// <summary>
        /// 接收訊息
        /// </summary>
        /// <param name="tmpTcpClient">TcpClient</param>
        /// <returns>接收到的訊息</returns>

        public string ReceiveMsg(TcpClient tmpTcpClient)
        {
            string receiveMsg = string.Empty;
            byte[] receiveBytes = new byte[tmpTcpClient.ReceiveBufferSize];
            int numberOfBytesRead = 0;
            NetworkStream ns = tmpTcpClient.GetStream();

            if (ns.CanRead)
            {
                do
                {
                    numberOfBytesRead = ns.Read(receiveBytes, 0, tmpTcpClient.ReceiveBufferSize);
                    receiveMsg = Encoding.Default.GetString(receiveBytes, 0, numberOfBytesRead);
                }

                while (ns.DataAvailable);
            }

            return receiveMsg;
        }

    }

}

 這邊有幾個要注意的重點:

  1. 不管傳送或接收都需要用 NetworkStream ns = tmpTcpClient.GetStream() 的方法來驅動
  2. 訊息需先轉成byte[]陣列才可傳送 - Encoding.Default.GetBytes()
  3. 傳送 - ns.Write();
  4. 相對地收到訊息時需先從byte[]陣列轉回一般字串 - Encoding.Default.GetString()
  5. 接收 - ns.Read()

 最後來看看Client長什麼樣:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using SocketDemo;


namespace ConsoleClient
{
    /// <summary>
    /// Client class
    /// </summary>

    public class Client
    {
        /// <summary>
        /// 連線至主機
        /// </summary>

        public void ConnectToServer()
        {
            //預設主機IP
            string hostIP = "192.168.0.216";

            //先建立IPAddress物件,IP為欲連線主機之IP
            IPAddress ipa = IPAddress.Parse(hostIP);

            //建立IPEndPoint
            IPEndPoint ipe = new IPEndPoint(ipa, 1234);

            //先建立一個TcpClient;
            TcpClient tcpClient = new TcpClient();
            
            //開始連線
            try
            {
                Console.WriteLine("主機IP=" + ipa.ToString());
                Console.WriteLine("連線至主機中...\n");
                tcpClient.Connect(ipe);
                if (tcpClient.Connected)
                {
                    Console.WriteLine("連線成功!");
                    CommunicationBase cb = new CommunicationBase();
                    cb.SendMsg("這是客戶端傳給主機的訊息", tcpClient);
                    Console.WriteLine(cb.ReceiveMsg(tcpClient));
                }

                else
                {
                    Console.WriteLine("連線失敗!");
                }

                Console.Read();
            }

            catch (Exception ex)
            {
                tcpClient.Close();
                Console.WriteLine(ex.Message);
                Console.Read();
            }

        }

    }

}

程式裡的註解應該都很清楚,希望能讓有需求的新手可以縮短research的時間

結果跑出來會像是這樣:

 

 謝謝收看