# LOSoMan的C# Socket通訊講解(C#為主)
###### tags: `LOSo講解` `C#` `Socket通訊`
## Socket通訊概論
Socket通訊為使用Ethernet傳輸資訊的一種方式。運作的方式就如同電話撥接,透過設定插座的號碼,其他人就可透過這串號碼與其通話。而Socket的號碼就是IP Number加上TCP Port。
Socket的連線端可以分為主機端(Server)及客戶端(Client)。通常Server要先設定並連接上傳輸埠口,才能開始監聽是否有Client請求連接;而Client也一樣要連接上傳輸埠口,才能發送要求連線。而當通訊結束時,則會互相確認要中斷連線。
## C#裡的Socket類別及常用方法
在C#程式裡,提供了[Socket類別](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket?view=net-6.0)讓programmer可以更簡單的設計出可連線的應用程式。
Socket類別的宣告,如:([參閱連結](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.-ctor?view=net-6.0#system-net-sockets-socket-ctor(system-net-sockets-addressfamily-system-net-sockets-sockettype-system-net-sockets-protocoltype)))
```csharp=1
Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
```
常使用的方法有:
1. [Bind(EndPoint)](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.bind?view=net-6.0#system-net-sockets-socket-bind(system-net-endpoint)): 使 Socket 與本機端點建立關聯。"EndPoint"為要與 EndPoint 關聯的本機 Socket。
2. [Listen(Int32)](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.listen?view=net-6.0#system-net-sockets-socket-listen(system-int32))或[Listen()](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.listen?view=net-6.0#system-net-sockets-socket-listen(system-int32)): 將 Socket 置於接聽狀態。
3. [Accept()](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.accept?view=net-6.0#system-net-sockets-socket-accept): 建立新建立連接的新 Socket。
4. [Connect(EndPoint)](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.connect?view=net-6.0#system-net-sockets-socket-connect(system-net-endpoint)): 建立與遠端主機的連線。
5. [Send(Byte[])](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.send?view=net-6.0#system-net-sockets-socket-send(system-byte())): 傳送資料至已連接的 Socket。
6. [Receive(Byte[])](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.receive?view=net-6.0#system-net-sockets-socket-receive(system-byte())): 從已繫結的 Socket 接收資料至接收緩衝區中。
7. [Close()](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket.close?view=net-6.0#system-net-sockets-socket-close): 關閉 Socket 連接並釋放所有相關資源。
## 範例程式
以下提供可執行Socket範例裡的Server及Client底層部分程式:
Server端程式:
```csharp=1
public class AsServer
{
#region --公開事件--
public event EventHandler<EventArgsMsg> ServerGetMsgEvent; //Server獲得回訊的事件
public event EventHandler<EventArgsStateChange> ServerStateChangeEvent; //Server狀態改變的事件
#endregion --公開事件--
#region --欄位--
Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//地址型別,流,協議型別
//192.168.43.76 本機ip:127.0.0.1
Socket clientSocket = null; //宣告一個client的socket類別
IPEndPoint ipendpoint = null; //宣告一個通訊端位置
#endregion --欄位--
#region --屬性(傳出、接收)--
public string WriteInMsg { get; set; } //用於傳出訊息
public string MsgReceive { get; set; } //用於接收訊息
#endregion --屬性(傳出、接收)--
#region --建構式--
public AsServer(string ip, int port) //建構式,須帶入ip位置及通訊埠編號
{
IPAddress ipaddress = IPAddress.Parse(ip);
ipendpoint = new IPEndPoint(ipaddress, port);
}
#endregion --建構式--
#region --公開方法--
public void StartConnect() //開始連線的方法
{
if (ipendpoint != null)
{
try
{
serverSocket.Bind(ipendpoint);//繫結完成
StateChange(EConnect.Connected, string.Format("Server Connected"));
serverSocket.Listen(0);//處理連結佇列個數 為0則為不限制
ClientAccept();
}
catch
{
StateChange(EConnect.Disconnect, string.Format("Server has already been connect"));
}
}
}
public void Stop() //關閉socket的方法
{
if (serverSocket != null)
serverSocket.Close();
if (clientSocket != null)
clientSocket.Close();
StateChange(EConnect.Disconnect, "Disconnected!!");
}
#endregion --公開方法--
#region --私有方法--
private void ClientAccept() //此方法用於監聽Client的連線狀態
{
if (clientSocket != null) //排除錯誤問題
{
Stop();
StateChange(EConnect.Disconnect, string.Format("Client Socket Error"));
return;
}
Task.Factory.StartNew(() =>
{
clientSocket = serverSocket.Accept();//接收一個客戶端連結
StateChange(EConnect.Connected, string.Format("Client Connected"));
Task.Factory.StartNew(() => { MessageSender(); }); //建立新執行緒,以傳出訊息
Task.Factory.StartNew(() => { MessageReceiver(); }); //建立新執行緒,以接收訊息
});
}
private void MessageSender() //傳出訊息的方法
{
while (Configuration.ConnectState.Equals(EConnect.Connected))
{
while (Configuration.ConnectState.Equals(EConnect.Connected) && string.IsNullOrEmpty(WriteInMsg)) //當沒有要傳的資料時,在這打轉
{
Thread.Sleep(1);
}
byte[] date = System.Text.Encoding.UTF8.GetBytes(WriteInMsg);//將string加密,轉換成為bytes陣列
clientSocket.Send(date); //傳輸資料
WriteInMsg = string.Empty;
}
clientSocket.Close(); //關閉socket
}
private void MessageReceiver() //接收訊息的方法
{
while (Configuration.ConnectState.Equals(EConnect.Connected))
{
byte[] dateBuffer = new byte[1024]; //宣告接收資訊的空間
int count = clientSocket.Receive(dateBuffer); //存介面接收到資料
MsgReceive = System.Text.Encoding.UTF8.GetString(dateBuffer, 0, count); //解密為string格式
if (ServerGetMsgEvent != null)
{
EventArgsMsg msgArg = new EventArgsMsg();
msgArg.Message = MsgReceive;
ServerGetMsgEvent.Invoke(this, msgArg); //呼叫上層方法執行UI變更
}
MsgReceive = string.Empty;
}
clientSocket.Close(); //關閉socket
}
private void StateChange(EConnect state, string changeMsg = "") //當狀態改變,可呼叫上層變更UI的方法
{
Configuration.ConnectState = state;
if (ServerStateChangeEvent != null)
{
EventArgsStateChange arg = new EventArgsStateChange();
arg.StateMessage = changeMsg;
ServerStateChangeEvent.Invoke(this, arg);
}
}
#endregion --私有方法--
}
```
Client端程式:
```csharp=1
public class AsClient
{
#region --公開事件--
public event EventHandler<EventArgsMsg> ClientGetMsgEvent; //Client獲得回訊的事件
public event EventHandler<EventArgsStateChange> ClientStateChangeEvent; //Client狀態改變的事件
#endregion --公開事件--
#region --欄位--
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //宣告一個client的socket類別
IPEndPoint ipendpoint = null; //宣告一個通訊端位置
#endregion --欄位--
#region --屬性(傳出、接收)--
public string WriteInMsg { get; set; } //用於傳出訊息
public string MsgReceive { get; set; } //用於接收訊息
#endregion --屬性(傳出、接收)--
#region --建構式--
public AsClient(string ip, int port) //建構式,須帶入ip位置及通訊埠編號
{
IPAddress ipaddress = IPAddress.Parse(ip);
ipendpoint = new IPEndPoint(ipaddress, port);
}
#endregion --建構式--
#region --公開方法--
public void StartConnect() //開始連線的方法
{
if (ipendpoint != null)
{
try //連接成功
{
clientSocket.Connect(ipendpoint);//繫結完成
StateChange(EConnect.Connected, string.Format("Client Connected"));
}
catch //連接失敗
{
StateChange(EConnect.Disconnect, string.Format("Connect Fail, please make sure that Server has been online"));
return;
}
Task.Factory.StartNew(() => { MessageSender(); }); //建立新執行緒,以傳出訊息
Task.Factory.StartNew(() => { MessageReceiver(); }); //建立新執行緒,以接收訊息
}
}
public void Stop() //關閉socket的方法
{
if (clientSocket != null)
clientSocket.Close();
StateChange(EConnect.Disconnect, "Disconnected!!");
}
#endregion --公開方法--
#region --私有方法--
private void MessageSender() //傳出訊息的方法
{
while (Configuration.ConnectState.Equals(EConnect.Connected))
{
while (Configuration.ConnectState.Equals(EConnect.Connected) && string.IsNullOrEmpty(WriteInMsg)) //當沒有要傳的資料時,在這打轉
{
Thread.Sleep(1);
}
byte[] date = System.Text.Encoding.UTF8.GetBytes(WriteInMsg); //將string加密,轉換成為bytes陣列
clientSocket.Send(date); //傳輸資料
WriteInMsg = string.Empty;
}
clientSocket.Close(); //關閉socket
}
private void MessageReceiver() //接收訊息的方法
{
while (Configuration.ConnectState.Equals(EConnect.Connected))
{
byte[] data = new byte[1024]; //宣告接收資訊的空間
int count = clientSocket.Receive(data); //存介面接收到資料
MsgReceive = System.Text.Encoding.UTF8.GetString(data, 0, count); //解密為string格式
if (ClientGetMsgEvent != null)
{
EventArgsMsg msgArg = new EventArgsMsg();
msgArg.Message = MsgReceive;
ClientGetMsgEvent.Invoke(this, msgArg); //呼叫上層方法執行UI變更
}
MsgReceive = string.Empty;
}
clientSocket.Close(); //關閉socket
}
private void StateChange(EConnect state, string changeMsg = "") //當狀態改變,可呼叫上層變更UI的方法
{
Configuration.ConnectState = state;
if (ClientStateChangeEvent != null)
{
EventArgsStateChange arg = new EventArgsStateChange();
arg.StateMessage = changeMsg;
ClientStateChangeEvent.Invoke(this, arg);
}
}
#endregion --私有方法--
}
```
執行結果:

## 參考資料
1. [TCP/IP 協定與 Internet 網路:第八章 TCP Socket 程式介面](http://www.tsnien.idv.tw/Internet_WebBook/chap8/8-1%20Socket%20%E7%B0%A1%E4%BB%8B.html)
2. [Microsoft Document Socket 類別](https://docs.microsoft.com/zh-tw/dotnet/api/system.net.sockets.socket?view=net-6.0)