本地网络的聊天服务器/客户端解决方案






4.80/5 (30投票s)
用于本地网络、终端服务器/终端客户端环境的聊天服务器/客户端解决方案
引言
在讨论代码之前,有几点我想指出。第一,这是我的第一篇文章,我有点兴奋。第二,我以业余方式进行个人软件开发。它们可能不是最佳实践。但我的目的是反映我的想法,解决我的问题。第三,我过去一年一直在关注 CodeProject,在这里学到了很多东西,因此我无法区分我从谁那里学到了什么。所以,如果你认为这里提出的一些想法是你的,请联系我,以便我能引用你。
现在,我们简要谈谈这个项目。这主要是一个为网络环境设计的服务器-客户端聊天应用程序。我对其进行了修改,使其可以在终端服务器/终端客户端环境中运行。我的意思是用户通过远程桌面连接客户端(微软远程桌面连接、hoblink 等)登录终端服务器。我选择将服务器做成控制台应用程序,以便于跟踪用户(谁登录或注销了...)。但服务器端可以实现更好的解决方案。
背景
CodeProject 和互联网上有很多聊天解决方案。我尝试过实现它们,但它们或多或少都显得有点令人困惑。最后,经过大量研究,我设法找到了一个解决方案。该解决方案不仅包含服务器端和客户端应用程序,还包含一个用于在服务器端保存用户信息的链表解决方案。事实上,链表解决方案本身就可以成为另一篇文章的主题,但我没有那么多时间,所以我将在这里简要解释。我已在代码中提供了大量注释以指导用户。
Using the Code
该解决方案包含五个项目
ServerApp
ServerClass
ClientApp
ClientClass
LinkedList
我进行这种分离是为了重用。
ServerApp
是一个控制台应用程序。
namespace ServerAppSpace //Namespace for the Server Application
{
//IServerApp is the interface for the server application to implement...
//it will provide necessary methods to interact with the server
class MsgServer : MarshalByRefObject, IServerApp
{
#region IServerApp implementation
...
#endregion
[STAThread]
public static void Main(string[] args)
{
TcpChannel channel = new TcpChannel(9001);
ChannelServices.RegisterChannel(channel);
ServerClass remService = new ServerClass();
ObjRef obj = RemotingServices.Marshal(remService,"TcpService");
// Create applications MainForm
MsgServer frmMain = new MsgServer();
// provide marshaled object with reference to Application
remService.theMainServer = ( IServerApp) frmMain;
...
RemotingServices.Unmarshal(obj);
RemotingServices.Disconnect(remService);
}
}
ServerClass
包含 ServerApp
和 ServerClass
所需的接口 (IServerApp
, IMyService
)。ClientApp
是一个 Windows 窗体,它实现了 IClientApp
。ClientClass
包含 ClientApp
和 ClientClass
所需的接口 (IClientApp
, IClientClass
)。LinkedList
是一个由名为 LinkedList
的类组成的项目。我在其编码中提供了必要的注释。请现在参考它。
在服务器文本框中,输入运行服务器的计算机名称。如果客户端和服务器在同一台计算机上运行,您可以输入“localhost
”。
在用户名文本框中,输入您要使用的用户名。服务器会检查用户名。如果用户名已被使用,它会要求您更改名称。
点击登录。如果上述任何文本框为空,它会要求您填写必要的文本框。
您可以通过两种方式发送消息
- 一,点击您希望发送消息的用户名。在
messageEntryBox
中输入消息,按 Enter 键或点击“发送消息”。 - 二,勾选全局复选框。您无需选择用户。消息将发送给所有在线用户。
如果未选中“全局”复选框,则不会从用户列表中选择任何用户。此时如果您尝试发送消息,将弹出一个警告,要求您选择一个用户。
嗯,我没有将用户列表设置为多选。除了“全局发送”之外的多消息功能目前尚未实现。
对话框的标题栏是自定义标题栏。我禁用了 Windows 窗体的原始工具栏。(为此,进入 Windows 窗体的属性,将 ControlBox
属性的值更改为 false
,并删除 Text
属性的值。)我在窗体顶部放置了一个标签和一个按钮作为标题栏。我还更改了它们的锚点设置,以使其大小和位置与主窗口的更改保持同步。我为标签添加了一个事件,以使其像原始标题栏一样移动窗口。(感谢 MinaFawzi 的文章 使用 GDI+ 创建非矩形窗体。)
我还为所有控件设置了锚点,因此当主窗口调整大小时,它们会根据新情况自动调整位置。
如果您点击“X”按钮,客户端将从服务器注销并关闭。如果您点击“注销”,将弹出一个消息框,询问您的选择...是注销但不关闭窗口,还是注销并关闭窗口,或者什么都不做... 这里可以安排一个更好的消息框,但我没有那么多时间。
为了进一步改进,可以在系统托盘中放置一个图标,并禁用任务栏显示。我可能会稍后实现此功能。
关注点
在编码过程的早期阶段,我首先创建了一个服务器,并让客户端定期检查服务器以获取更新。但这在我看来有点愚蠢。然后我寻找了另一种方法。正确的方法应该是“客户端向某人发送消息,服务器获取消息并将其转发给相关用户”;但如何实现呢?所以我决定像服务器端一样设计客户端(实际上是为每个客户端注册一个单独的 TCP 通道,并创建客户端类对象,该对象可以由服务器调用以将消息推送到客户端)。服务器端和客户端端的主要设计是相同的。使用 new
创建一个类对象,然后将其封送。
客户端
ClientClass remService = new ClientClass();
ObjRef obj = RemotingServices.Marshal(remService,"TcpClient");
服务器端
ServerClass remService = new ServerClass();
ObjRef obj = RemotingServices.Marshal(remService,"TcpService");
(嗯,我必须承认 .NET 确实促进了远程应用程序之间的通信。如果我们用 C++ 和 COM 开发这个项目,很多东西都需要手动编码,并且需要更长的时间。)但后来,又需要进行更改。因为程序将在网络终端服务器上运行,客户端将通过终端客户端登录网络服务器。在第一种方法中,所有客户端都具有相同的静态端口号。这对于在不同机器上运行的单个客户端没有问题,但在终端服务器-终端客户端方法中,这个静态端口会导致问题……为了克服这种情况,我找到了一个解决方案:在注册 TCP 通道之前,客户端可以要求服务器发送端口号。获取此端口号后,客户端注册其 TCP 通道,服务器端则保留此信息以便以后找到合适的客户端。服务器将唯一的 TCP 号码发送给每个注册客户端。因此,可以在单个和/或多台机器上启动多个客户端。
我还想指出一个困扰我很久的问题。当我第一次设计客户端时,每次我按下登录按钮登录服务器时,它总是卡住。我一开始找不到解决方案,但后来我发现了 Ingo Rammer 的这篇文章:Thinktecture。它帮了我大忙。
我已在网络环境中测试了此客户端-服务器解决方案,多台机器运行 WinXP Professional SP2(.NET Framework 1.1)。还需要进行更多测试,但目前就这些。
我在 VS 2005 和 Vista SP2 上测试了该解决方案。
修订
版本1.01
- 修复了构建错误 - 解决方案现在应该可以构建了。
- 向选定用户发送消息(不是所有用户,仅选定的用户)