Instant Messaging apps like Wechat and Ding Talk are widely used in our personal life and business. Thus, creating a simple functional chatroom based on pure Java is kind of required ability for Java programmers, while it is also a good opportunity for us to practice multithreading programming as well as Java socket programming. This tutorial will divide the whole process into 3 major sub-steps to illustrate how to achieve this.
First step:
The first step is to create a ChatRoom class, which will be our main class towards users.
public class ChatRoom {
public ServerSocket serverSocket;
//这里主要运用了socket套接字的技术,实现的信息间的通讯
public ChatRoom(ServerSocket serverSocket){
this.serverSocket = serverSocket;
}
public void startServe(){
try{
while(!serverSocket.isClosed()){
//利用多线程技术,使用一个端口创建多线程Socket
Socket socket = serverSocket.accept();
System.out.println("client"+socket.getInetAddress());
//ClientHandle作为用户使用的service媒介,并处理服务器端的线程
ClientHandle clientHandle = new ClientHandle(socket);
Thread thread = new Thread(clientHandle);
thread.start();
}
} catch (IOException e) {
closeServe();
e.printStackTrace();
}
}
public void closeServe(){
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8848);
ChatRoom chatRoom = new ChatRoom(serverSocket);
chatRoom.startServe();
} catch (IOException e) {
e.printStackTrace();
}
}
}
The core concept here is creating a server handler(The ClientHandle) in order to separate our server layer from the main chatroom class, as the server-side needs its own thread handling information exchange.
Step two: Server-side ClientHandle
Here is how to create a Server-side class. As mentioned before, the server-side needs its own thread for obtaining and broadcasting information to all other clients. Therefore, the ClientHandle class needs to implement Runnable interface.
Another critical point is that we need to create an ArrayList or HashMap to contain all the ClientHandles. Sending messages to all ClientHandles in this list is how we could achieve broadcasting information. As all threads are sharing the same list, we have to label the list as "static";
public class ClientHandle implements Runnable{
private Socket socket;
//创建的list应该是static的,不然多线程的情况下无法共用
private static List<ClientHandle> list = new ArrayList<>();
//bufferReader用来读取信息
private BufferedReader bufferedReader;
private BufferedWriter bufferedWriter;
private String user;
public ClientHandle(Socket socket){
this.socket = socket;
try {
this.bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
user = bufferedReader.readLine();
list.add(this);
bufferedWriter.write("欢迎光临:"+user);
//newLine告诉计算机已完成输入
bufferedWriter.newLine();
bufferedWriter.flush();
broadCast(user+"到来聊天室");
} catch (IOException e) {
closeEverything(socket,bufferedWriter,bufferedReader);
e.printStackTrace();
}
}
@Override
public void run() {
String message;
while(!socket.isClosed()){
try {
message = bufferedReader.readLine();
//broadCast使用forEach来将信息传递给每个元素
broadCast(message);
} catch (IOException e) {
closeEverything(socket,bufferedWriter,bufferedReader);
}
}
}
public void broadCast(String message){
for(ClientHandle clientHandle : list){
if(!clientHandle.getUser().equals(this.user)) {
//这里判断是除开自己的Client线程不再重复发送信息
try {
clientHandle.bufferedWriter.write(message);
clientHandle.bufferedWriter.newLine();
clientHandle.bufferedWriter.flush();
} catch (IOException e) {
closeEverything(socket, bufferedWriter, bufferedReader);
e.printStackTrace();
}
}
}
}
public void remove(){
//在删除用户的时候要把用户从集合中删除掉
list.remove(this);
broadCast("Server:"+user+"已离开!");
}
public void closeEverything(Socket socket, BufferedWriter writer, BufferedReader reader){
remove();
if(socket!=null && writer!=null && reader!=null){
try {
writer.close();
reader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Step 3: Create Client Classes
Last but not least, the Client class is our user class. Here, the major point that we need to remember is that the sending and receiving methods should be separated. Meanwhile, the sending and receiving methods have their own thread in one client.
public class Client {
private Socket socket;
private BufferedReader bufferedReader;
private BufferedWriter bufferedWriter;
private String user;
public Client(Socket socket, String user){
this.socket = socket;
this.user =user;
try {
this.bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (IOException e) {
closeEverything(socket,bufferedWriter,bufferedReader);
e.printStackTrace();
}
}
public void sendMessage(){
//发出信息有单独的线程
try {
bufferedWriter.write(user);
bufferedWriter.newLine();
bufferedWriter.flush();
Scanner scanner = new Scanner(System.in);
while (!socket.isClosed()){
String message = scanner.nextLine();
bufferedWriter.write(user+":"+message);
bufferedWriter.newLine();
bufferedWriter.flush();
}
} catch (IOException e) {
closeEverything(socket,bufferedWriter,bufferedReader);
e.printStackTrace();
}
}
public void listen(){
//接收信息也有单独的线程
new Thread( new Runnable() {
@Override
public void run() {
String message;
try {
while(socket.isConnected()) {
message = bufferedReader.readLine();
System.out.println(message);
}
} catch (IOException e) {
closeEverything(socket,bufferedWriter,bufferedReader);
e.printStackTrace();}
}
}).start();
}
public void closeEverything(Socket socket, BufferedWriter writer, BufferedReader reader){
if(socket!=null && writer!=null && reader!=null){
try {
socket.close();
writer.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你的用户名:");
String user = scanner.nextLine();
try {
Socket socket = new Socket("127.0.0.1",8848);
Client client = new Client(socket,user);
client.listen();
client.sendMessage();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Conclusion
There are two critical points we need to focus on for this project:
1. The ClientHandle class should has its own thread while each Client class should have separated sending and listening method threads.
2.All ClientHandle classes should share one static list.