JAVA简单聊天室的实现

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

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

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

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

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

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

 

一、待实现的聊天室构想

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

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

2.整体框架图

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

A.用户信息正确

B.用户信息错误

二、代码架构

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

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<ServerThread> 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<stList.size();i++) {
			ServerThread st=stList.get(i);
			st.sendMsg2Me(msg);//发消息给每一个客户机
		}
	}
}
package communicatetest4;

import java.util.HashMap;
import java.util.Map;

//定义一个处理用户登录信息的类
public class DaoTools {
	//内存用户信息数据库
	private static Map<String,UserInfo>userDB=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模型进行重构,有兴趣的小伙伴可以看下我的另一篇博客(《基于netty NIO开发的聊天室》

  • 78
    点赞
  • 574
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
以下是一个简单Java局域网聊天室实现代码,包括服务器端和客户端: 服务器端代码: ``` import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class ChatServer { private List<Socket> clients = new ArrayList<>(); private ServerSocket serverSocket; public ChatServer(int port) { try { serverSocket = new ServerSocket(port); } catch (IOException e) { e.printStackTrace(); } } public void start() { System.out.println("Server is running..."); while (true) { try { Socket client = serverSocket.accept(); clients.add(client); new ServerThread(client).start(); } catch (IOException e) { e.printStackTrace(); } } } private class ServerThread extends Thread { private Socket client; private BufferedReader in; private PrintWriter out; public ServerThread(Socket client) { this.client = client; try { in = new BufferedReader(new InputStreamReader(client.getInputStream())); out = new PrintWriter(client.getOutputStream(), true); } catch (IOException e) { e.printStackTrace(); } } public void run() { String line; try { while ((line = in.readLine()) != null) { System.out.println("Received message: " + line); for (Socket c : clients) { PrintWriter pw = new PrintWriter(c.getOutputStream(), true); pw.println(line); } } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { ChatServer server = new ChatServer(8888); server.start(); } } ``` 客户端代码: ``` import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class ChatClient { private Socket socket; private BufferedReader in; private PrintWriter out; private String host; private int port; public ChatClient(String host, int port) { this.host = host; this.port = port; } public void start() { try { socket = new Socket(host, port); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); new ClientThread().start(); BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = stdin.readLine()) != null) { out.println(line); } } catch (IOException e) { e.printStackTrace(); } } private class ClientThread extends Thread { public void run() { String line; try { while ((line = in.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { ChatClient client = new ChatClient("localhost", 8888); client.start(); } } ``` 需要注意的是,这只是一个简单实现,还需要考虑一些安全和性能问题,比如数据加密、多线程处理等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值