與服務端連線建立後,可透過連線發送接收資料。連接埠間以串流形式傳輸資料,幾乎任何物件都可儲存至串流,故可傳輸任何類型資料。對客戶端,寫入串流即傳送資料;讀取串流即接收資料。對服務端,寫入串流即發送資料;讀取串流即接收資料。
任務:客戶端列印字串發送服務端,服務端輸出並轉大寫回發,客戶端接收後再列印。分兩部分:客戶端發送/服務端接收、服務端回發/客戶端接收。
class Server {
static void Main(string[] args) {
const int BufferSize = 8192;
Console.WriteLine("Server is running ... ");
IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
TcpListener listener = new TcpListener(ip, 8500);
listener.Start();
Console.WriteLine("Start Listening ...");
TcpClient remoteClient = listener.AcceptTcpClient();
Console.WriteLine("Client Connected! {0} <-- {1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
NetworkStream streamToClient = remoteClient.GetStream();
byte[] buffer = new byte[BufferSize];
int bytesRead = streamToClient.Read(buffer, 0, BufferSize);
Console.WriteLine("Reading data, {0} bytes ...", bytesRead);
string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: {0}", msg);
}
}
注意:可讀字串總位元組數大於 BufferSize 會截斷。大物件需分次讀取轉存:
byte[] buffer = new byte[BufferSize];
int bytesRead;
MemoryStream msStream = new MemoryStream();
do {
bytesRead = streamToClient.Read(buffer, 0, BufferSize);
msStream.Write(buffer, 0, bytesRead);
} while (bytesRead > 0);
buffer = msStream.GetBuffer();
string msg = Encoding.Unicode.GetString(buffer);
class Client {
static void Main(string[] args) {
Console.WriteLine("Client Running ...");
TcpClient client;
try {
client = new TcpClient();
client.Connect("localhost", 8500);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return;
}
Console.WriteLine("Server Connected! {0} --> {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
string msg = "\"Welcome To TraceFact.Net\"";
NetworkStream streamToServer = client.GetStream();
byte[] buffer = Encoding.Unicode.GetBytes(msg);
streamToServer.Write(buffer, 0, buffer.Length);
Console.WriteLine("Sent: {0}", msg);
}
}
執行結果:服務端阻塞於 AcceptTcpClient,客戶端連線後服務端讀取資料並輸出。
do {
TcpClient remoteClient = listener.AcceptTcpClient();
// ... 讀取一次資料 ...
} while (true);
TcpClient remoteClient = listener.AcceptTcpClient();
NetworkStream streamToClient = remoteClient.GetStream();
do {
// ... 重複讀取資料 ...
} while (true);
客戶端修改為選單模式:按 S 輸入字串發送,按 X 退出。測試可見服務端連續接收多條訊息。
客戶端呼叫 Close() 或 Dispose(),服務端 Read() 持續回傳 0 產生無限迴圈;直接關閉客戶端,服務端阻塞於 Read() 拋出「遠端主機強制關閉連線」異常。讀寫操作必須包在 try/catch 中。
角色對調:服務端寫入串流,客戶端讀取串流。加上 lock 保護串流讀寫。
class Server {
static void Main(string[] args) {
const int BufferSize = 8192;
ConsoleKey key;
Console.WriteLine("Server is running ... ");
IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
TcpListener listener = new TcpListener(ip, 8500);
listener.Start();
Console.WriteLine("Start Listening ...");
TcpClient remoteClient = listener.AcceptTcpClient();
Console.WriteLine("Client Connected! {0} <-- {1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);
NetworkStream streamToClient = remoteClient.GetStream();
do {
byte[] buffer = new byte[BufferSize];
int bytesRead;
try {
lock (streamToClient) {
bytesRead = streamToClient.Read(buffer, 0, BufferSize);
}
if (bytesRead == 0) throw new Exception("讀取到 0 位元組");
Console.WriteLine("Reading data, {0} bytes ...", bytesRead);
string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: {0}", msg);
msg = msg.ToUpper();
buffer = Encoding.Unicode.GetBytes(msg);
lock (streamToClient) {
streamToClient.Write(buffer, 0, buffer.Length);
}
Console.WriteLine("Sent: {0}", msg);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
break;
}
} while (true);
streamToClient.Dispose();
remoteClient.Close();
Console.WriteLine("\n\n輸入 \"Q\" 鍵退出。");
do { key = Console.ReadKey(true).Key; } while (key != ConsoleKey.Q);
}
}
class Client {
static void Main(string[] args) {
Console.WriteLine("Client Running ...");
TcpClient client;
ConsoleKey key;
const int BufferSize = 8192;
try {
client = new TcpClient();
client.Connect("localhost", 8500);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return;
}
Console.WriteLine("Server Connected! {0} --> {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint);
NetworkStream streamToServer = client.GetStream();
Console.WriteLine("Menu: S - Send, X - Exit");
do {
key = Console.ReadKey(true).Key;
if (key == ConsoleKey.S) {
Console.Write("Input the message: ");
string msg = Console.ReadLine();
byte[] buffer = Encoding.Unicode.GetBytes(msg);
try {
lock (streamToServer) {
streamToServer.Write(buffer, 0, buffer.Length);
}
Console.WriteLine("Sent: {0}", msg);
int bytesRead;
buffer = new byte[BufferSize];
lock (streamToServer) {
bytesRead = streamToServer.Read(buffer, 0, BufferSize);
}
msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: {0}", msg);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
break;
}
}
} while (key != ConsoleKey.X);
streamToServer.Dispose();
client.Close();
Console.WriteLine("\n\n輸入 \"Q\" 鍵退出。");
do { key = Console.ReadKey(true).Key; } while (key != ConsoleKey.Q);
}
}
客戶端發送 "Hello, I'm jimmy zhang.",服務端回應 "HELLO, I'M JIMMY ZHANG."。
ASCII 無法儲存中文;UTF-8 變長編碼,ASCII 字元 1 位元組,中文 3 位元組;Unicode 固定 2 位元組,ASCII 字元第 2 位元組補零,中文 2 位元組。