65.9K
CodeProject 正在变化。 阅读更多。
Home

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

2005年6月6日

CPOL

6分钟阅读

viewsIcon

148592

downloadIcon

9188

用于本地网络、终端服务器/终端客户端环境的聊天服务器/客户端解决方案

引言

在讨论代码之前,有几点我想指出。第一,这是我的第一篇文章,我有点兴奋。第二,我以业余方式进行个人软件开发。它们可能不是最佳实践。但我的目的是反映我的想法,解决我的问题。第三,我过去一年一直在关注 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 包含 ServerAppServerClass 所需的接口 (IServerApp, IMyService)。ClientApp 是一个 Windows 窗体,它实现了 IClientAppClientClass 包含 ClientAppClientClass 所需的接口 (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

  • 修复了构建错误 - 解决方案现在应该可以构建了。
  • 向选定用户发送消息(不是所有用户,仅选定的用户)
© . All rights reserved.