Java多用户通信系统
多用户通信系统总结
使用面向对象、多线程、网络编程,基于终端完成一个多用户通信系统。
完成功能
1、用户登录、退出
2、获取所有在线用户列表
3、私聊
4、群发消息
5、发送文件
6、私聊对方离线时,系统缓存消息,对方上线时发送
项目结构总结
使用了User类来表示用户登录时的信息
使用了Message类来表示客户端和服务器端之间发送的所有信息
客户端完成连接时,创建一个线程持续接收服务器的信息
服务器端完成连接时,创建一个线程持续接收客户端的信息,并处理客户端发送的信息
代码实现
Message类,其中包含了所有客户端和服务器端之间发送消息所需要的所有信息
import java.io.Serializable;
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String sender;
private String recevier;
private String content;
private String sendTime;
private String messageType;
private byte[] bytes;
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public Message() {
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getRecevier() {
return recevier;
}
public void setRecevier(String recevier) {
this.recevier = recevier;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getMessageType() {
return messageType;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
public Message(String sender, String recevier, String content, String sendTime, String messageType) {
this.sender = sender;
this.recevier = recevier;
this.content = content;
this.sendTime = sendTime;
this.messageType = messageType;
}
}
MessageType类,这个类用于定义所有消息的类型,方便客户端和服务器端根据消息类型进行对应的操作
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED = "1"; // 成功登录
String MESSAGE_LOGIN_FAIL = "2"; // 失败登录
String MESSAGE_CHAT = "3"; // 聊天信息
String MESSAGE_GET_ONLINELIST = "4"; // 获取在线用户列表
String MESSAGE_RET_ONLINELIST = "5"; // 返回在线用户列表
String MESSAGE_LOG_OUT = "6"; // 退出系统
String MESSAGE_TOALL = "7"; //群发消息
String MESSAGE_FILE = "8"; // 发送文件
}
User类,这个类包含了用户登录时用于验证账户所需要的信息
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private String passwd;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public User(String userId, String passwd) {
this.userId = userId;
this.passwd = passwd;
}
}
这几个类被客户端和服务器端共享
客户端代码
ClientView类,这个类定义了客户端的用户界面,完成和用户之间的交互操作
import java.util.Scanner;
public class ClientView {
private boolean loop = true;
private String input;
ConnectService connectService = new ConnectService();
public static void main(String[] args) {
new ClientView();
}
public ClientView(){
Scanner scanner = new Scanner(System.in);
while(loop){
System.out.println("================用户登录界面=================");
System.out.println("\t\t1. 登录");
System.out.println("\t\t2. 退出");
System.out.println("===========================================");
System.out.println("输入:");
input = scanner.next();
switch (input){
case "1":
String name;
String passwd;
System.out.print("请输入您的用户名:");
name = scanner.next();
System.out.print("请输入您的密码:");
passwd = scanner.next();
if(connectService.checkSign(name,passwd)){
System.out.println("***********************************************");
System.out.println("++++++++++++恭喜" + name + "登陆成功捏!++++++++++");
System.out.println("***********************************************");
while(loop){
System.out.println("***************您想干些什么捏?^_^");
System.out.println("\t\t1. 查看所有在线用户");
System.out.println("\t\t2. 私聊");
System.out.println("\t\t3. 群发");
System.out.println("\t\t4. 发个文件玩玩");
System.out.println("\t\t9. 累了,退了");
System.out.println("请告诉奴家您想要做的事:");
input = scanner.next();
switch (input){
case "1":
//System.out.println("\t\t1. 查看所有在线用户");
connectService.getUserList(name);
break;
case "2":
System.out.println("您想要跟谁说悄悄话呢?");
String getter = scanner.next();
System.out.println("您想要跟他说些什么呢?");
String content = scanner.next();
connectService.privateChat(name,getter,content);
break;
case "3":
System.out.println("您想要对其他人说些什么呀?");
String content1 = scanner.next();
connectService.toAll(name,content1);
break;
case "4":
System.out.println("您想发给哪位友人呢?");
String getterForFile = scanner.next();
System.out.println("您想发些什么有意思的小东西(路径)呀?");
String fileSrc = scanner.next();
System.out.println("您想发给对方的那个位置呀?");
String fileDes = scanner.next();
connectService.sendFile(name,getterForFile,fileSrc,fileDes);
break;
case "9":
System.out.println("主人辛苦啦~期待您的下次使用哟(*❦ω❦)");
connectService.logOut(name);
loop = false;
System.exit(0); // 正常退出进程
break;
}
}
}else {
System.out.println("亲亲,登录失败了呢,请重试一下吧~");
}
break;
case "2":
loop = false;
break;
}
}
}
}
ConnectionThread类,这个类定义了客户端的线程,用于帮助客户端接收并处理服务器端发送来的消息
import java.io.*;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class ConnectionThread extends Thread{
private Socket socket;
private boolean loop = true;
public ConnectionThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
while(loop){
// 循环读取服务器端的消息
InputStream inputStream = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Message message = (Message) objectInputStream.readObject();
switch (message.getMessageType()){
case MessageType.MESSAGE_FILE:
String senderFile = message.getSender();
byte[] bytes = message.getBytes();
System.out.println("叮咚,主人,收到来自" + senderFile + "发给您的一个小文件呢!");
System.out.println("吾已经帮您暂存到" + message.getContent() + "了,您注意查收呦(*^▽^*)");
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(message.getContent()));
outputStream.write(bytes);
//outputStream.flush();
outputStream.close();
break;
case MessageType.MESSAGE_RET_ONLINELIST :
// 获取到在线用户列表
if(message.getContent().equals("")){
System.out.println("主人,暂时没有其他在线用户呢~");
}else{
List<String> userIds = Arrays.stream(message.getContent().split(" ")).toList();
System.out.println("亲亲,这是您请求的在线列表捏~(づ ̄3 ̄)づ╭❤~");
for(int i = 0;i < userIds.size();++i){
System.out.println(i + "[ " + userIds.get(i) +" ]");
}
}
System.out.println("继续告诉吾您想要些什么吧~(#^.^#)");
break;
case MessageType.MESSAGE_CHAT:
String sender = message.getSender();
String content = message.getContent();
System.out.println("叮咚,主人,收到来自" + sender + "的一条信息呢!");
System.out.println("他对主人您说: " + content);
// System.out.println("1-查看 \t 2-忽略");
// Scanner scanner = new Scanner(System.in);
// String input = scanner.next();
// switch (input){
// case "1":
// System.out.println(sender + ": " + content);
// break;
// case "2":
// break;
// default:
// break;
// }
break;
default:
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
}
}
}
ConnectionThreadHashMap类,这个类用于收纳所有客户端的线程,方便客户端后续的功能扩展
import java.util.HashMap;
public class ConnectionThreadHashMap {
private static HashMap<String,ConnectionThread> hm = new HashMap<>();
public static void put(String name,ConnectionThread connectionThread){
hm.put(name,connectionThread);
}
public static ConnectionThread get(String name){
return hm.get(name);
}
}
ConnectService类,这个类用于客户端的功能实现,完成客户端和服务器端之间的交互
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class ConnectService {
private User user = null;
private Socket socket = null;
// 发送文件
public void sendFile(String sender, String getter, String fileSrc, String fileDes){
byte[] bytes = new byte[(int)(new File(fileSrc).length())];
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(fileSrc));
bufferedInputStream.read(bytes);
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_FILE);
message.setBytes(bytes);
message.setSender(sender);
message.setRecevier(getter);
message.setContent(fileDes);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
// 群发消息
public void toAll(String sender, String content){
Message message = new Message();
message.setSender(sender);
message.setContent(content);
message.setMessageType(MessageType.MESSAGE_TOALL);
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
// 私聊消息
public void privateChat(String sender,String getter,String content){
Message message = new Message();
message.setSender(sender);
message.setRecevier(getter);
message.setContent(content);
message.setMessageType(MessageType.MESSAGE_CHAT);
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
// 登出系统
public void logOut(String name){
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_LOG_OUT);
message.setSender(user.getUserId());
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
// 获取在线用户列表
public void getUserList(String name){
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_GET_ONLINELIST);
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
public boolean checkSign(String name, String passwd){
user = new User(name,passwd);
boolean ret = false;
try {
// 和服务器进行登录判断
socket = new Socket(InetAddress.getByName("127.0.0.1"),6666);
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(user);
InputStream inputStream = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Message message = (Message) objectInputStream.readObject();
// System.out.println("回收到服务器的一条回复:" + message.getMessageType());
// 登录成功
if(message.getMessageType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)){
//System.out.println("获取到可以登陆许可");
ConnectionThread connectionThread = new ConnectionThread(socket);
connectionThread.start();
ConnectionThreadHashMap.put(name,connectionThread);
//System.out.println("可以执行完成put");
ret = true;
}else{
socket.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
return ret;
}
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
服务器端
ConnectionThread类,用于帮助服务器端接收并处理来自客户端的消息
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Iterator;
import java.util.Set;
public class ConnectionThread extends Thread{
private Socket socket;
private String userId;
private boolean loop = true;
@Override
public void run() {
try {
while(loop){
// 循环读取客户端的消息
InputStream inputStream = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Message message = (Message) objectInputStream.readObject();
switch (message.getMessageType()){
case MessageType.MESSAGE_FILE:
System.out.println(message.getSender() + "想跟" +
message.getRecevier() + "发小文件");
System.out.println(message.getContent());
Message messageFile = new Message();
messageFile .setMessageType(MessageType.MESSAGE_FILE);
messageFile .setRecevier(message.getRecevier());
messageFile .setSender(message.getSender());
messageFile.setBytes(message.getBytes());
messageFile.setContent(message.getContent());
try {
Socket tos = ConnectionThreadHashMap.get(messageFile .getRecevier()).getSocket();
ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(tos.getOutputStream());
objectOutputStream2.writeObject(messageFile );
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
break;
case MessageType.MESSAGE_GET_ONLINELIST :
String ret = "";
Set<String> strings = ConnectionThreadHashMap.hm.keySet();
for(String s : strings){
if(userId.equals(s)) continue;
ret += s + " ";
}
Message message1 = new Message();
message1.setMessageType(MessageType.MESSAGE_RET_ONLINELIST);
if(ret.equals("")){
message1.setContent(ret);
}else{
message1.setContent(ret.substring(0,ret.length() - 1));
}
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message1);
break;
case MessageType.MESSAGE_LOG_OUT:
System.out.println(userId + "登出系统");
socket.close();
ConnectionThreadHashMap.remove(userId);
loop = false;
break;
case MessageType.MESSAGE_CHAT:
if(!ConnectionThreadHashMap.hm.containsKey(message.getRecevier())){
OutLineSender.putMessage(message);
continue;
}
System.out.println(message.getSender() + "想跟" +
message.getRecevier() + "说: " + message.getContent());
Message message2 = new Message();
message2.setMessageType(MessageType.MESSAGE_CHAT);
message2.setRecevier(message.getRecevier());
message2.setSender(message.getSender());
message2.setContent(message.getContent());
try {
Socket tos = ConnectionThreadHashMap.get(message2.getRecevier()).getSocket();
ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(tos.getOutputStream());
objectOutputStream2.writeObject(message2);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
break;
case MessageType.MESSAGE_TOALL:
System.out.println(message.getSender() + "想跟所有人" + "说: " + message.getContent());
Message message3 = new Message();
message3.setMessageType(MessageType.MESSAGE_CHAT);
message3.setSender(message.getSender());
message3.setContent(message.getContent());
Iterator<ConnectionThread> iterator = ConnectionThreadHashMap.hm.values().iterator();
while(iterator.hasNext()){
ConnectionThread connectionThread = iterator.next();
if(connectionThread.userId.equals(message.getSender())) continue;
Socket socket1 = connectionThread.getSocket();
try {
ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(socket1.getOutputStream());
objectOutputStream2.writeObject(message3);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
break;
default:
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
}
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public ConnectionThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
public ConnectionThread() {
}
}
ConnectionThreadHashMap类,用于收纳和查询所有服务器端的线程
import java.util.HashMap;
public class ConnectionThreadHashMap {
public static HashMap<String,ConnectionThread> hm = new HashMap<>();
public static void put(String name, ConnectionThread connectionThread){
hm.put(name,connectionThread);
}
public static ConnectionThread get(String name){
return hm.get(name);
}
public static void remove(String name){
hm.remove(name);
}
}
OutLineSender类,用于完成离线消息的暂存与发送
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class OutLineSender extends Thread{
private ConcurrentHashMap<String,String> allUser;
private HashMap<String,ConnectionThread> onlineUser;
private static Vector<Message> messages = new Vector<>();
private ArrayList<Message> readyMessages = new ArrayList<>();
public static void putMessage(Message message){
synchronized (messages){messages.add(message);}
}
@Override
public void run() {
while(true){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (messages){
if(!messages.isEmpty()){
for(Iterator<Message> it = messages.iterator();it.hasNext();){
Message message = it.next();
if(onlineUser.containsKey(message.getRecevier())){
readyMessages.add(message);
System.out.println(message.getSender() + "想跟" +
message.getRecevier() + "说: " + message.getContent());
try {
Socket tos = ConnectionThreadHashMap.get(message.getRecevier()).getSocket();
ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(tos.getOutputStream());
objectOutputStream2.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
}
for(Iterator<Message> it = readyMessages.iterator();it.hasNext();){
messages.remove(it.next());
}
readyMessages.clear();
}
}
}
}
public OutLineSender() {
}
public OutLineSender(ConcurrentHashMap<String, String> allUser, HashMap<String, ConnectionThread> onlineUser) {
this.allUser = allUser;
this.onlineUser = onlineUser;
}
public ConcurrentHashMap<String, String> getAllUser() {
return allUser;
}
public void setAllUser(ConcurrentHashMap<String, String> allUser) {
this.allUser = allUser;
}
public HashMap<String, ConnectionThread> getOnlineUser() {
return onlineUser;
}
public void setOnlineUser(HashMap<String, ConnectionThread> onlineUser) {
this.onlineUser = onlineUser;
}
}
ServerService类,用于接收客户端的连接请求,并且在登录验证通过后,创建一个线程完成后续服务器端和客户端之间的通信
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
public class ServerService {
public static void main(String[] args) {
new ServerService();
}
ServerSocket serverSocket;
private boolean loop = true;
private static ConcurrentHashMap<String,String> hm = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, String> getHm() {
return hm;
}
public static void setHm(ConcurrentHashMap<String, String> hm) {
ServerService.hm = hm;
}
static {
hm.put("1", "123");
hm.put("2", "123456");
hm.put("人才", "123456");
hm.put("萧炎", "666666");
hm.put("陈平安", "888888");
}
boolean checkLoginIn(String name,String passwd){
if(hm.get(name) == null || !hm.get(name).equals(passwd)){
return false;
}else{
return true;
}
}
ServerService(){
try {
serverSocket = new ServerSocket(6666);
OutLineSender outLineSender = new OutLineSender();
outLineSender.setAllUser(hm);
outLineSender.setOnlineUser(ConnectionThreadHashMap.hm);
outLineSender.start();
// 循环接收客户端连接
while (loop){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
User user = (User) objectInputStream.readObject();
Message message = new Message();
// 检验客户端提交的用户名和密码
// 假设这种状况验证成功
if(checkLoginIn(user.getUserId(), user.getPasswd())){
System.out.println("收到一条登录,但令其成功:" + user.getUserId());
message.setMessageType(MessageType.MESSAGE_LOGIN_SUCCEED);
objectOutputStream.writeObject(message);
ConnectionThread connectionThread = new ConnectionThread(socket,user.getUserId());
connectionThread.start();
ConnectionThreadHashMap.put(user.getUserId(),connectionThread);
}else{
System.out.println("收到一条登录,但令其失败:" + user.getUserId());
message.setMessageType(MessageType.MESSAGE_LOGIN_FAIL);
objectOutputStream.writeObject(message);
socket.close();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
serverSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
项目源于视频:https://www.bilibili.com/video/BV1fh411y7R8?spm_id_from=333.788.videopod.episodes&vd_source=16bf0c507e4a78c3ca31a05dff1bee4e&p=686