多线程TCP网络即时通讯项目
1、需求:
2、界面设计:
3、功能说明:
①.用户登录:
②.拉取在线用户列表:
③.无异常退出:
- 客户端结束线程,并向服务端发送message,关闭其在服务端所对应的线程,并释放资源
④.私聊:
⑤.群聊:
⑥.服务端推送消息:
⑦.离线留言:
⑧.离线发送文件:
4、客户端项目结构:
5、客户端项目代码:
①.ClientConnectThread
package qqclient.service;
import qqcommon.Message;
import qqcommon.MessageType;
import java.io.*;
import java.net.Socket;
public class ClientConnectThread extends Thread {
private Socket socket;
public ClientConnectThread(Socket socket) {
this.socket = socket;
}
public Socket getSocket() {
return socket;
}
@Override
public void run() {
while (true) {
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
switch (message.getMesType()) {
case MessageType.MESSAGE_RET_ONLINE_FRIEND://获取服务端返回的在线用户列表
String[] s = message.getContent().split(" ");
if (s.length > 0) {
System.out.println("\n===当前在线用户列表====");
for (int i = 0; i < s.length; i++) {
System.out.println("用户" + i + ": " + s[i]);
}
}
break;
case MessageType.MESSAGE_TO_ONE_MES://从服务端接收到私聊信息
System.out.println("\n用户" + message.getSender() + "对你说:" + message.getContent());
break;
case MessageType.MESSAGE_TO_ALL_MES://从服务端接收到群聊消息
System.out.println("\n用户" + message.getSender() + "群发了:" + message.getContent());
break;
case MessageType.MESSAGE_FILE_TO_ONE_MES://从服务端接收到文件消息
System.out.println("\n用户" + message.getSender() + "向你发送了文件:" + message.getFileName() + ",文件大小为:" + message.getFileSize()+"KB");
FileOutputStream fos = new FileOutputStream(new File(message.getFileDes(),message.getFileName()));
fos.write(message.getFileContent());
System.out.println("文件传输完毕");
fos.close();
break;
case MessageType.MESSAGE_SEVER_MES://服务端推送的消息
System.out.println("\n服务端向你推送了消息:" + message.getContent());
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
②.ManageClientConnectThread
package qqclient.service;
import java.util.concurrent.ConcurrentHashMap;
public class ManageClientConnectThread {
private static ConcurrentHashMap<String, ClientConnectThread> hsm = new ConcurrentHashMap<>(16);
public static void addClientConnectThread(String userid, ClientConnectThread clientThread){
hsm.put(userid, clientThread);
}
public static ClientConnectThread getClientConnectThread(String userid){
return hsm.get(userid);
}
public static void removeClientConnectThread(String userid){
hsm.remove(userid);
}
}
③.UserClientService
package qqclient.service;
import qqcommon.Message;
import qqcommon.MessageType;
import qqcommon.User;
import utils.Utility;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class UserClientService {
private Socket socket;
private User user;
public UserClientService() {
}
public void logout() {
try {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(user.getId());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
ManageClientConnectThread.removeClientConnectThread(user.getId());
System.out.println(user.getId() + "在客户端退出");
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
}
}
public void getOnlineUsers() {
try {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
message.setSender(user.getId());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendAllMessage() {
System.out.println("请输入你要群发的消息:");
String allMes = Utility.readString(100);
try {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
message.setSender(user.getId());
message.setContent(allMes);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendOneMessage() {
System.out.print("请输入你要私聊的对象:");
String receiver = Utility.readString(50);
System.out.print("请输入私聊的内容:");
String content = Utility.readString(100);
try {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_TO_ONE_MES);
message.setSender(user.getId());
message.setReceiver(receiver);
message.setContent(content);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendFileToOne() {
System.out.print("请输入你发送文件的对象:");
String receiver = Utility.readString(50);
System.out.print("请输入发送文件的源路径:");
String fileSrcPath = Utility.readString(100);
System.out.print("请输入发送文件的目标路径:");
String fileDesPath = Utility.readString(100);
File file = new File(fileSrcPath);
if (!file.exists()) {
System.out.println("文件不存在");
return;
}
try {
byte[] bytes = new byte[(int) file.length()];
FileInputStream fis = new FileInputStream(file);
int fileSize = fis.read(bytes);
String fileName = fileSrcPath.substring(fileSrcPath.lastIndexOf("\\") + 1);
Message message = new Message();
message.setFileSize(fileSize);
message.setFileSrc(fileSrcPath);
message.setFileDes(fileDesPath);
message.setFileContent(bytes);
message.setFileName(fileName);
message.setMesType(MessageType.MESSAGE_FILE_TO_ONE_MES);
message.setSender(user.getId());
message.setReceiver(receiver);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean login(String userid, String userpsw) {
user = new User(userid, userpsw);
try {
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(user);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
if (MessageType.MESSAGE_LOGIN_SUCCESS.equals(message.getMesType())) {
ClientConnectThread clientConnectThread = new ClientConnectThread(socket);
clientConnectThread.start();
ManageClientConnectThread.addClientConnectThread(userid, clientConnectThread);
return true;
} else {
socket.close();
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
④.QQView
package qqclient.view;
import qqclient.service.UserClientService;
import utils.Utility;
public class QQView {
private boolean loop = true;
private UserClientService userClientService = new UserClientService();
public void mainMenu() {
while (loop) {
System.out.println("========欢迎登录网络通讯系统========");
System.out.println("\t\t1.登录系统");
System.out.println("\t\t9.退出系统");
System.out.print("请输入你的选择:");
switch (Utility.readString(1)) {
case "1":
System.out.print("请输入账号:");
String userid = Utility.readString(50);
System.out.print("请输入密码:");
String userpsw = Utility.readString(50);
if (userClientService.login(userid, userpsw)) {
System.out.println("========欢迎您, " + userid + " 登录成功========");
while (loop) {
System.out.println("========网络通信系统二级菜单(" + userid + ")========");
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.print("请输入你的选择:");
switch (Utility.readString(1)) {
case "1":
userClientService.getOnlineUsers();
break;
case "2":
userClientService.sendAllMessage();
break;
case "3":
userClientService.sendOneMessage();
break;
case "4":
userClientService.sendFileToOne();
break;
case "9":
userClientService.logout();
loop = false;
break;
}
}
} else {
System.out.println("登录失败");
}
break;
case "9":
System.out.println("退出系统");
loop = false;
break;
}
}
}
}
⑤.Message
package qqcommon;
import java.io.Serializable;
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String sender;
private String receiver;
private String content;
private String time;
private String mesType;
private String fileSrc;
private String fileDes;
private int fileSize;
private String fileName;
private byte[] fileContent;
public byte[] getFileContent() {
return fileContent;
}
public void setFileContent(byte[] fileContent) {
this.fileContent = fileContent;
}
public String getFileSrc() {
return fileSrc;
}
public void setFileSrc(String fileSrc) {
this.fileSrc = fileSrc;
}
public String getFileDes() {
return fileDes;
}
public void setFileDes(String fileDes) {
this.fileDes = fileDes;
}
public int getFileSize() {
return fileSize;
}
public void setFileSize(int fileSize) {
this.fileSize = fileSize;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Message() {
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
}
⑥.MessageType
package qqcommon;
public interface MessageType {
String MESSAGE_LOGIN_SUCCESS = "1";
String MESSAGE_LOGIN_FAIL = "2";
String MESSAGE_TO_ONE_MES = "3";
String MESSAGE_GET_ONLINE_FRIEND = "4";
String MESSAGE_RET_ONLINE_FRIEND = "5";
String MESSAGE_CLIENT_EXIT = "6";
String MESSAGE_TO_ALL_MES = "7";
String MESSAGE_FILE_TO_ONE_MES = "8";
String MESSAGE_SEVER_MES = "9";
}
⑦.User
package qqcommon;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String passWord;
public User(String id, String passWord) {
this.id = id;
this.passWord = passWord;
}
public User() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
⑧.QQFrame
package qqframe;
import qqclient.view.QQView;
public class QQFrame {
public static void main(String[] args) {
new QQView().mainMenu();
}
}
⑨.Utility
package utils;
import java.util.Scanner;
public class Utility {
private static Scanner scanner = new Scanner(System.in);
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false);
c = str.charAt(0);
if (c != '1' && c != '2' &&
c != '3' && c != '4' && c != '5') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}
public static char readChar() {
String str = readKeyBoard(1, false);
return str.charAt(0);
}
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);
return (str.length() == 0) ? defaultValue : str.charAt(0);
}
public static int readInt() {
int n;
for (; ; ) {
String str = readKeyBoard(10, false);
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(10, true);
if (str.equals("")) {
return defaultValue;
}
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("")? defaultValue : str;
}
public static char readConfirmSelection() {
System.out.println("请输入你的选择(Y/N): 请小心选择");
char c;
for (; ; ) {
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
private static String readKeyBoard(int limit, boolean blankReturn) {
String line = "";
while (scanner.hasNextLine()) {
line = scanner.nextLine();
if (line.length() == 0) {
if (blankReturn) return line;
else continue;
}
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
6、服务端项目结构:
7、服务端项目代码:
①.与客户端共有的类省略:
- Message
- MessageType
- User
- Utility
②.QQFrame
package qqframe;
import qqserver.service.QQServer;
public class QQFrame {
public static void main(String[] args) {
new QQServer();
System.out.println("服务端退出");
}
}
③.ManageServerConnectThread
package qqserver.service;
import java.util.concurrent.ConcurrentHashMap;
public class ManageServerConnectThread {
private static ConcurrentHashMap<String, ServerConnectThread> hsm = new ConcurrentHashMap<>(16);
public static ConcurrentHashMap<String, ServerConnectThread> getHsm() {
return hsm;
}
public static void addServerConnectThread(String userid, ServerConnectThread sct){
hsm.put(userid,sct);
}
public static ServerConnectThread getServerConnectThread(String userid){
return hsm.get(userid);
}
public static void removeUserThread(String userid){
hsm.remove(userid);
}
}
④.QQServer
package qqserver.service;
import qqcommon.Message;
import qqcommon.MessageType;
import qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public class QQServer {
private ServerSocket serverSocket = null;
public QQServer() {
try {
serverSocket = new ServerSocket(9999);
System.out.println("服务端启动,监听本机的9999端口");
new SendServerMessageThread().start();
while (true) {
Socket socket = serverSocket.accept();
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
User user = (User) ois.readObject();
Message message = new Message();
if (checkUser(user)) {
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESS);
} else {
message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
}
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
if (MessageType.MESSAGE_LOGIN_SUCCESS.equals(message.getMesType())) {
ServerConnectThread serverConnecThread = new ServerConnectThread(socket);
serverConnecThread.start();
ManageServerConnectThread.addServerConnectThread(user.getId(), serverConnecThread);
}else{
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static ConcurrentHashMap<String, String> users = new ConcurrentHashMap<>(16);
static {
users.put("100", "123456");
users.put("200", "123456");
users.put("300", "123456");
users.put("400", "123456");
users.put("哈哈", "123456");
}
public boolean checkUser(User user) {
if (user == null) {
return false;
} else if (users.get(user.getId()) == null) {
System.out.println("用户:" + user.getId() + " 不存在");
return false;
} else if (!users.get(user.getId()).equals(user.getPassWord())) {
System.out.println("用户:" + user.getId() + " 的密码校验错误");
return false;
}
System.out.println("用户:" + user.getId() + " 登录成功");
return true;
}
public static String getOnlineUser() {
Enumeration<String> userids = ManageServerConnectThread.getHsm().keys();
Iterator<String> it = userids.asIterator();
StringBuilder str = new StringBuilder();
while (it.hasNext()) {
str.append(it.next() + " ");
}
return new String(str);
}
}
⑤.SendServerMessageThread
package qqserver.service;
import qqcommon.Message;
import qqcommon.MessageType;
import utils.Utility;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.Iterator;
public class SendServerMessageThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("是否推送消息(Y/N),输入exit退出消息推送服务:");
String isSendMes = Utility.readString(10);
if ("Y".equals(isSendMes) || "y".equals(isSendMes)) {
System.out.print("请输入服务端要推送的消息内容:");
String content = Utility.readString(100);
Message message1 = new Message();
message1.setMesType(MessageType.MESSAGE_SEVER_MES);
message1.setContent(content);
ObjectOutputStream oos;
Collection<ServerConnectThread> threads = ManageServerConnectThread.getHsm().values();
Iterator<ServerConnectThread> it = threads.iterator();
while (it.hasNext()) {
try {
oos = new ObjectOutputStream(it.next().getSocket().getOutputStream());
oos.writeObject(message1);
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("向" + threads.size() + "个在线用户推送了消息");
} else if ("exit".equals(isSendMes)) {
System.out.println("消息推送服务关闭");
break;
}
}
}
}
⑥.ServerConnectThread
package qqserver.service;
import qqcommon.Message;
import qqcommon.MessageType;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Collection;
import java.util.Iterator;
public class ServerConnectThread extends Thread {
private Socket socket;
public ServerConnectThread(Socket socket) {
this.socket = socket;
}
public Socket getSocket() {
return socket;
}
@Override
public void run() {
boolean loop = true;
while (loop) {
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
Message message1 = new Message();
ObjectOutputStream oos;
switch (message.getMesType()) {
case MessageType.MESSAGE_GET_ONLINE_FRIEND://客户端要在线用户列表
System.out.println("用户 " + message.getSender() + " 要在线用户列表");
message1.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
message1.setContent(QQServer.getOnlineUser());
oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message1);
break;
case MessageType.MESSAGE_TO_ONE_MES://私聊信息
System.out.println("用户 " + message.getSender() + " 向用户" + message.getReceiver() + "私聊了消息:" + message.getContent());
message1.setMesType(MessageType.MESSAGE_TO_ONE_MES);
message1.setContent(message.getContent());
message1.setSender(message.getSender());
message1.setReceiver(message.getReceiver());
ServerConnectThread receiverThread1 = ManageServerConnectThread.getServerConnectThread(message.getReceiver());
if (receiverThread1 == null) {
System.out.println("用户" + message.getReceiver() + " 不存在,私聊失败");
} else {
oos = new ObjectOutputStream(receiverThread1.getSocket().getOutputStream());
oos.writeObject(message1);
}
break;
case MessageType.MESSAGE_TO_ALL_MES://群聊消息
System.out.println("用户 " + message.getSender() + " 其他用户群发了消息:" + message.getContent());
message1.setMesType(MessageType.MESSAGE_TO_ALL_MES);
message1.setSender(message.getSender());
message1.setContent(message.getContent());
Collection<ServerConnectThread> threads = ManageServerConnectThread.getHsm().values();
ServerConnectThread senderThread = ManageServerConnectThread.getServerConnectThread(message.getSender());
Iterator<ServerConnectThread> it = threads.iterator();
ServerConnectThread receiverThread = null;
while (it.hasNext()) {
receiverThread = it.next();
if (receiverThread != senderThread) {
oos = new ObjectOutputStream(receiverThread.getSocket().getOutputStream());
oos.writeObject(message1);
}
}
break;
case MessageType.MESSAGE_FILE_TO_ONE_MES://文件消息
System.out.println("用户" + message.getSender() + " 向 用户" + message.getReceiver() + "发送了文件" + message.getFileName() + ",文件大小为:" + message.getFileSize() + "KB");
message1.setMesType(MessageType.MESSAGE_FILE_TO_ONE_MES);
message1.setFileContent(message.getFileContent());
message1.setFileSrc(message.getFileSrc());
message1.setFileDes(message.getFileDes());
message1.setFileSize(message.getFileSize());
message1.setSender(message.getSender());
message1.setReceiver(message.getReceiver());
message1.setFileName(message.getFileName());
ServerConnectThread receiverThread2 = ManageServerConnectThread.getServerConnectThread(message.getReceiver());
if (receiverThread2 == null) {
System.out.println("用户" + message.getReceiver() + " 不存在,文件发送失败");
} else {
oos = new ObjectOutputStream(receiverThread2.getSocket().getOutputStream());
oos.writeObject(message1);
}
break;
case MessageType.MESSAGE_CLIENT_EXIT://客户端要退出
System.out.println(message.getSender() + "在服务端退出");
ManageServerConnectThread.removeUserThread(message.getSender());
socket.close();
loop = false;
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
B站韩顺平教程 服务端源码:https://gitee.com/Kevin_Hunter/Java-TCP 客户端源码:https://gitee.com/Kevin_Hunter/Java-TCP-Client
|