第五步:既然是聊天室,那么仅仅只能一个用户自己和自己聊天,显然该该程序是有瑕疵的。那么我们就需要支持多用户同时在线聊天。这一步中,我们就需要用到多线程的概念。为什么要用到多线程?线程可以通俗的理解为每有一个新运动员便多建造一条跑道,以便所有运动员可以经历同样的从头到尾的全部过程。那如果放到程序中便是每有一个新的客户端登录,便重新经历一遍从连接成功到发送消息的过程,同时,为了能够让服务端循环的接受客户端,需要将客户端连接连接逻辑放入到循环中。而这些代码仅与服务端有关,客户端不必修改。
服务端:
/**
* 主要运行逻辑
*/
public void start() {
try {
while(true) {
Socket socket=server.accept();
System.out.println(socket.getInetAddress()+"连接成功!");
moreClientHandler mch=new moreClientHandler(socket);
Thread t=new Thread(mch);
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 多客户端处理内部类
*/
class moreClientHandler implements Runnable{
private Socket socket;
public moreClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
PrintWriter pw=new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
String Read="";
while((Read=br.readLine())!=null) {
pw.println(Read);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
第六步:实现了可以同时连接多个客户端后,便需要使得各个客户端之间可以相互通信,因为仅仅完成第五步的话,我们会发现每个客户端仅仅能够与自己对话,无法实现聊天室的基本功能,所以我们需要实现互相之间的通信。客户端与服务端信息互发时,我们是通过发送信息客户端通过自己的输出流将信息发送至服务端后,服务端通过自己的输入流读取到客户端发送来的信息,再通过自己的输出流将信息发送回客户端,我们通过给每个客户端一个服务端输入输出流来实现客户端与服务端的交互,那么便可以将客户端看做是key,将客户端对应的服务端输出流看做value,由此我们可以定义一个map集合,来存储客户端与其对应的服务端输出流,当某一客户端发送信息时,可以通过遍历map集合中的所有value即所有服务端输出流,将某一客户端发到服务端的信息逐一发送到各个客户端,即实现了群聊,所以我们现在需要给每一个客户端一个名字,以方便用于分辨是哪个客户端,在客户端连接服务端时,应将自己的名字同时发送至服务端。所以,服务端也需要一个信息发送方法。同时,可以在客户端窗体设置方法块内设置窗体名字,使得自己的客户端名字可见。
服务端:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Server {
//成员变量
ServerSocket server;
Map<String,PrintWriter> map;
/**
* 服务端构造方法
*/
public Server() {
try {
server=new ServerSocket(8888);
map=new HashMap<String,PrintWriter>();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 多客户端处理内部类
*/
class moreClientHandler implements Runnable{
private Socket socket;
public moreClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
PrintWriter pw=new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
String ClientName=br.readLine();
map.put(ClientName,pw);
String Read="";
while((Read=br.readLine())!=null) {
sendMessage(ClientName,Read);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服务端反馈信息至客户端
*/
public void sendMessage(String str1,String str2) {
Set<Entry<String,PrintWriter>> entrys=map.entrySet();
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for(Entry<String,PrintWriter> entry:entrys) {
entry.getValue().println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
}
}
客户端:
public class Client extends JFrame{
private String name;
public Client(String name) {
this.name=name;
try {
socket=new Socket("localhost",8888);
panel=new JPanel();
showMsg=new JTextArea(15,42);
sendMsg=new JTextArea(5,42);
inIt();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 主运行逻辑
*/
public void start() {
sendMessage(name);
KeyListener key=new KeyAdapter() {
public void keyReleased(KeyEvent e) {
String str=sendMsg.getText();
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
sendMessage(str);
}
}
};
sendMsg.addKeyListener(key);
try {
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
String backMsg="";
while((backMsg=br.readLine())!=null) {
showMsg.append(backMsg+"\n");
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
public static void main(String[] args) {
Client client=new Client("zyd"+(int)(Math.random()*100));
client.start();
}
}
第七步:现在群聊已经完全实现,接下来就是实现私聊,也就是当@某个客户端时,仅被@客户端与本客户端可见消息内容,其他客户端均不可见。
服务端:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Server {
//成员变量
ServerSocket server;
Map<String,PrintWriter> map;
/**
* 服务端构造方法
*/
public Server() {
try {
server=new ServerSocket(8888);
map=new HashMap<String,PrintWriter>();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 多客户端处理内部类
*/
class moreClientHandler implements Runnable{
private Socket socket;
public moreClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
PrintWriter pw=new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
String ClientName=br.readLine();
map.put(ClientName,pw);
String Read="";
while((Read=br.readLine())!=null) {
sendMessage(ClientName,Read);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服务端反馈信息至客户端
*/
public void sendMessage(String str1,String str2) {
Set<Entry<String,PrintWriter>> entrys=map.entrySet();
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
if(!str2.substring(0,1).equals("@")) {
for(Entry<String,PrintWriter> entry:entrys) {
entry.getValue().println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
}
}else {
map.get(str1).println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
map.get(str2.substring(str2.indexOf('@')+1,str2.indexOf(' '))).println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
}
}
总结:聊天室至此已经完成,其中主要运用了多线程及输入输出流及网络编程,实现了人与人之间通过机器进行交互,通过再一次的编写写聊天室,更加通透的了解了流的使用以及对多线程的理解,希望能够在以后的编程到路上更上一层楼。与所有看到本系列博文的程序猿共勉。