Java full duplex chat server (and client)
Article directory
Full code at bottom
Design thinking
When it comes to chat tools, people have to think of QQ. So what process did we go through when we chatted on QQ?
Sign in
Everyone has their own account password, according to which they can log in to QQ server. So our chat server must also have this process. Therefore, we need a container to store the user account password in the server, and we also need to implement a user authentication module.
Find chat object
The server should have a function: that is, to tell each client who is currently online, so as to keep the information can be delivered. So we need to implement some commands so that the server can send some additional information to the client in addition to forwarding information.
Send message
When we click send, the information is actually sent to the server, and then it is forwarded according to the additional information in the information. So we need to solve the problem of how to add additional information to information and how to parse it.
In addition, there is another problem to consider: for the information to be forwarded, whether the server forwards it immediately or saves it and polls it first. Here I use message queue, that is, the server first saves the received messages to the queue, and polls the queue for sending every other period of time.
summary
In order to realize full duplex chat server, we need to:
- Information processing module
- Customer authentication module (server)
- Online user module (server)
- Information transceiver module
Information processing module
Most of the information is sent by the client to the server for processing, so the information sent should have additional instructions. For this full duplex chat server development, the information data format is set as:
(receiver) @ - (information subject) @ - (sender)
For example, if Jack wants to send the message "I am Jack" to Nancy through the client, the string received by the server is "Nancy@-I am Jack@-Jack". Then, the server will verify and analyze the string: the receiver is Nancy, the content is I am Jack, and the sender is Jack. After judging that the string has complete meaning and analyzing, put the message into the message queue and wait for sending.
Message queue
In order to improve the execution efficiency of the server, a message queue is added, which is a separate thread. The function is to send the information to be forwarded.
The workflow is as follows: query this queue every other period of time, if not empty, poll the queue. Use the read information to search. If the Socket corresponding to the user name is found, transfer to the sending and receiving module to send the information. If not found, no response.
The client can send some special information to the server, that is, the message receiver is the server. The server keeps the special string as the message response from the server to the client. As follows:
1.Time: get server time
2.Users: get the list of current online users
3. (to be added )
//Message queue public static List<Message> waitForSend; //Information analysis public void analyseToMessage(String data) { String toWho=data.split("@-")[0]; String content=data.split("@-")[1]; String who=data.split("@-")[2]; Message message=new Message(toWho, content,who); if ("Server".equals(toWho)) { beats.add(message); return; } waitForSend.add(message); } public void analyseToMessage(Message m) { waitForSend.add(m); }
Customer verification module
Trigger this module every time the user uses the client to connect to the server.
The function is to verify the currently connected client. Since there is no password system, only verify whether the user name entered by the user is the same as the user name on the current online user list. If the name is the same, an error message is returned and the user name is required to be re entered.
It can be expanded here: for example, joining the password system. In this way, when the user logs in for the first time, the registration program will be carried out, and the user name and password entered by the client will be stored in the database. The next time the user logs in, enter the user name and password directly. You can also save to a txt file without using a database. But for security and efficiency, it's better to use a database.
In addition, especially for the customer authentication module and the information transceiver module, it has the right to modify this module. After the customer verification is successful, the relevant method will be called to add the user to this module. The heartbeat module under the information receiving and sending module will delete the client that fails the heartbeat detection by calling this module.
static class Authorized implements Runnable{ Socket s; public Authorized(Socket s){ this.s=s; } @Override public void run() { try { while (true) { Writer writer = new OutputStreamWriter(s.getOutputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); writer.write("Please Input Your Name:\n"); writer.flush(); String name; while (true) { if ((name = reader.readLine()) != null) { break; } } if(serverHandler.getOnlineUsers().isRepeat(name)) { writer.write("User exists,please rename\n"); writer.flush(); continue; } else { serverHandler.getOnlineUsers().addUser(s,name); Message m=new Message(name,"Hello","Server"); serverHandler.analyseToMessage(m); break; } } } catch (IOException e) { e.printStackTrace(); } } } static class CheckBeats implements Runnable{ int time=0; public CheckBeats(int delayTime){ time=delayTime; } @Override public void run() { while (true){ try { Thread.sleep(time); for (int i=0;i<serverHandler.getOnlineUsers().getOnlineUsers().size();i++){ for(Message m:serverHandler.getBeats()){ if(serverHandler.getOnlineUsers().getOnlineUserList().contains(m.getWho())){ continue; } else { serverHandler.getOnlineUsers().userLogout(serverHandler.getOnlineUsers().getOnlineUsers().get(i).getName()); } } } serverHandler.initialBeats(); } catch (InterruptedException | IOException e) { e.printStackTrace(); } } } }
Information transceiver module
This module exists in both the client and the server and is similar.
Its main performance is Send and receive two threads, the role of which is to provide the service of sending and receiving information. For the server, the received information will be delivered to the information processing module for processing, and the sent information will be received by the target client. For the client: the received message will appear in the user's eyes, and the sent message will be received by the server.
static class Send implements Runnable{ @Override public void run() { while(true) { if(ServerHandler.waitForSend!=null&&ServerHandler.waitForSend.size()!=0) { try { serverHandler.send(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } static class Recive implements Runnable{ @Override public void run() { while(true) { if(serverHandler.getOnlineUsers().getOnlineUserList().size()!=1) { for(int i=1;i<serverHandler.getOnlineUsers().getOnlineUsers().size();i++) { try { BufferedReader reader=new BufferedReader(new InputStreamReader(serverHandler.getOnlineUsers().getOnlineUsers().get(i).getSocket().getInputStream())); String temp; String data=""; if(reader.ready()&&(data=reader.readLine())!=null) { //Simple judge whether the format of data is correct if(!Message.analyseFormat(data)){ continue; } //Simple judge the user who sent is themselves if(!data.split("@-")[2].equals(serverHandler.getOnlineUsers().getOnlineUsers().get(i).getName())){ continue; } serverHandler.analyseToMessage(data.replaceAll("\n","")); } else { continue; } } catch (IOException e) { System.out.println("Nothing"); } } } try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
Complete code
ServerHandler.java
package zhl.ServerHandler; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.Socket; import java.util.ArrayList; import java.util.List; import zhl.OnlineUser.*; public class ServerHandler { public static OnlineUser onlineUser; public static List<Message> waitForSend; public static List<Message> beats; public ServerHandler() { onlineUser=new OnlineUser(); waitForSend=new ArrayList<Message>(); onlineUser.addUser(new Socket(), "Server"); beats=new ArrayList<Message>(); beats.add(new Message("Server","alive","Server")); } /** * send message to users * @throws IOException */ public void send() throws IOException { int p=0; Writer writer; for (int i=0;i<waitForSend.size();i++){ Message m=waitForSend.get(p); //When client send message to Server if(m.getToWho().equals("Server")){ System.out.println("["+m.getWho()+"]"+m.getContent()); waitForSend.remove(p); continue; } Socket client= onlineUser.getUser(m.getToWho()); //If do not exist User if(client==null){ waitForSend.add(new Message(m.getWho(),"User not exists\n","Server")); waitForSend.remove(p); continue; } writer=new OutputStreamWriter(client.getOutputStream()); writer.write(m.getContent()); System.out.println("Send: "+m.getToWho()+" "+m.getContent()); writer.flush(); waitForSend.remove(p); } } public void analyseToMessage(String data) { String toWho=data.split("@-")[0]; String content=data.split("@-")[1]; String who=data.split("@-")[2]; Message message=new Message(toWho, content,who); if ("Server".equals(toWho)) { beats.add(message); return; } waitForSend.add(message); } public void analyseToMessage(Message m) { waitForSend.add(m); } public OnlineUser getOnlineUsers() { return onlineUser; } public List<Message> getBeats(){ return beats; } public void initialBeats(){ beats=new ArrayList<Message>(); beats.add(new Message("Server","alive","Server")); } }
Message.java
package zhl.OnlineUser; public class Message { private String toWho; private String content; private String who; public Message(String toWho,String content,String who) { this.toWho=toWho; this.content=content+"\n"; this.who=who; } public static boolean analyseFormat(String data){ int sl=3; return data.split("@-").length == sl; } public String getToWho() { return toWho; } public String getContent() { return content; } public void setToWho(String toWho) { this.toWho=toWho; } public void setContent(String content) { this.content=content; } public void setWho(String who) { this.who=who; } public String getWho() { return who; } }
OnlineUser.java
package zhl.OnlineUser; import java.io.IOException; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class OnlineUser { private List<User> onlineUser; int ordinal=0; public OnlineUser() { onlineUser=new ArrayList<User>(); } public void addUser(Socket s,String name) { User u=new User(s, name); onlineUser.add(u); System.out.println(name+" Online"); } public void userLogout(String name) throws IOException { for(int i=0;i<onlineUser.size();i++) { if(onlineUser.get(i).getName().equals(name)) { onlineUser.get(i).getSocket().close(); onlineUser.remove(i); break; } } } public Socket getUser(String name) throws IOException { for (User user : onlineUser) { if (user.getName().equals(name)) { return user.getSocket(); } } return null; } public List<String> getOnlineUserList(){ List<String> nameList=new ArrayList<String>(); for (User user : onlineUser) { nameList.add(user.getName()); } return nameList; } public List<User> getOnlineUsers(){ return onlineUser; } public boolean isRepeat(String name) throws IOException { for(User u:onlineUser){ if(u.getName().equals(name)) { return true; } } return false; } }
User.java
package zhl.OnlineUser; import java.net.Socket; public class User { private String name; private Socket s; public User(Socket s,String name) { this.name=name; this.s=s; } public String getName() { return name; } public Socket getSocket() { return s; } }
Server.java
package zhl.Server; import java.awt.*; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import zhl.OnlineUser.Message; import zhl.ServerHandler.ServerHandler; /** * @author zhl */ public class Server { private static ServerHandler serverHandler; private ServerSocket serverSocket; private Server() { //Only once serverHandler=new ServerHandler(); } private void startAccept() throws IOException { try { serverSocket=new ServerSocket(4755); Send send= new Send(); Recive recive= new Recive(); CheckBeats cb=new CheckBeats(6000); ExecutorService cached= Executors.newCachedThreadPool(); cached.execute(send); cached.execute(recive); cached.execute(cb); //Start Listening while(true) { Socket socket= serverSocket.accept(); Authorized authorized= new Authorized(socket); cached.execute(authorized); } } finally { serverSocket.close(); } } /** * For Test * @throws IOException * */ public static void main(String[] args) throws IOException { System.out.println("Launching"); Server server=new Server(); System.out.println("Listening"); server.startAccept(); } static class Send implements Runnable{ @Override public void run() { while(true) { if(ServerHandler.waitForSend!=null&&ServerHandler.waitForSend.size()!=0) { try { serverHandler.send(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } static class Recive implements Runnable{ @Override public void run() { while(true) { if(serverHandler.getOnlineUsers().getOnlineUserList().size()!=1) { for(int i=1;i<serverHandler.getOnlineUsers().getOnlineUsers().size();i++) { try { BufferedReader reader=new BufferedReader(new InputStreamReader(serverHandler.getOnlineUsers().getOnlineUsers().get(i).getSocket().getInputStream())); String temp; String data=""; if(reader.ready()&&(data=reader.readLine())!=null) { //Simple judge whether the format of data is correct if(!Message.analyseFormat(data)){ continue; } //Simple judge the user who sent is themselves if(!data.split("@-")[2].equals(serverHandler.getOnlineUsers().getOnlineUsers().get(i).getName())){ continue; } serverHandler.analyseToMessage(data.replaceAll("\n","")); } else { continue; } } catch (IOException e) { System.out.println("Nothing"); } } } try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } static class Authorized implements Runnable{ Socket s; public Authorized(Socket s){ this.s=s; } @Override public void run() { try { while (true) { Writer writer = new OutputStreamWriter(s.getOutputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); writer.write("Please Input Your Name:\n"); writer.flush(); String name; while (true) { if ((name = reader.readLine()) != null) { break; } } if(serverHandler.getOnlineUsers().isRepeat(name)) { writer.write("User exists,please rename\n"); writer.flush(); continue; } else { serverHandler.getOnlineUsers().addUser(s,name); Message m=new Message(name,"Hello","Server"); serverHandler.analyseToMessage(m); break; } } } catch (IOException e) { e.printStackTrace(); } } } static class CheckBeats implements Runnable{ int time=0; public CheckBeats(int delayTime){ time=delayTime; } @Override public void run() { while (true){ try { Thread.sleep(time); for (int i=0;i<serverHandler.getOnlineUsers().getOnlineUsers().size();i++){ for(Message m:serverHandler.getBeats()){ if(serverHandler.getOnlineUsers().getOnlineUserList().contains(m.getWho())){ continue; } else { serverHandler.getOnlineUsers().userLogout(serverHandler.getOnlineUsers().getOnlineUsers().get(i).getName()); } } } serverHandler.initialBeats(); } catch (InterruptedException | IOException e) { e.printStackTrace(); } } } } }
Client.java
package zhl.Server; import java.io.*; import java.net.*; import java.util.Scanner; public class Client { static Socket client; public static void main(String[] args) { try { client=new Socket("127.0.0.1",4755); //client=new Socket("47.92.165.73",4755); Recive recive=new Recive(); Send send=new Send(); Thread sed =new Thread(send); Thread rec =new Thread(recive); rec.start(); sed .start(); } catch (IOException e) { //System.out.println(e.getMessage()); } } static class Recive implements Runnable{ BufferedReader reader; Recive() throws IOException { reader= new BufferedReader(new InputStreamReader(client.getInputStream())); } @Override public void run() { String temp=null; String data=""; while(true) { try { if ((temp=reader.readLine())!=null) { data+=temp; } } catch (IOException e) { e.printStackTrace(); } if(data!="") { System.out.println(data); } data=""; } } } static class Send implements Runnable{ @Override public void run() { while (true){ try { Writer writer=new OutputStreamWriter(client.getOutputStream()); BufferedReader reader=new BufferedReader(new InputStreamReader(System.in)); writer.write(reader.readLine()+"\n"); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } } } }