下方實作範例的功能為:
使用:
1. 較為簡易的處理方式為先確認伺服器和客戶端單次傳送的最大長度,依此長度設定 buffer 大小(廢話)
2. 或是使用下列程式碼在超過緩衝區大小時接續讀取: (為了方便讀者查找位置,前六行註解的部分為上方程式碼已提供之內容)
注意: 使用此方式在純英文內容時可完整讀取,但如果是中文內容則在結尾和開頭的部分可能會出現亂碼,且無法以簡易的方式處理,經筆者研究,最好的傳輸方式為客戶端和伺服器端皆使用 base64 傳送和接收訊息
接收訊息:(一樣保留前六行註解的部分,方便讀者查找位置)
注:伺服器發出回應和客戶端接收的部分也需要更改
希望大家都能輕易的使用 Socket!
參考資料:
TcpListener Class
NetworkStream.Read Method
TcpListener.BeginAcceptTcpClient(AsyncCallback, Object) Method
- 客戶端連接伺服器後傳送「嗨,我是 Ruyut」並接收伺服器端傳送的訊息
- 伺服器等待客戶端連接,接收訊息後回傳 「嗨,我是伺服器」,中斷客戶端連線,重新等待客戶端連接
伺服器端完整程式碼
SocketService.cs
using System.Net;
using System.Net.Sockets;
using System.Text;
public class SocketService
{
private TcpListener? _tcpListener;
public void Start(string ip, int port)
{
_tcpListener = new TcpListener(IPAddress.Parse(ip), port);
_tcpListener.Start();
_tcpListener.BeginAcceptTcpClient(AsyncCallback, _tcpListener);
Console.WriteLine("伺服器已啟動,開始監聽");
}
/// <summary>
/// 非同步接收客戶端連線
/// </summary>
private void AsyncCallback(IAsyncResult asyncResult)
{
if (asyncResult.AsyncState is not TcpListener listener) return;
TcpClient client = listener.EndAcceptTcpClient(asyncResult);
Console.WriteLine("客戶端已連線");
// 開始接收客戶端資料
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
var read = stream.Read(buffer, 0, buffer.Length);
var receive = Encoding.UTF8.GetString(buffer, 0, read);
Console.WriteLine($"接收到客戶端訊息:{receive}");
// 開始傳送資料給客戶端
string message = "嗨,我是伺服器";
byte[] bytes = Encoding.UTF8.GetBytes(message);
stream.Write(bytes, 0, bytes.Length);
Console.WriteLine($"發送給客戶端的訊息: {message}");
// 關閉連線
stream.Close();
client.Close();
// 接收下一個訊息
listener.BeginAcceptTcpClient(AsyncCallback, listener);
}
public void Close()
{
_tcpListener?.Stop();
}
}
使用:
SocketService socketService = new SocketService();
socketService.Start("127.0.0.1", 8000);
// 關閉
// socketService.Stop();
客戶端完整程式碼
using System.Net.Sockets;
using System.Text;
string ip = "127.0.0.1";
int port = 8000;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
socket.Connect(ip, port);
Console.WriteLine($"已連伺服器");
// 發送訊息
var message = "嗨,我是 Ruyut";
byte[] data = Encoding.UTF8.GetBytes(message);
socket.Send(data);
Console.WriteLine($"發送給伺服器的訊息: {message}");
// 接收訊息
byte[] buffer = new byte[1024];
int length = socket.Receive(buffer);
string receive = Encoding.UTF8.GetString(buffer, 0, length);
Console.WriteLine($"接收到伺服器回傳的訊息: {receive}");
接收訊息時超出緩衝區處理方式
在本示範中未提及接收訊息大小超過 byte[1024] 時的處理方式,若傳送超過時發送端會收到下列錯誤訊息:
System.Net.Sockets.SocketException (10054): 遠端主機已強制關閉一個現存的連線。
1. 較為簡易的處理方式為先確認伺服器和客戶端單次傳送的最大長度,依此長度設定 buffer 大小(廢話)
2. 或是使用下列程式碼在超過緩衝區大小時接續讀取: (為了方便讀者查找位置,前六行註解的部分為上方程式碼已提供之內容)
// 開始接收客戶端資料
// NetworkStream stream = client.GetStream();
// byte[] buffer = new byte[1024];
// var read = stream.Read(buffer, 0, buffer.Length);
// var receive = Encoding.UTF8.GetString(buffer, 0, read);
// Console.WriteLine($"接收到客戶端訊息:{receive}");
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
StringBuilder stringBuilder = new StringBuilder();
do
{
var count = stream.Read(buffer, 0, buffer.Length);
stringBuilder.AppendFormat("{0}", Encoding.UTF8.GetString(buffer, 0, count));
}
while(stream.DataAvailable);
var receive = stringBuilder.ToString();
Console.WriteLine($"接收到客戶端資料:{receive}");
注意: 使用此方式在純英文內容時可完整讀取,但如果是中文內容則在結尾和開頭的部分可能會出現亂碼,且無法以簡易的方式處理,經筆者研究,最好的傳輸方式為客戶端和伺服器端皆使用 base64 傳送和接收訊息
使用 base64 傳送和接收訊息
發送訊息:
// 發送訊息
string message = "嗨,我是 Ruyut"; // 這裡請替換為很長很長的文章做測試
string messageBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(message));
byte[] data = Encoding.ASCII.GetBytes(messageBase64);
socket.Send(data);
Console.WriteLine($"發送給伺服器的訊息: {message}");
接收訊息:(一樣保留前六行註解的部分,方便讀者查找位置)
// 開始接收客戶端資料
// NetworkStream stream = client.GetStream();
// byte[] buffer = new byte[1024];
// var read = stream.Read(buffer, 0, buffer.Length);
// var receive = Encoding.UTF8.GetString(buffer, 0, read);
// Console.WriteLine($"接收到客戶端訊息:{receive}");
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
StringBuilder stringBuilder = new StringBuilder();
do
{
var count = stream.Read(buffer, 0, buffer.Length);
stringBuilder.AppendFormat("{0}", Encoding.ASCII.GetString(buffer, 0, count));
} while (stream.DataAvailable);
string receive = stringBuilder.ToString();
byte[] base64 = Convert.FromBase64String(receive);
string result = Encoding.UTF8.GetString(base64);
Console.WriteLine($"接收到客戶端資料:{result}");
注:伺服器發出回應和客戶端接收的部分也需要更改
希望大家都能輕易的使用 Socket!
參考資料:
TcpListener Class
NetworkStream.Read Method
TcpListener.BeginAcceptTcpClient(AsyncCallback, Object) Method
留言
張貼留言
如果有任何問題、建議、想說的話或文章題目推薦,都歡迎留言或來信: a@ruyut.com