最简易多人聊天室的实现——

一篇搞定从单人到多人的聊天实现。

声明:该文章为原创,且只适合基础浅薄的初学者阅读,该文章中没用使用任何较为复杂的操作和技巧,只为初学者快速理清多人聊天的实现思路。

主要知识需求:

  • IO流
  • 多线程
  • TCP网络编程

大体思路:

首先在了解多人聊天室实现思路之前,我们先要搞清楚双人聊天的机制,即单一客户端与服务器进行的交互思路:

服务器与单独客户端间的交互思路

首先服务器端要实例化一个ServerSocket对象,并用其中的accept()方法等待客户端的连接。
这里值得注意的是:ServreSocket类中的accept()方法是一个阻塞方法,也就是说accept()方法在获取客户端的连接之前会一直进行阻塞式的等待,不会让其下面的代码执行,直到得到客户端的连接为止。

服务器一直等待的同时,我们就要建立客户端并向服务器申请连接。那么在客户端代码中,我们要实例化一个Socket对象,其应该具有和服务器端ServreSocket相同的端口号,以此确保对服务器提出准确的连接。在成功实例化创建Socket对象后,就相当于成功和服务器建立了连接(前提是申请的服务器对象已创建并在执行accept()方法等待)

在客户端成功提出连接申请后,我们再回到服务器端accept()方法上来,accept()方法在成功得到连接申请后,返回的值是一个Socket对象,该Socket对象是通向该客户端的连接。

在成功通过Socket对象和ServerSocket对象在客户端与服务器间建立TCP连接后,我们就要开始进行服务器与客户端间的信息交流了,其手段则是通过IO流的实现: 通过Socket类中的getInputStream()方法和getOutputStrea()方法可以获取对应方向的输入流和输出流。

服务器端通过对accept()得到的Socke对象使用getOutputStrea()方法创建的OutputStream流可以向客户端写入信息,同理对该Socket对象使用getInputStream()方法获取的InputStream流可以从客户端中读取信息。反之客户端亦然。

到此我们成功实现了客户端与服务器之前有顺序的一读一写的功能思路。不过存在的问题也同样十分明显:即就是我们假若先让客户端向服务器写数据,再从服务器中接受数据,那么服务器也必须先等待客户端写入的数据,再向客户端写数据,这样一来读写的顺序就被固定了,无法实现同时进行随意的读写操作。
在此做一点补充:IO流的read()方法其实也是一种阻塞方法,即就是read()会一直等待流中信息的到来并进行读取,如果没有信息从流中过来,它就会对线程进行阻塞并一直等待。

要实现两端随意的无顺序读写操作,也就需要使用多线程的操作。我们的思路是:读和写的操作分开,分成两个相互独立的线程同时进行,以达到读写并行,互不干扰的目的。

具体的思路是:

  • 服务器对客户端的监听读取:服务器对客户端启动一个无线循环的监听线程,在对客户端建立了读取流后在等效于while(true)的循环中用其读取流的read()方法对客户端中传过来的信息进行无线的监听并读取。
  • 服务器对客户端的循环写入:服务器对客户端启动一个循环写入的线程,在对客户端建立了写入流后在等效于while(true)的循环中先等待接受控制台传入的信息,并把该信息通过输出流写入客户端的流中。

以上两个思路的线程并发进行,则可以实现服务器对客户端的同时读取与写入操作,客户端对服务器的操作完全可以照搬以上操作,这里就不再赘述。

这会至此,我们终于实现了服务器和客户端两个对象之间的随意的信息交互功能。

多人聊天的实现思路:

在大肆铺垫之后,终于进入正题:
现在我们需要思考多人聊天和双人聊天之间的区别,此前我们实现的客户端与服务器间交互的思路是:服务器接受到另一端的信息后只给服务器自己看到就可以了,而现在不同了,现在要实现群聊的功能,那么服务器在接受到任何一个客户端发来的消息之后,就要让所有连接服务器的客户端都看到这条消息,那么服务器就要进行转发操作。服务器的转发操作就是实现多人聊天的关键:

  • 首先服务器要不断接收多个客户端的连接,并对多个客户端分别建立独有的IO流。
  • 在接收到一个客户端的连接后,为了方便在转发信息的时候能够找到这个客户端,我们在accept()它之后就把他放到一个ArrayList容器中进行保存,方便后续遍历这些客户端
  • 要实现多人聊天,那么对于每个连接的客户端和服务器之间都用该保持类似我们上面所述的操作,即对每一个客户端,服务器都应该建立一个对其监听读取的线程,和对其循环写入的线程。不过在这里,我们把读取和写入合并为同一个线程,因为我们读取到任意一个客户端信息的同时,就要把这个信息转发给所有客户端,这个操作可以在接收到服务器的连接并添加至容器后进行。 大致思路可用以下伪代码表示
•  while(true)
 {
 socket=accept();
 arraryList.add(socket)
 Thread t1=new Thread(监听读取并转发的线程)
 t1.start();
 }
  • 关于服务器接收并转发信息线程的细节:
    首先服务器在该线程通过等价于while(true)的循环中使用输入流的read()方法分别阻塞式等待每一个客户端的信息,一旦输入流读取到信息,则遍历服务器中存放已连接服务器的ArrayList容器,对每一个Socket客户端对象建立输出流,并把该信息发送到每一个客户端的流中。

以上就是服务器的转发操作,客户端的操作相对于之前基本没有变化,还是保持两个独立的线程实时对服务器中传来的信息进行监听并等待控制台输入的数据对服务器发送即可