分享一个C#编写简单的聊天程序(详细介绍)

引言

这是一篇基于Socket进行网络编程的入门文章,我对于网络编程的学习并不够深入,这篇文章是对于自己知识的一个巩固,同时希望能为初学的朋友提供一点参考。文章大体分为四个部分:程序的分析与设计、C#网络编程基础(篇外篇)、聊天程序的实现模式、程序实现。

程序的分析与设计

1.明确程序功能

如果大家现在已经参加了工作,你的经理或者老板告诉你,“小王,我需要你开发一个聊天程序”。那么接下来该怎么做呢?你是不是在脑子里有个雏形,然后就直接打开VS2005开始设计窗体,编写代码了呢?在开始之前,我们首先需要进行软件的分析与设计。就拿本例来说,如果只有这么一句话“一个聊天程序”,恐怕现在大家对这个“聊天程序”的概念就很模糊,它可以是像QQ那样的非常复杂的一个程序,也可以是很简单的聊天程序;它可能只有在对方在线的时候才可以进行聊天,也可能进行留言;它可能每次将消息只能发往一个人,也可能允许发往多个人。它还可能有一些高级功能,比如向对方传送文件等。所以我们首先需要进行分析,而不是一上手就开始做,而分析的第一步,就是搞清楚程序的功能是什么,它能够做些什么。在这一步,我们的任务是了解程序需要做什么,而不是如何去做。

了解程序需要做什么,我们可以从两方面入手,接下来我们分别讨论。

1.1请求客户提供更详细信息

我们可以做的第一件事就是请求客户提供更加详细的信息。尽管你的经理或老板是你的上司,但在这个例子中,他就是你的客户(当然通常情况下,客户是公司外部委托公司开发软件的人或单位)。当遇到上面这种情况,我们只有少得可怜的一条信息“一个聊天程序”,首先可以做的,就是请求客户提供更加确切的信息。比如,你问经理“对这个程序的功能能不能提供一些更具体的信息?”。他可能会像这样回答:“哦,很简单,可以登录聊天程序,登录的时候能够通知其他在线用户,然后与在线的用户进行对话,如果不想对话了,就注销或者直接关闭,就这些吧。”

有了上面这段话,我们就又可以得出下面几个需求:
1.程序可以进行登录。
2.登录后可以通知其他在线用户。
3.可以与其他用户进行对话。
4.可以注销或者关闭。

1.2对于用户需求进行提问,并进行总结

经常会有这样的情况:可能客户给出的需求仍然不够细致,或者客户自己本身对于需求就很模糊,此时我们需要做的就是针对用户上面给出的信息进行提问。接下来我就看看如何对上面的需求进行提问,我们至少可以向经理提出以下问题:

NOTE:这里我穿插一个我在见到的一个印象比较深刻的例子:客户往往向你表达了强烈的意愿他多么多么想拥有一个属于自己的网站,但是,他却没有告诉你网站都有哪些内容、栏目,可以做什么。而作为开发者,我们显然关心的是后者。
1.登录时需要提供哪些内容?需不需要提供密码?
2.允许多少人同时在线聊天?
3.与在线用户聊天时,可以将一条消息发给一个用户,还是可以一次将消息发给多个用户?
4.聊天时发送的消息包括哪些内容?
5.注销和关闭有什么区别?
6.注销和关闭对对方需不需要给对方提示?

由于这是一个范例程序,而我在为大家讲述,所以我只能再充当一下客户的角色,来回答上面的问题:
1.登录时只需要提供用户名称就可以了,不需要输入密码。
2.允许两个人在线聊天。(这里我们只讲述这种简单情况,允许多人聊天需要使用多线程)
3.因为只有两个人,那么自然是只能发给一个用户了。
4.聊天发送的消息包括:用户名称、发送时间还有正文。
5.注销并不关闭程序,只是离开了对话,可以再次进行连接。关闭则是退出整个应用程序。
6.注销和关闭均需要给对方提示。

好了,有了上面这些信息我们基本上就掌握了程序需要完成的功能,那么接下来做什么?开始编码了么?上面的这些属于业务流程,除非你对它已经非常熟悉,或者程序非常的小,那么可以对它进行编码,但是实际中,我们最好再编写一些用例,这样会使程序的流程更加的清楚。

1.3编写用例

通常一个用例对应一个功能或者叫需求,它是程序的一个执行路径或者执行流程。编写用例的思路是:假设你已经有了这样一个聊天程序,那么你应该如何使用它?我们的使用步骤,就是一个用例。用例的特点就每次只针对程序的一个功能编写,最后根据用例编写代码,最终完成程序的开发。我们这里的需求只有简单的几个:登录,发送消息,接收消息,注销或关闭,上面的分析是对这几点功能的一个明确。接下来我们首先编写第一个用例:登录。

在开始之前,我们先明确一个概念:客户端,服务端。因为这个程序只是在两个人(机器)之间聊天,那么我们大致可以绘出这样一个图来:

我们期望用户A和用户B进行对话,那么我们就需要在它们之间建立起连接。尽管“用户A”和“用户B”的地位是对等的,但按照约定俗称的说法:我们将发起连接请求的一方称为客户端(或叫本地),另一端称为服务端(或叫远程)。所以我们的登录过程,就是“用户A”连接到“用户B”的过程,或者说客户端(本地)连接到服务端(远程)的过程。在分析这个程序的过程中,我们总是将其分为两部分,一部分为发起连接、发送消息的一方(本地),一方为接受连接、接收消息的一方(远程)。

登录和连接(本地)
主路径 可选路径
1.打开应用程序,显示登录窗口
2.输入用户名
3.点击“登录”按钮,登录成功 3.“登录”失败

如果用户名为空,重新进入第2步。

4.显示主窗口,显示登录的用户名称
5.点击“连接”,连接至远程
6.连接成功
6.1提示用户,连接已经成功。
6.连接失败
6.1 提示用户,连接不成功
5.在用户界面变更控件状态

5.2连接为灰色,表示已经连接

5.3注销为亮色,表示可以注销

5.4发送为亮色,表示可以发消息

这里我们的用例名称为登录和连接,但是后面我们又打了一个括号,写着“本地”,它的意思是说,登录和连接是客户端,也就是发起连接的一方采取的动作。同样,我们需要写下当客户端连接至服务端时,服务端采取的动作。

登录和连接(远程)
主路径 可选路径
1-4 同客户端
5.等待连接
6.如果有连接,自动在用户界面显示“远程主机连接成功”

接下来我们来看发送消息。在发送消息时,已经是登录了的,也就是“用户A”、“用户B”已经做好了连接,所以我们现在就可以只关注发送这一过程:

发送消息(本地)
主路径 可选路径
1.输入消息
2.点击发送按钮 2.没有输入消息,重新回到第1步
3.在用户界面上显示发出的消息 3.服务端已经断开连接或者关闭

3.1在客户端用户界面上显示错误消息

然后我们看一下接收消息,此时我们只关心接收消息这一部分。

接收消息(远程)
主路径 可选路径
1.侦听到客户端发来的消息,自动显示在用户界面上。

注意到这样一点:当远程主机向本地返回消息时,它的用例又变为了上面的用例“发送消息(本地)”。因为它们的角色已经互换了。

最后看一下注销,我们这里研究的是当我们在本地机器点击“注销”后,双方采取的动作:

注销(本地主动)
主路径 可选路径
1.点击注销按钮,断开与远程的连接
2.在用户界面显示已经注销
3.更改控件状态

3.1注销为灰色,表示已经注销

3.2连接为亮色,表示可以连接

3.3发送为灰色,表示无法发送

与此对应,服务端应该作出反应:

注销(远程被动)
主路径 可选路径
1.自动显示远程用户已经断开连接。

注意到一点:当远程主动注销时,它采取的动作为上面的“本地主动”,本地采取的动作则为这里的“远程被动”。

至此,应用程序的功能分析和用例编写就告一段落了,通过上面这些表格,之后再继续编写程序变得容易了许多。另外还需要记得,用例只能为你提供一个操作步骤的指导,在实现的过程中,因为技术等方面的原因,可能还会有少量的修改。如果修改量很大,可以重新修改用例;如果修改量不大,那么就可以直接编码。这是一个迭代的过程,也没有一定的标准,总之是以高效和合适为标准。

2.分析与设计

我们已经很清楚地知道了程序需要做些什么,尽管现在还不知道该如何去做。我们甚至可以编写出这个程序所需要的接口,以后编写代码的时候,我们只要去实现这些接口就可以了。这也符合面向接口编程的原则。另外我们注意到,尽管这是一个聊天程序,但是却可以明确地划分为两部分,一部分发送消息,一部分接收消息。另外注意上面标识为自动的语句,它们暗示这个操作需要通过事件的通知机制来完成。关于委托和事件,可以参考这两篇文章:

  • C#中的委托和事件 - 委托和事件的入门文章,同时捎带讲述了Observer设计模式和.NET的事件模型
  • C#中的委托和事件(续)- 委托和事件更深入的一些问题,包括异常、超时的处理,以及使用委托来异步调用方法。

2.1消息Message

首先我们可以定义消息,前面我们已经明确了消息包含三个部分:用户名、时间、内容,所以我们可以定义一个结构来表示这个消息:

public struct Message {
 private readonly string userName;
 private readonly string content;
 private readonly DateTime postDate;

 public Message(string userName, string content) {
  this.userName = userName;
  this.content = content;
  this.postDate = DateTime.Now;
 }

 public Message(string content) : this("System", content) { }

 public string UserName {
  get { return userName; }
 }

 public string Content {
  get { return content; }
 }

 public DateTime PostDate {
  get { return postDate; }
 }

 public override string ToString() {
  return String.Format("{0}[{1}]:\r\n{2}\r\n", userName, postDate, content);
 }
 }

2.2消息发送方IMessageSender

从上面我们可以看出,消息发送方主要包含这样几个功能:登录、连接、发送消息、注销。另外在连接成功或失败时还要通知用户界面,发送消息成功或失败时也需要通知用户界面,因此,我们可以让连接和发送消息返回一个布尔类型的值,当它为真时表示连接或发送成功,反之则为失败。因为登录没有任何的业务逻辑,仅仅是记录控件的值并进行显示,所以我不打算将它写到接口中。因此我们可以得出它的接口大致如下:

public interface IMessageSender {
 bool Connect(IPAddress ip, int port); // 连接到服务端
 bool SendMessage(Message msg);  // 发送用户
 void SignOut();     // 注销系统
}

2.3消息接收方IMessageReceiver

而对于消息接收方,从上面我们可以看出,它的操作全是被动的:客户端连接时自动提示,客户端连接丢失时显示自动提示,侦听到消息时自动提示。注意到上面三个词都用了“自动”来修饰,在C#中,可以定义委托和事件,用于当程序中某种情况发生时,通知另外一个对象。在这里,程序即是我们的IMessageReceiver,某种情况就是上面的三种情况,而另外一个对象则为我们的用户界面。因此,我们现在首先需要定义三个委托:

public delegate void MessageReceivedEventHandler(string msg);
 public delegate void ClientConnectedEventHandler(IPEndPoint endPoint);
 public delegate void ConnectionLostEventHandler(string info);

接下来,我们注意到接收方需要侦听消息,因此我们需要在接口中定义的方法是StartListen()和StopListen()方法,这两个方法是典型的技术相关,而不是业务相关,所以从用例中是看不出来的,可能大家现在对这两个方法是做什么的还不清楚,没有关系,我们现在并不写实现,而定义接口并不需要什么成本,我们写下IMessageReceiver的接口定义:

public interface IMessageReceiver {
 event MessageReceivedEventHandler MessageReceived; // 接收到发来的消息
 event ConnectionLostEventHandler ClientLost;  // 远程主动断开连接
 event ClientConnectedEventHandler ClientConnected; // 远程连接到了本地
 void StartListen();  // 开始侦听端口
 void StopListen();  // 停止侦听端口
}

我记得曾经看过有篇文章说过,最好不要在接口中定义事件,但是我忘了他的理由了,所以本文还是将事件定义在了接口中。

2.4主程序Talker

而我们的主程序是既可以发送,又可以接收,一般来说,如果一个类像获得其他类的能力,以采用两种方法:继承和复合。因为C#中没有多重继承,所以我们无法同时继承实现了IMessageReceiver和IMessageSender的类。那么我们可以采用复合,将它们作为类成员包含在Talker内部:

public class Talker {
 private IMessageReceiver receiver;
 private IMessageSender sender;

 public Talker(IMessageReceiver receiver, IMessageSender sender) {
  this.receiver = receiver;
  this.sender = sender;
 }
 }

现在,我们的程序大体框架已经完成,接下来要关注的就是如何实现它,现在让我们由设计走入实现,看看实现一个网络聊天程序,我们需要掌握的技术吧。

C#网络编程基础(篇外篇)

这部分的内容请参考 C#网络编程 系列文章,共5个部分较为详细的讲述了基于Socket的网络编程的初步内容。

编写程序代码

如果你已经看完了上面一节C#网络编程,那么本章完全没有讲解的必要了,所以我只列出代码,对个别值得注意的地方稍微地讲述一下。首先需要了解的就是,我们采用的是三个模式中开发起来难度较大的一种,无服务器参与的模式。还有就是我们没有使用广播消息,所以需要提前知道连接到的远程主机的地址和端口号。

1.实现IMessageSender接口

public class MessageSender : IMessageSender {

 TcpClient client;
 Stream streamToServer;

 // 连接至远程
 public bool Connect(IPAddress ip, int port) {
  try {
  client = new TcpClient();
  client.Connect(ip, port);
  streamToServer = client.GetStream(); // 获取连接至远程的流
  return true;
  } catch {
  return false;
  }
 }

 // 发送消息
 public bool SendMessage(Message msg) {
  try {
  lock (streamToServer) {
   byte[] buffer = Encoding.Unicode.GetBytes(msg.ToString());
   streamToServer.Write(buffer, 0, buffer.Length);
   return true;
  }
  } catch {
  return false;
  }
 }

 // 注销
 public void SignOut() {
  if (streamToServer != null)
  streamToServer.Dispose();
  if (client != null)
  client.Close();
 }
 }

这段代码可以用朴实无华来形容,所以我们直接看下一段。

2.实现IMessageReceiver接口

public delegate void PortNumberReadyEventHandler(int portNumber);

 public class MessageReceiver : IMessageReceiver {

 public event MessageReceivedEventHandler MessageReceived;
 public event ConnectionLostEventHandler ClientLost;
 public event ClientConnectedEventHandler ClientConnected;

 // 当端口号Ok的时候调用 -- 需要告诉用户界面使用了哪个端口号在侦听
 // 这里是业务上体现不出来,在实现中才能体现出来的
 public event PortNumberReadyEventHandler PortNumberReady;

 private Thread workerThread;
 private TcpListener listener;

 public MessageReceiver() {
  ((IMessageReceiver)this).StartListen();
 }

 // 开始侦听:显示实现接口
 void IMessageReceiver.StartListen() {
  ThreadStart start = new ThreadStart(ListenThreadMethod);
  workerThread = new Thread(start);
  workerThread.IsBackground = true;
  workerThread.Start();
 }

 // 线程入口方法
 private void ListenThreadMethod() {
  IPAddress localIp = IPAddress.Parse("127.0.0.1");
  listener = new TcpListener(localIp, 0);
  listener.Start();

  // 获取端口号
 IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint;
  int portNumber = endPoint.Port;
  if (PortNumberReady != null) {
  PortNumberReady(portNumber); // 端口号已经OK,通知用户界面
 }

  while (true) {
  TcpClient remoteClient;
  try {
   remoteClient = listener.AcceptTcpClient();
  } catch {
   break;
  }
  if (ClientConnected != null) {
   // 连接至本机的远程端口
  endPoint = remoteClient.Client.RemoteEndPoint as IPEndPoint;
   ClientConnected(endPoint); // 通知用户界面远程客户连接
  }

  Stream streamToClient = remoteClient.GetStream();
  byte[] buffer = new byte[8192];

  while (true) {
   try {
   int bytesRead = streamToClient.Read(buffer, 0, 8192);
   if (bytesRead == 0) {
    throw new Exception("客户端已断开连接");
   }
   string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);

   if (MessageReceived != null) {
    MessageReceived(msg); // 已经收到消息
   }
   } catch (Exception ex) {
   if (ClientLost != null) {
    ClientLost(ex.Message); // 客户连接丢失
   break;   // 退出循环
   }
   }
  }
  }
 }

 // 停止侦听端口
 public void StopListen() {
  try {
  listener.Stop();
  listener = null;
  workerThread.Abort();
  } catch { }
 }
 }

这里需要注意的有这样几点:我们StartListen()为显式实现接口,因为只能通过接口才能调用此方法,接口的实现类看不到此方法;这通常是对于一个接口采用两种实现方式时使用的,但这里我只是不希望MessageReceiver类型的客户调用它,因为在MessageReceiver的构造函数中它已经调用了StartListen。意思是说,我们希望这个类型一旦创建,就立即开始工作。我们使用了两个嵌套的while循环,这个它可以为多个客户端的多次请求服务,但是因为是同步操作,只要有一个客户端连接着,我们的后台线程就会陷入第二个循环中无法自拔。所以结果是:如果有一个客户端已经连接上了,其它客户端即使连接了也无法对它应答。最后需要注意的就是四个事件的使用,为了向用户提供侦听的端口号以进行连接,我又定义了一个PortNumberReadyEventHandler委托。

3.实现Talker类

Talker类是最平庸的一个类,它的全部功能就是将操作委托给实际的IMessageReceiver和IMessageSender。定义这两个接口的好处也从这里可以看出来:如果日后想重新实现这个程序,所有Windows窗体的代码和Talker的代码都不需要修改,只需要针对这两个接口编程就可以了。

public class Talker {
 private IMessageReceiver receiver;
 private IMessageSender sender;

 public Talker(IMessageReceiver receiver, IMessageSender sender) {
  this.receiver = receiver;
  this.sender = sender;
 }

 public Talker() {
  this.receiver = new MessageReceiver();
  this.sender = new MessageSender();
 }

 public event MessageReceivedEventHandler MessageReceived {
  add {
  receiver.MessageReceived += value;
  }
  remove {
  receiver.MessageReceived -= value;
  }
 }

 public event ClientConnectedEventHandler ClientConnected {
  add {
  receiver.ClientConnected += value;
  }
  remove {
  receiver.ClientConnected -= value;
  }
 }

 public event ConnectionLostEventHandler ClientLost {
  add {
  receiver.ClientLost += value;
  }
  remove {
  receiver.ClientLost -= value;
  }
 }

 // 注意这个事件
 public event PortNumberReadyEventHandler PortNumberReady {
  add {
  ((MessageReceiver)receiver).PortNumberReady += value;
  }
  remove {
  ((MessageReceiver)receiver).PortNumberReady -= value;
  }
 }

 // 连接远程 - 使用主机名
 public bool ConnectByHost(string hostName, int port) {
  IPAddress[] ips = Dns.GetHostAddresses(hostName);
  return sender.Connect(ips[0], port);
 }

 // 连接远程 - 使用IP
 public bool ConnectByIp(string ip, int port) {
  IPAddress ipAddress;
  try {
  ipAddress = IPAddress.Parse(ip);
  } catch {
  return false;
  }
  return sender.Connect(ipAddress, port);
 }

 // 发送消息
 public bool SendMessage(Message msg) {
  return sender.SendMessage(msg);
 }

 // 释放资源,停止侦听
 public void Dispose() {
  try {
  sender.SignOut();
  receiver.StopListen();
  } catch {
  }
 }

 // 注销
 public void SignOut() {
  try {
  sender.SignOut();
  } catch {
  }
 }
 }

4.设计窗体,编写窗体事件代码

现在我们开始设计窗体,我已经设计好了,现在可以先进行一下预览:

这里需要注意的就是上面的侦听端口,是程序接收消息时的侦听端口,也就是IMessageReceiver所使用的。其他的没有什么好说的,下来我们直接看一下代码,控件的命名是自解释的,我就不多说什么了。唯一要稍微说明下的是txtMessage指的是下面发送消息的文本框,txtContent指上面的消息记录文本框:

public partial class PrimaryForm : Form {

 private Talker talker;
 private string userName;

 public PrimaryForm(string name) {
  InitializeComponent();
  userName = lbName.Text = name;
  this.talker = new Talker();
  this.Text = userName + " Talking ...";
  talker.ClientLost +=
  new ConnectionLostEventHandler(talker_ClientLost);
  talker.ClientConnected +=
  new ClientConnectedEventHandler(talker_ClientConnected);
  talker.MessageReceived +=
  new MessageReceivedEventHandler(talker_MessageReceived);
  talker.PortNumberReady +=
  new PortNumberReadyEventHandler(PrimaryForm_PortNumberReady);
 }

 void ConnectStatus() { }
 void DisconnectStatus() { }

 // 端口号OK
 void PrimaryForm_PortNumberReady(int portNumber) {
  PortNumberReadyEventHandler del = delegate(int port) {
  lbPort.Text = port.ToString();
  };
  lbPort.Invoke(del, portNumber);
 }

 // 接收到消息
 void talker_MessageReceived(string msg) {
  MessageReceivedEventHandler del = delegate(string m) {
  txtContent.Text += m;
  };
  txtContent.Invoke(del, msg);
 }

 // 有客户端连接到本机
 void talker_ClientConnected(IPEndPoint endPoint) {
  ClientConnectedEventHandler del = delegate(IPEndPoint end) {
  IPHostEntry host = Dns.GetHostEntry(end.Address);
  txtContent.Text +=
   String.Format("System[{0}]:\r\n远程主机{1}连接至本地。\r\n", DateTime.Now, end);
  };
  txtContent.Invoke(del, endPoint);
 }

 // 客户端连接断开
 void talker_ClientLost(string info) {
  ConnectionLostEventHandler del = delegate(string information) {
  txtContent.Text +=
   String.Format("System[{0}]:\r\n{1}\r\n", DateTime.Now, information);
  };
  txtContent.Invoke(del, info);
 }

 // 发送消息
 private void btnSend_Click(object sender, EventArgs e) {
  if (String.IsNullOrEmpty(txtMessage.Text)) {
  MessageBox.Show("请输入内容!");
  txtMessage.Clear();
  txtMessage.Focus();
  return;
  }

  Message msg = new Message(userName, txtMessage.Text);
  if (talker.SendMessage(msg)) {
  txtContent.Text += msg.ToString();
  txtMessage.Clear();
  } else {
  txtContent.Text +=
   String.Format("System[{0}]:\r\n远程主机已断开连接\r\n", DateTime.Now);
  DisconnectStatus();
  }
 }

 // 点击连接
 private void btnConnect_Click(object sender, EventArgs e) {

  string host = txtHost.Text;
  string ip = txtHost.Text;
  int port;

  if (String.IsNullOrEmpty(txtHost.Text)) {
  MessageBox.Show("主机名称或地址不能为空");
  }  

  try{
  port = Convert.ToInt32(txtPort.Text);
  }catch{
  MessageBox.Show("端口号不能为空,且必须为数字");
  return;
  }

  if (talker.ConnectByHost(host, port)) {
  ConnectStatus();
  txtContent.Text +=
   String.Format("System[{0}]:\r\n已成功连接至远程\r\n", DateTime.Now);
  return;
  }

  if(talker.ConnectByIp(ip, port)){
  ConnectStatus();
  txtContent.Text +=
   String.Format("System[{0}]:\r\n已成功连接至远程\r\n", DateTime.Now);
  }else{
  MessageBox.Show("远程主机不存在,或者拒绝连接!");
  }  

  txtMessage.Focus();
 }

 // 关闭按钮点按
 private void btnClose_Click(object sender, EventArgs e) {
  try {
  talker.Dispose();
  Application.Exit();
  } catch {
  }
 }

 // 直接点击右上角的叉
 private void PrimaryForm_FormClosing(object sender, FormClosingEventArgs e) {
  try {
  talker.Dispose();
  Application.Exit();
  } catch {
  }
 }

 // 点击注销
 private void btnSignout_Click(object sender, EventArgs e) {
  talker.SignOut();
  DisconnectStatus();
  txtContent.Text +=
  String.Format("System[{0}]:\r\n已经注销\r\n",DateTime.Now);
 }

 private void btnClear_Click(object sender, EventArgs e) {
  txtContent.Clear();
 }
 }

在上面代码中,分别通过四个方法订阅了四个事件,以实现自动通知的机制。最后需要注意的就是SignOut()和Dispose()的区分。SignOut()只是断开连接,Dispose()则是离开应用程序。

总结

这篇文章简单地分析、设计及实现了一个聊天程序。这个程序只是对无服务器模式实现聊天的一个尝试。我们分析了需求,随后编写了几个用例,并对本地、远程的概念做了定义,接着编写了程序接口并最终实现了它。这个程序还有很严重的不足:它无法实现自动上线通知,而必须要事先知道端口号并进行手动连接。为了实现一个功能强大且开发容易的程序,更好的办法是使用集中型服务器模式。
感谢阅读,希望这篇文章能对你有所帮助。

(0)

相关推荐

  • c#多线程网络聊天程序代码分享(服务器端和客户端)

    XuLIeHua类库 复制代码 代码如下: using System;using System.Collections;  using System.Collections.Generic;using System.Threading;  using System.Runtime.Serialization;using System.Runtime.Serialization.Formatters.Binary;using System.Text;using System.IO;using Sy

  • C#聊天程序服务端与客户端完整实例代码

    本文所述为基于C#实现的多人聊天程序服务端与客户端完整代码.本实例省略了结构定义部分,服务端主要是逻辑处理部分代码,因此使用时需要完善一些窗体按钮之类的. 先看服务端代码如下: using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Net; using

  • C#实现简单聊天程序的方法

    本文实例讲述了C#简单聊天程序实现方法.分享给大家供大家参考.具体如下: 假如有服务器端程序,ChatServer和客户端程序ChatClient.实现客户端向服务器端发送信息的简单功能. 运行步骤, 1.先是服务器端start listen, 2.然后客户端connect. 3.客户端发送消息   只要服务器端start listen了,然后客户端也connect了.这样建立起连接后.接受发送信息就方便了,只要用writer,reader去操作NetworkStream   服务器ChatSe

  • c#实现多线程局域网聊天系统

    觉得好有点帮助就顶一下啦. socke编程,支持多客户端,多线程操作避免界面卡死. 开启socket private void button1_Click(object sender, EventArgs e) { try { int port = int.Parse(txt_port.Text); string host = txt_ip.Text; //创建终结点 IPAddress ip = IPAddress.Parse(host); IPEndPoint ipe = new IPEnd

  • C#基于Windows服务的聊天程序(1)

    本文将演示怎么通过C#开发部署一个Windows服务,该服务提供各客户端的信息通讯,适用于局域网.采用TCP协议,单一服务器连接模式为一对多:多台服务器的情况下,当客户端连接数超过预设值时可自动进行负载转移,当然也可手动切换服务器,这种场景在实际项目中应用广泛. 简单的消息则通过服务器转发,文件类的消息则让客户端自己建立连接进行传输.后续功能将慢慢完善. 自定义协议: 1.新建Windows服务项目 2.修改配置文件添加 <appSettings> <add key="maxQ

  • C#基于UDP实现的P2P语音聊天工具

    语音获取 要想发送语音信息,首先得获取语音,这里有几种方法,一种是使用DirectX的DirectXsound来录音,我为了简便使用一个开源的插件NAudio来实现语音录取. 在项目中引用NAudio.dll //------------------录音相关----------------------------- private IWaveIn waveIn; private WaveFileWriter writer; private void LoadWasapiDevicesCombo(

  • C#制作简单的多人在线即时交流聊天室

    实现网页版的在线聊天室的方法有很多,在没有来到HTML5之前,常见的有:定时轮询.长连接+长轮询.基于第三方插件(如FLASH的Socket),而如果是HTML5,则比较简单,可以直接使用WebSocket,当然HTML5目前在PC端并没有被所有浏览器支持,所以我的这个聊天室仍是基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室,这个聊天室其实是我上周周末完成的,功能简单,可能有些不足,但可以满足在线即时聊天需求,分享也是给大家提供一个思路,大家可以基于此来实现更好的在线即时聊

  • 基于c#用Socket做一个局域网聊天工具

    程序设计成为简单的服务端和客户端之间的通信, 但通过一些方法可以将这两者进行统一起来, 让服务端也成为客户端, 让客户端也成为服务端, 使它们之间可以互相随时不间断的通信. 考虑到实现最原始的服务端和客户端之间的通信所需要的步骤对于写这样的程序是很有帮助的. 作为服务端, 要声明一个Socket A并绑定(Bind)某一个IP+这个IP指定的通信端口, 比如这个是127.0.0.1:9050, 然后开始监听(Listen), Listen可以监听来自多个IP传过来的连接请求, 具体可以同时连接几

  • 分享一个C#编写简单的聊天程序(详细介绍)

    引言 这是一篇基于Socket进行网络编程的入门文章,我对于网络编程的学习并不够深入,这篇文章是对于自己知识的一个巩固,同时希望能为初学的朋友提供一点参考.文章大体分为四个部分:程序的分析与设计.C#网络编程基础(篇外篇).聊天程序的实现模式.程序实现. 程序的分析与设计 1.明确程序功能 如果大家现在已经参加了工作,你的经理或者老板告诉你,"小王,我需要你开发一个聊天程序".那么接下来该怎么做呢?你是不是在脑子里有个雏形,然后就直接打开VS2005开始设计窗体,编写代码了呢?在开始之

  • java实现简单TCP聊天程序

    本文实例为大家分享了java实现TCP聊天程序的具体代码,供大家参考,具体内容如下 服务端代码: package com.test.server; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { public

  • 使用python编写简单的小程序编译成exe跑在win10上

    每天的工作其实很无聊,早知道应该去IT公司闯荡的.最近的工作内容是每逢一个整点,从早7点到晚11点,去查一次客流数据,整理到表格中,上交给素未蒙面的上线,由他呈交领导查阅. 人的精力毕竟是有限的,所以不一定在每个整点都可以及时去做这项工作.灵机一动,这种一丝不苟的活儿应该让计算器来做,由它来在每个整点来告诉我该去工作了. 说干就干,平时只用c#写过小程序,由于办公电脑上是公用的,所以没有想自己电脑一样装有visual studio,索性心一横,用python试试吧.总是听说那句大名鼎鼎的"人生苦

  • Android编写简单的聊天室应用

    最近写了一个简单的聊天室应用,可以发送表情,更改头像这些功能.主要技术点就是怎样把表情图片放到textview等Ui控件中展示.这里废话不多说,下面是效果图: 这里主要讲下怎样把文本替换到表情,先说下思路,首先我们的图片是保存在本地资源目录drawable中而所有的资源文件都是R这个类来管理,所以我们可以利用正则表达式找出图片id包装成ImageSpan然后把ImageSpan放到SpannableString中,最后把SpannableString放入edittext中,下面是源码: pack

  • jQuery Deferred和Promise创建响应式应用程序详细介绍

    这篇文章,我们一起探索一下 JavaScript 中的 Deferred 和 Promise 的概念,它们是 JavaScript 工具包(如Dojo和MochiKit)中非常重要的一个功能,最近也首次亮相于 流行的 JavaScript 库 jQuery(已经是1.5版本的事情了). Deferred 提供了一个抽象的非阻塞的解决方案(如 Ajax 请求的响应),它创建一个 "promise" 对象,其目的是在未来某个时间点返回一个响应.如果您之前没有接触过 "promis

  • 如何编写一个最简单的聊天程序?

    chat.html <html> <head> <title>精彩春风之简单聊天</title> </head> <frameset rows="*,100"> <frame src="chatopinions.asp"> <frame src="chatform.asp"> </frameset> </html> chatop

  • C#用记事本编写简单WinForm窗体程序

    平时我们编写WinForm程序经常使用VS进行拖控件的方式,这样做虽然简单,但是无法深入了解WinForm程序的本质.其实,用记事本也可以编写出VS编写的WinForm程序.还是直接看代码吧: 1.打开记事本,写入以下代码,另存为hello.cs文件 using System; using System.Windows.Forms; namespace Hello { public class Form1:Form { private System.Windows.Forms.Button bt

  • js编写简单的聊天室功能

    这个聊天室写的特别简易,比较适合刚开始学习js的同学借鉴,当然,写的不好,也希望诸位大神可以进行批评改正. 聊天室要求: 1.不能发空消息 2.敏感字***显示 3.图片替换 开心,尴尬 4.显示聊天内容和时间 5.每发一条信息,随机显示名称,先把一些名称定义到array里面 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> &

  • 解析:通过php socket并借助telnet实现简单的聊天程序

    以下是通过php的socket扩展模块实现的一个简单的消息处理服务器端:绑定在一个本机的端口,监听客户端的连接,接收数据并转发给发送者之外的所有客户端socket_server.php 复制代码 代码如下: #!/usr/bin/env php<?php//author:zhxiaif(!extension_loaded('sockets')){    die('the sockets extension is not loaded!');}const PORT=9981;$socket=soc

  • 编写简单的Python程序来判断文本的语种

    1.问题的描述 用Python进行文本处理时,有时候处理的文本中包含中文.英文.日文等多个语系的文本,有时候不能同时进行处理,这个时候就需要判别当前文本是属于哪个语系的.Python中有个langid工具包提供了此功能,langid目前支持97种语言的检测,非常好用. 2.程序的代码 以下Python是调用langid工具包来对文本进行语言检测与判别的程序代码: import langid #引入langid模块 def translate(inputFile, outputFile): fin

随机推荐