转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/5827212.html
另:具体代码实现已移植github: https://github.com/ygj0930/Chat-Room-in-Java ,大家fork之余记得给我个star呀~
聊天室程序的结构图:
架构解释:
Server服务器相当于一个中转站,Client客户端程序传送信息到服务器,服务器再把信息分发到其他客户端上,实现即时通信。
所需技术:
1:数据传输。
服务器与客户端之间的信息传递,都通过数据通道实现,有一个客户端连接到服务器,就有一条数据通道架设于该客户端和服务器之间。
这条数据通道通过Socket来实现:每个客户端通过一个socket与服务器建立连接,这是通道的一个“端点”。而服务器端ServerSocket一旦检测到有客户端接入,accept()方法就会马上返回一个socket对象与客户端的socket对接起来,这是通道的另一个“端点”。“两点建立一条直线”,当然,这里不一定是直的(废话)。总之,一条像水管一样的通道就这样把一个客户端与服务器联系起来了。之后的事情,就是把我们要传输的数据,通过数据通道传输,如同水管运水一样,数据就是那些水。
2:持续监听。
即时聊天要求某一客户端(聊天者)敲出来的话要立刻更新到连接到该服务器上的所有客户端的界面上,这个简单,学过while循环的人应该都能立刻想到:我用一个while()语句持续地从数据通道检测数据不就成了?一旦数据通道有信息,马上把它取出来,在客户端显示就ok了。
然而!这事儿还没完。每个客户端都要保持持续接收用户输入的状态。你总不能一个客户端只发送一次信息就关闭掉了吧?
那么问题来了,又要保持持续监听服务器端传过来的信息,又要保持持续监听键盘输入,那这俩while循环到底怎么安排呢?
这里最容易犯的错误就是,用一个while循环嵌套了另一个while循环。比如下面的代码:
while ((msg1 = br.readLine()) != null) {
System.out.println(msg1);
while (true) {
msg2 = re.readLine();
pw.println(msg2);
}
外层循环持续监听数据通道,一旦有服务器端传过来的信息,就提取出来显示到客户端。内层循环持续监听键盘输入,有则通过pw(数据通道,等下会有源码)传输给服务器。
这里错误在于,内层循环是死循环,不会停止,所以会出现客户端不停输入信息发送到服务器,然而信息却没有更新到其他的客户端去。因为客户端代码停留在内层循环,没有出去执行监听并提取数据通道信息那一部分代码。
还有一种案例,就是在客户端输入信息,却没有显示出来,狂按键盘也没见打出一个字。但是把服务器程序关闭后却又在客户端程序窗口看到一大堆刚才输入的信息。
这种错误的原因与上面相反,是监听数据通道的while循环阻塞了下一个while循环(接收键盘输入部分)的执行。代码如下:
BufferedReader br = new BufferedReader(new InputStreamReader(socket .getInputStream())); String msg1; while ((msg1 = br.readLine()) != null) {
System.out.println(msg1);
} BufferedReader re = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
String msg2;
while (true) {
msg2 = re.readLine();
pw.println(msg2);
}
上面一个while循环持续监听数据通道(服务器向客户端传送信息),保持在等待接收信息状态,导致下面的代码没有执行,所以客户端不能接收用户输入,狂敲键盘也没用。
而关闭服务器程序后,数据通道断了,就不再监听,转而运行到了下面的while循环,所以就能接收用户输入。
那么正确的姿势是怎样的呢?那就是:并行。
在一个while循环运行的同时,另一个while循环也运行。两个while循环没有先后之分。
要实现并行,就需要用到多线程技术。【多线程是啥我就不讲了,基本上能做到这个项目的人都是学了多线程再来实践的吧哈哈哈~】
正确完整代码贴出来,仅供参考。如果你是用来应付老师布置的作业的,小心和别的同学撞车-_-:
Server.java: import java.net.*;
import java.io.*;
import java.util.*; public class Server {
int port;
List<Socket> clients;
ServerSocket server; public static void main(String[] args) {
new Server();
} public Server() {
try { port = 8080;
clients = new ArrayList<Socket>();
server = new ServerSocket(port); while (true) {
Socket socket = server.accept();
clients.add(socket);
Mythread mythread = new Mythread(socket);
mythread.start();
} } catch (Exception ex) {
}
} class Mythread extends Thread {
Socket ssocket;
private BufferedReader br;
private PrintWriter pw;
public String msg; public Mythread(Socket s) {
ssocket = s;
} public void run() { try {
br = new BufferedReader(new InputStreamReader(ssocket.getInputStream()));
msg = "欢迎【" + ssocket.getInetAddress() + "】进入聊天室!当前聊天室有【"
+ clients.size() + "】人"; sendMsg(); while ((msg = br.readLine()) != null) { msg = "【" + ssocket.getInetAddress() + "】说:" + msg;
sendMsg(); }
} catch (Exception ex) { }
} public void sendMsg() {
try {
System.out.println(msg); for (int i = clients.size() - 1; i >= 0; i--) {
pw = new PrintWriter(clients.get(i).getOutputStream(), true);
pw.println(msg);
pw.flush();
}
} catch (Exception ex) {
}
}
} } Client.java: import java.io.*;
import java.net.*;
import java.util.*; public class Client {
public int port = 8080;
Socket socket = null; public static void main(String[] args) {
new Client();
} public Client() { try {
socket = new Socket("127.0.0.1", port); new Cthread().start(); BufferedReader br = new BufferedReader(new InputStreamReader(socket
.getInputStream()));
String msg1;
while ((msg1 = br.readLine()) != null) { System.out.println(msg1);
}
} catch (Exception e) {
}
} class Cthread extends Thread { public void run() {
try { BufferedReader re = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
String msg2; while (true) { msg2 = re.readLine();
pw.println(msg2);
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
*惯,来个总结:用Socket构建数据通道(InputStream/OutputStream),用多线程同时运行两个while实现监听与更新,就是聊天室的技术所在。
(本人新手,恳请老码农们指正。感激不尽!若需要转载与交流,请于评论处留言告知我一声,谢谢)