[C#.NET] 簡易TcpListener/TcpClient使用方式
把最近學到的技巧再拿來分享一下,希望有新手遇到類似的問題也能夠得到幫助。
這次的目標是利用socket的方式建立兩台機器的TCP/IP連線,然後再利用這個連線做檔案傳輸的動作。首先先了解一下待會所會用到的物件,第一個要介紹的就是IPEndPoint,我們可以把IPEndPoint想成是IP+Port所組成的一個物件。第二個就是主角socket了,socket是在1983年從柏克萊大學所釋出的函式庫,socket把檔案視為串流的方式讓其可在網路架構上流通。這次選擇使用TcpListener/TcpClient的方式,因為該物件已經把socket包起來,所以使用上相對簡易。首先我們先來看看海角點部落裡提供的TCP socket連線流程圖,再來看看這次我們想要達成的目標:
接下來看看這次方案裡的三個專案:
- ConsoleServer - 主機
- ConsoleClient - 客戶端
- 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
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
這邊有幾個要注意的重點:
- 建立IPEndPoint端點需要IPAddress跟Port
- TcpListener是主機專用 - 專門用來聽port是否有從客戶端來的連線request
- 在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
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的物件再使用其傳送及接收的方法:
- cb.ReceiveMsg()
- 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;
}
}
}
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;
}
}
}
這邊有幾個要注意的重點:
- 不管傳送或接收都需要用 NetworkStream ns = tmpTcpClient.GetStream() 的方法來驅動
- 訊息需先轉成byte[]陣列才可傳送 - Encoding.Default.GetBytes()
- 傳送 - ns.Write();
- 相對地收到訊息時需先從byte[]陣列轉回一般字串 - Encoding.Default.GetString()
- 接收 - 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();
}
}
}
}
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的時間。
結果跑出來會像是這樣:
謝謝收看