.NET Remoting 是 .NET Framework 早期的分散式物件通訊技術,支援:
MarshalByRefObject,透過 Proxy 存取注意:.NET Remoting 已被 WCF (Windows Communication Foundation) 取代,並在 .NET Core/.NET 5+ 中移除。本文僅供維護舊系統或學習分散式概念參考。
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Windows.Forms; // 需參考 System.Windows.Forms.dll
namespace ChatServer.Remoting
{
///
/// 聊天室主題:管理用戶清單、訊息廣播、線上狀態
///
public class ChatTopic : MarshalByRefObject
{
private readonly ArrayList _users = new ArrayList(); // 用戶名稱
private readonly ArrayList _textBoxes = new ArrayList(); // 客戶端 RichTextBox 參考
private readonly ArrayList _listBoxes = new ArrayList(); // 客戶端 ListBox 參考
///
/// 用戶加入聊天室
///
/// true=成功加入,false=暱稱重複
public bool Join(string userName, ref RichTextBox textBox, ref ListBox listBox)
{
lock (_users)
{
if (_users.Contains(userName))
{
textBox.AppendText($"暱稱「{userName}」已被使用,請更換。\n");
return false;
}
// 註冊用戶
_users.Add(userName);
_textBoxes.Add(textBox);
_listBoxes.Add(listBox);
// 通知現有用戶更新線上清單
foreach (ListBox lb in _listBoxes)
{
lb.Items.Add(userName);
}
// 歡迎訊息僅發給新用戶
textBox.AppendText("已連線至伺服器。\n");
// 廣播加入訊息
Broadcast($"{userName} 加入聊天室");
// 將現有用戶加入新用戶清單
foreach (string existingUser in _users)
{
listBox.Items.Add(existingUser);
}
return true;
}
}
///
/// 用戶離開聊天室
///
public void Leave(string userName)
{
lock (_users)
{
int index = _users.IndexOf(userName);
if (index < 0) return;
_users.RemoveAt(index);
_textBoxes.RemoveAt(index);
_listBoxes.RemoveAt(index);
// 廣播離開訊息
Broadcast($"{userName} 離開聊天室");
// 更新所有用戶線上清單
foreach (ListBox lb in _listBoxes)
{
lb.Items.Remove(userName);
}
}
}
///
/// 廣播訊息給所有連線用戶
///
public void Broadcast(string message)
{
lock (_users)
{
Console.WriteLine($"[廣播] {message}");
foreach (RichTextBox rtb in _textBoxes)
{
try
{
rtb.AppendText(message + "\n");
}
catch
{
// 忽略個別用戶端 UI 存取失敗
}
}
}
}
///
/// 無限期存活 (Singleton 模式必須)
///
public override object InitializeLifetimeService() => null;
}
///
/// 伺服器啟動程式
///
public class ServerProgram
{
public static void Main()
{
const int Port = 1099;
// 1. 建立 HTTP 通道 (SOAP 格式)
var channel = new HttpChannel(Port);
ChannelServices.RegisterChannel(channel, false); // false = 不確保安全性 (簡化測試)
// 2. 註冊 Singleton 遠端物件
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ChatTopic),
"ChatTopic.soap", // URI 尾段
WellKnownObjectMode.Singleton);
Console.WriteLine($"聊天伺服器已啟動,監聽埠 {Port} (HTTP/SOAP)");
Console.WriteLine("物件 URI: https://localhost:1099/ChatTopic.soap");
Console.WriteLine("按 Enter 鍵停止伺服器...");
Console.ReadLine();
}
}
}
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using ChatServer.Remoting; // 參考伺服器組件或共用介面組件
namespace ChatClient
{
public class ChatForm : Form
{
private readonly TextBox _nicknameBox = new TextBox { Left = 80, Top = 360, Width = 150 };
private readonly Button _connectBtn = new Button { Text = "連線", Left = 16, Top = 360, Width = 60 };
private readonly Button _disconnectBtn = new Button { Text = "離開", Left = 344, Top = 336, Width = 96, Enabled = false };
private readonly Button _sendBtn = new Button { Text = "發送", Left = 136, Top = 64, Width = 72 };
private readonly TextBox _messageBox = new TextBox { Left = 72, Top = 32, Width = 160 };
private readonly RichTextBox _chatArea = new RichTextBox
{
Left = 24, Top = 8, Width = 288, Height = 208,
ReadOnly = true, BackColor = SystemColors.Info, Multiline = true
};
private readonly ListBox _userList = new ListBox
{
Left = 320, Top = 0, Width = 152, Height = 329
};
private readonly GroupBox _inputGroup = new GroupBox
{
Text = "發送訊息", Left = 16, Top = 232, Width = 240, Height = 104
};
private HttpChannel _channel;
private ChatTopic _topic;
private string _userName;
private bool _connected;
public ChatForm()
{
Text = "聊天客戶端";
ClientSize = new Size(496, 425);
// 版面配置
_inputGroup.Controls.AddRange(new Control[] { _sendBtn, _messageBox, new Label { Text = "訊息", Left = 16, Top = 32 } });
Controls.AddRange(new Control[]
{
_nicknameBox, _connectBtn, _disconnectBtn, _sendBtn,
_messageBox, _chatArea, _userList, _inputGroup,
new Label { Text = "暱稱", Left = 16, Top = 360 }
});
// 事件繫結
_connectBtn.Click += ConnectBtn_Click;
_disconnectBtn.Click += DisconnectBtn_Click;
_sendBtn.Click += SendBtn_Click;
FormClosing += ChatForm_FormClosing;
}
private void ConnectBtn_Click(object sender, EventArgs e)
{
if (_connected) return;
string nickname = _nicknameBox.Text.Trim();
if (string.IsNullOrEmpty(nickname))
{
MessageBox.Show("請輸入暱稱");
return;
}
try
{
// 1. 建立客戶端回呼通道 (埠 0 = OS 自動指派)
_channel = new HttpChannel(0);
ChannelServices.RegisterChannel(_channel, false);
// 2. 取得遠端物件 Proxy
_topic = (ChatTopic)Activator.GetObject(
typeof(ChatTopic),
"https://localhost:1099/ChatTopic.soap");
// 3. 呼叫遠端 Join 方法 (傳遞 UI 控制項參考)
bool success = _topic.Join(nickname, ref _chatArea, ref _userList);
if (!success)
{
CleanupConnection();
return;
}
// 4. 連線成功,更新 UI 狀態
_connected = true;
_userName = nickname;
_nicknameBox.Enabled = false;
_connectBtn.Enabled = false;
_disconnectBtn.Enabled = true;
_sendBtn.Enabled = true;
_messageBox.Enabled = true;
Text = $"聊天客戶端 - {nickname} (已連線)";
}
catch (Exception ex)
{
MessageBox.Show($"連線失敗: {ex.Message}");
CleanupConnection();
}
}
private void DisconnectBtn_Click(object sender, EventArgs e) => Disconnect();
private void SendBtn_Click(object sender, EventArgs e)
{
if (!_connected || string.IsNullOrWhiteSpace(_messageBox.Text)) return;
try
{
_topic.Broadcast($"{_userName}: {_messageBox.Text}");
_messageBox.Clear();
}
catch (Exception ex)
{
MessageBox.Show($"發送失敗: {ex.Message}");
Disconnect();
}
}
private void ChatForm_FormClosing(object sender, FormClosingEventArgs e) => Disconnect();
private void Disconnect()
{
if (!_connected) return;
try
{
_topic?.Leave(_userName);
}
catch { /* 忽略離開時錯誤 */ }
finally
{
CleanupConnection();
}
}
private void CleanupConnection()
{
_connected = false;
_topic = null;
_userName = null;
_nicknameBox.Enabled = true;
_connectBtn.Enabled = true;
_disconnectBtn.Enabled = false;
_sendBtn.Enabled = false;
_messageBox.Enabled = false;
_nicknameBox.Clear();
_chatArea.Clear();
_userList.Items.Clear();
Text = "聊天客戶端";
try { ChannelServices.UnregisterChannel(_channel); } catch { }
_channel = null;
}
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ChatForm());
}
}
}
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ChatTopic),
"ChatTopic.soap",
WellKnownObjectMode.Singleton); // 關鍵:所有客戶端共用同一實例
// 伺服器端物件必須覆寫 InitializeLifetimeService 回傳 null
public override object InitializeLifetimeService() => null; // 永不租約過期
HttpChannel(0) (埠 0 讓 OS 指派可用埠)RichTextBox.AppendText, ListBox.Items.Add)// 客戶端將 UI 控制項傳址給伺服器
_topic.Join(nickname, ref _chatArea, ref _userList);
// 伺服器端直接操作客戶端 UI (透過 Proxy)
textBox.AppendText("訊息"); // 實際發生跨程序/跨機器呼叫
警告:此做法緊耦合伺服器與客戶端 UI 技術 (WinForms),違反分層架構。生產環境應定義純資料介面 (如 IChatClientCallback),客戶端實作介面並透過事件更新 UI。
lock (_users) 保護共用集合Invoke/BeginInvokeChatCommon (類別庫):定義介面/共用型別 (可選,簡化則直接參考伺服器組件)ChatServer (主控台應用程式):參考 ChatCommon、System.Runtime.Remoting、System.Windows.FormsChatClient (Windows Forms 應用程式):參考 ChatCommon、System.Runtime.RemotingChatServer.exe,確認主控台顯示「聊天伺服器已啟動」ChatClient.exe 實例,輸入不同暱稱連線| 技術 | 優點 | 適用場景 |
|---|---|---|
| SignalR (ASP.NET Core) | WebSocket/長輪詢自動切換、支援 Scale-out、JS/.NET 客戶端 | 即時 Web 應用、聊天室、儀表板 |
| gRPC | HTTP/2、Protobuf 序列化、強型別契約、雙向串流 | 微服務通訊、高效能內部 API |
| WebSocket (System.Net.WebSockets) | 標準雙向通訊、低延遲 | 自訂即時協定 |
| WCF (NetTcpBinding / WSDualHttpBinding) | 企業級功能、安全性、交易、可靠傳訊 | 舊系統遷移、企業整合 |
.NET Remoting 透過 MarshalByRefObject、HTTP 通道、Singleton 模式實現分散式聊天室。核心流程:伺服器註冊遠端物件 → 客戶端建立回呼通道 → 取得 Proxy → 呼叫 Join 註冊 UI → 伺服器廣播訊息回呼客戶端 UI。雖已淘汰,但其 Proxy/Stub、封送、租約、通道等概念為理解現代 gRPC、SignalR、微服務通訊奠定基礎。