0

    JAVA简单聊天室的实现

    2023.07.21 | admin | 162次围观

    鉴于之前有不少同学在跟我要客户端的代码,我近期整理了一下,把整个工程都传到github上了。地址:

    里面有比较详细的工程运行教程qq群聊对话生成器在线制作,这篇博客则主要对工程的代码实现进行介绍,没有通信知识基础的同学,在看这篇博客之前可以先看下我通信板块的另外几篇博客:

    《JAVA通信(一)——输入数据到客户端》

    《JAVA通信(二)——实现客户机和服务器通信》

    《JAVA通信(三)——实现多人聊天

    前面我们已经了解了通信技术的基本原理,也通过多线程实现了一个服务器同时与多个客户机通信的程序。今天我们来实现一个简单的聊天室。也就是当有客户机给服务器发消息时,这个消息必须同时被发送到其他的客户机。(注意:并不是直接让客户机之间进行连接)

    一、待实现的聊天室构想

    1.我们先来看一下QQ群是怎样运作的。

    首先,用户需要通过验证加入到某一个群;加入之后,每个用户都会有自己的一个聊天室界面,这个界面中实时更新所有群成员发送的消息。

    2.整体框架图

    3.服务器和单一客户机交互图

    A.用户信息正确

    B.用户信息错误

    二、代码架构

    按照我们前面的分析,感觉只需要构建服务器和客户机这两个类就可以实现这个聊天室了。但是这样一来就会造成这两个类中包含了过多的方法,有悖于面向对象的“单一职责原则”。(《面向对象的三大特征和六大基本原则》)不利于我们后期对这个程序进行修改扩展。因此这里我们对这两个类进行了更加仔细的职责划分。总共分成以下五个类。

    JAVA简单聊天室的实现

    ChatServer类:服务器类,也是主类,里面包含服务器的创建方法setUpServer(int port)和主函数入口main。当程序开始运行时,它会把相应的端口port设置为服务器。并让其始终处于待连接状态。每当有客户机连接上来时,就实例化一个线程类(ServerThread)对象,并启动一个线程去处理。(也就相当于我们为每个用户提供了一个独立的线程)。

    ServerThread类:客户端类。它是一个线程类。里面实现了线程的启动方法run()和客户机服务器的通信处理方法processSocket()。当然在通信之前我们必须要先验证这个用户信息是否正确。这个验证方法我们在DaoTool类中实现。这里直接调用它的验证方法即可

    DaoTool:用户信息验证类。里面实现了用户信息的验证方法checkLogin()。并且它还储存了一个模拟的用户信息库userDB。

    UserInfo:用户信息类。里面保存了每一个用户的信息,包括用户名和密码。定义了获取用户名和密码的方法。

    ChatTools:聊天室类。负责保存当前登录的每一个用户,并且当某一个客户机给服务器发了消息,它需要立即把这条消息转发给其他客户机。

    细分后的构图如下:

    三、具体的代码实现

    package communicatetest4;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    public class ChatServer {
    	//主函数入口
    	public static void main(String[] args) throws IOException {
    		//实例化一个服务器类的对象
    		ChatServer cs=new ChatServer();
    		//调用方法,为指定端口创建服务器
    		cs.setUpServer(9000);
    	}
    	private void setUpServer(int port) throws IOException {
    		// TODO Auto-generated method stub
    		ServerSocket server=new ServerSocket(port);
    		//打印出当期创建的服务器端口号
    		System.out.println("服务器创建成功!端口号:"+port);
    		while(true) {
    			//等待连接进入
    			Socket socket=server.accept();
    			System.out.println("进入了一个客户机连接:"+socket.getRemoteSocketAddress().toString());
    			//启动一个线程去处理这个对象
    			ServerThread st=new ServerThread(socket);
    			st.start();
    		}
    	}
    }
    

    package communicatetest4;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.Socket;
    /*
     * 每当有客户机和服务器连接时,都要定义一个接受对象来进行数据的传输
     * 从服务器的角度看,这个类就是客户端
     */
    public class ServerThread extends Thread{
    	private Socket client;//线程中的处理对象
    	private OutputStream ous;//输出流对象
    	private UserInfo user;//用户信息对象
    	
    	public ServerThread(Socket client) {
    		this.client=client;
    	}
    	
    	public UserInfo getOwerUser() {
    		return this.user;
    	}
    	
    	public void run() {
    		try {
    			processSocket();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	//在显示屏中打印信息,例如"用户名"、"密码"等等
    	public void sendMsg2Me(String msg) throws IOException {
    		msg+="\r\n";
    		ous.write(msg.getBytes());
    		ous.flush();
    	}
    	
    	
    	private void processSocket() throws IOException {
    		// TODO Auto-generated method stub
    		InputStream ins=client.getInputStream();
    		ous=client.getOutputStream();
    		BufferedReader brd=new BufferedReader(new InputStreamReader(ins));
    		
    		sendMsg2Me("欢迎你来聊天,请输入你的用户名:");
    		String userName=brd.readLine();
    		sendMsg2Me("请输入密码:");
    		String pwd=brd.readLine();
    		user=new UserInfo();
    		user.setName(userName);
    		user.setPassword(pwd);
    		//调用数据库,验证用户是否存在
    		boolean loginState=DaoTools.checkLogin(user);
    		if(!loginState) {
    			//如果不存在这个账号则关闭
    			this.closeMe();
    			return;
    		}
    		ChatTools.addClient(this);//认证成功,把这个用户加入服务器队列
    		String input=brd.readLine();
    		while(!input.equals("bye")) {
    			System.out.println("服务器读到的是:"+input);
    			ChatTools.castMsg(this.user, input);
    			input=brd.readLine();
    		}
    		ChatTools.castMsg(this.user, "bye");
    		this.closeMe();
    	}
    	
    	//关闭当前客户机与服务器的连接。
    	public void closeMe() throws IOException {
    		client.close();
    	}
    	
    	
    }
    

    package communicatetest4;
    import java.io.IOException;
    import java.util.ArrayList;
    /*
     * 定义一个管理类,相当于一个中介,处理线程,转发消息
     * 这个只提供方法调用,不需要实例化对象,因此都是静态方法
     */
    public class ChatTools {
    	//保存线程处理的对象
    	private static ArrayList stList=new ArrayList();
    	//不需要实例化类,因此构造器为私有
    	private ChatTools() {}
    	
    	//将一个客户对应的线程处理对象加入到队列中
    	public static void addClient(ServerThread st) throws IOException {
    		stList.add(st);//将这个线程处理对象加入到队列中
    		castMsg(st.getOwerUser(),"我上线了!目前人数:"+stList.size());
    	}
    	
    	//发送消息给其他用户
    	public static void castMsg(UserInfo sender,String msg) throws IOException {
    		msg=sender.getName()+"说:"+msg;//加上说的对象
    		for(int i=0;i

    package communicatetest4;
    import java.util.HashMap;
    import java.util.Map;
    //定义一个处理用户登录信息的类
    public class DaoTools {
    	//内存用户信息数据库
    	private static MapuserDB=new HashMap();
    	//静态块:模拟生成内存中的用户数据,用户名为1~10
    	//当程序启动时这段代码会自动执行向userDB放入数据
    	static {
    		for(int i=1;i<=10;i++) {
    			UserInfo user=new UserInfo();
    			user.setName("user"+i);
    			user.setPassword("psw"+i);
    			userDB.put(user.getName(), user);
    		}
    	}
    	
    	public static boolean checkLogin(UserInfo user) {
    		//在只验证用户名是否存在
    		if(userDB.containsKey(user.getName())) {
    			return true;
    		}
    		System.out.println(user.getName()+"用户验证失败!");
    		return false;
    	}
    }
    

    package communicatetest4;
    //定义一个用户信息的类
    public class UserInfo {
    	private String name;//用户名
    	private String password;//密码
    	private String loignTime;//登录时间
    	private String address;//客户机端口名
    	
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		// TODO Auto-generated method stub
    		this.name=name;
    	}
    	
    	public void setPassword(String psw) {
    		this.password=password;
    	}
    }
    

    四、程序实现图

    五、小总结

    1.服务器和客户机的连接。其实在验证用户名和密码之前,客户机就已经和服务器连接上了。如果没有连接,我们是无法把用户名和密码送给服务器进行验证的。只是当我们验证完用户信息后,如果发现这个用户名不存在或者密码不正确时,再去断开客户机和服务器的连接。

    2.ChatTools里面都是静态属性和静态方法,因为我们不需要实例化这个类的对象,我们只想要调用它的方法,对静态方法不了解的同学可以看一下我的另一篇博客(《静态方法和非静态方法的区别JAVA》)

    3、这种基于BIO模型所写出来的服务器性能比较有限,最大并发数大约在2000到2300左右个客户端。后期我进一步提升了服务器的性能,详情可见博客(《C10k破局(一)——线程池和消息队列实现高并发服务器》)。当然使用线程池只是在BIO模型的基础上做了一定的优化,真想要要大幅度地提升服务器性能就只能使用NIO或者AIO模型进行重构qq群聊对话生成器在线制作,有兴趣的小伙伴可以看下我的另一篇博客(《基于netty NIO开发的聊天室》)

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论