Java full duplex chat server implementation ideas and code

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:

  1. Information processing module
  2. Customer authentication module (server)
  3. Online user module (server)
  4. 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();
                }
            }
        }
    }
}




19 original articles published, praised 3, 6850 visitors
Private letter follow

Keywords: Java socket Database

Added by barbatruc on Wed, 12 Feb 2020 11:28:00 +0200