本通信系统可以实现局域网内的用户通信,并且有简单的GUI实现(虽然很丑但是好歹有界面了!!)
先附运行结果图:
(1)启动服务端程序,监听9999端口
(2)设置客户端允许多个实例运行
(3)启动客户端,进入用户登录界面,输入正确的用户名及密码,用户名为admin1,登录成功后发送一个验证消息,服务端验证用户名和密码是否正确,正确则新建一个线程与客户端保持通信,并将该线程加入到线程管理类中。
用户名和密码是用ConcurrentHashMap模拟的数据库
(4)进入二级界面
(5)再启动一个客户端程序,输入正确的用户名和密码,用户名为admin2,同样出现一个二级页面
(6)服务器显示两个客户端连接成功
(7)现在两台客户端可以进行通信,获取在线好友测试,实质是线程管理器返回已经存在的线程。(这里有报错,要点两下才能获取到,我实力不够还没能解决,但是不影响运行哈哈哈)
(8)客户端admin1对admin2发送“早上好”,客户端发送消息到服务器,消息内容包括接收者,发送者,消息类型,消息内容。服务端收到后根据消息类型进行相应的转发。
(9)admin2收到消息,客户端与服务端连接成功后会新建一个与服务端保持通信的线程,该线程识别服务端发来的消息并在“你收到的消息:”界面进行打印输出。
(10)再启动一个客户端,用户名为admin3,测试群聊功能,admin3群聊发送“大家晚上吃什么”,所有在线用户都收到该消息。
(10)测试服务端推送功能,在服务端输入消息“版本更新”,所有用户收到消息,服务端也开启一个线程负责发消息,本质是服务端创建一个消息对象,接收者为所有用户。
(11)正常退出,本质是客户端发送断开连接的消息,服务端收到后主动关闭socket通道,如果贸然退出,服务端socket通道异常关闭,则服务端不能正常运行。测试admin1退出系统,服务端收到退出并打印信息。与admin1的通道关闭。客户端全部退出后,服务端仍能正常运行。
(12)补充,查看要运行的服务器所在的的ip地址为192.168.109.164,客户端创建socket对象时改一下ip地址就可以实现局域网内的通信了
客户端目录如下图
服务端目录如下图
下面是代码:
客户端和服务端公用的Message类,MessageType接口,User类,任何在socket中传输的对象都要序列化,所以Message和User都要实现Serializable接口
不需要引入jar包或者配置其他的东西,按目录把代码复制粘贴到正确位置改一下客户端获取socket对象参数中的IP地址,直接可以运行
package common;
import java.io.Serializable;
/**
客户端和服务端通信的消息对象
**/
public class Message implements Serializable {
private String sender; //发送者
private String getter; //接收者
private String content; //内容
private String sendTime; //发送时间
private String messType; //消息类型[可以在接口中定义
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
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 getMessType() {
return messType;
}
public void setMessType(String messType) {
this.messType = messType;
}
}
package common;
/**
* 消息类型
*/
public interface MessageType {
String MESSAGE_LOGIN_USCCESS = "1"; //登陆成功
String MESSAGE_LOGIN_FAIL = "2"; //登陆失败
String MESSAGE_COMM_MES = "3"; //普通信息包
String MESSAGE_GET_ONLINE = "4"; //请求拉取信息用户
String MESSAGE_SEND_ONLINE = "5"; //返回信息用户
String MESSAGE_CLIENT_EXIT = "6"; //客户端请求推出
String MESSAGE_COMMON_CHAT="7"; //聊天
String MESSAGE_COMMON_CHATTOALL="8"; //群聊天
}
package common;
import java.io.Serializable;
/**
用户信息
**/
public class User implements Serializable {
private String userID;
private String userPassword;
public User() {
}
public User(String userID, String userPassword) {
this.userID = userID;
this.userPassword = userPassword;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
}
先上客户端代码:
package service;
import common.Message;
import common.MessageType;
import javax.swing.*;
import java.io.ObjectInputStream;
import java.net.Socket;
public class ClientConnectServerThread extends Thread{
// 该线程持有socket
private Socket socket;
// 显示消息
private JTextArea jTextArea;
private JPanel jPanel;
public ClientConnectServerThread(Socket socket) {
this.socket = socket;
}
//设置收到消息
private String[] users;
public String[] getUsers() {
return users;
}
public void setUsers(String[] users) {
this.users = users;
}
@Override
public void run() {
// Thread要在后台和服务器通信,写成While循环
while (true){
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
// 如果没有Message会阻塞在这里
Message message = (Message) ois.readObject();
if(message.getMessType().equals(MessageType.MESSAGE_SEND_ONLINE)){
//取出在线列表
String[] users = message.getContent().split(" ");
this.setUsers(users);
System.out.println("===当前在线用户列表===");
for (int i = 0; i < users.length;i++) {
System.out.println(users[i]);
}
System.out.println();
}else if(message.getMessType().equals(MessageType.MESSAGE_COMMON_CHAT)) {
String toOne = message.getSendTime() + "\n" + message.getSender() +
"对你说" + message.getContent();
if(jTextArea.getLineCount()>=8){
jTextArea.setText("");
}
jTextArea.append(toOne+"\n");
// System.out.println(toOne);
}else if(message.getMessType().equals(MessageType.MESSAGE_COMMON_CHATTOALL)){
String toAll= message.getSender()+"对大家伙说:"+message.getContent();
if(jTextArea.getLineCount()>=8){
jTextArea.setText("");
}
jTextArea.append(toAll+"\n");
// System.out.println(toAll);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return socket;
}
public JPanel getjPanel() {
return jPanel;
}
public void setjPanel(JPanel jPanel) {
this.jPanel = jPanel;
}
public JTextArea getjTextArea() {
return jTextArea;
}
public void setjTextArea(JTextArea jTextArea) {
this.jTextArea = jTextArea;
}
}
package service;
import java.util.HashMap;
import java.util.Iterator;
/**
* 管理和客户端通信的线程
*/
public class ManageClientThreads {
private static HashMap<String , ClientConnectServerThread> hm = new HashMap<>();
// 添加线程对象到集合
public static void addClientThread(String ID,ClientConnectServerThread scct){
hm.put(ID,scct);
// if(scct != null){
// System.out.println(ID+"线程添加成功");
// }
}
public static ClientConnectServerThread getClientThread(String ID){
// if(hm.get(ID)!= null){
// System.out.println("线程获取成功");
// }
return hm.get(ID);
}
// 返回在线列表
public static String getOnlineUser(){
// 集合遍历
Iterator<String> iterator = hm.keySet().iterator();
String onlineUserList = "";
while (iterator.hasNext()){
onlineUserList += iterator.next().toString()+" ";
}
return onlineUserList;
}
}
package service;
import common.Message;
import common.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
/**
* 消息服务
*/
public class MessageClient {
//私聊
public void toOne(String content, String senderID, String getterID){
Message message = new Message();
message.setSender(senderID);
message.setGetter(getterID);
message.setContent(content);
message.setSendTime(new Date().toString());
message.setMessType(MessageType.MESSAGE_COMMON_CHAT);
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientThreads.getClientThread(senderID).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
//群聊
public void toAll(String content, String sender){
Message message = new Message();
message.setSender(sender);
message.setContent(content);
message.setMessType(MessageType.MESSAGE_COMMON_CHATTOALL);
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientThreads.getClientThread(sender).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
package service;
import common.Message;
import common.MessageType;
import common.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* 验证用户登录
*/
public class UserClient {
private User user = new User();
// socket可能在其他地方使用,设为属性
private Socket socket;
private boolean b = false;
public boolean checkUser(String ID, String password) throws Exception {
// 创建User对象
user.setUserID(ID);
user.setUserPassword(password);
// 连接服务器
socket = new Socket(InetAddress.getByName("192.168.109.164"), 9999);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(user);//发送User对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message ms = (Message) ois.readObject();
if(ms.getMessType().equals(MessageType.MESSAGE_LOGIN_USCCESS)){
// 创建一个和服务器保持通信的线程
ClientConnectServerThread ccst = new ClientConnectServerThread(socket);
ManageClientThreads.addClientThread(user.getUserID(),ccst);
// 启动客户端线程
ccst.start();
b = true;
}else {
socket.close();
b = false;
}
return b;
}
// 请求在线用户
public void onlineFriend(){
// 发送Message
Message message = new Message();
message.setMessType(MessageType.MESSAGE_GET_ONLINE);
message.setSender(user.getUserID());
// 发送给服务器
// 得到当前线程的Socket
try {
ClientConnectServerThread clientThread = ManageClientThreads.getClientThread(user.getUserID());
Socket socket = clientThread.getSocket();
ObjectOutputStream oos =
new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message); //发送请求
} catch (Exception e) {
e.printStackTrace();
}
}
// 退出客户端
public void logout(){
Message message = new Message();
message.setMessType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(user.getUserID());
try {
ClientConnectServerThread clientThread = ManageClientThreads.getClientThread(user.getUserID());
Socket socket = clientThread.getSocket();
ObjectOutputStream oos =
new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
System.out.println(user.getUserID()+"退出系统");
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
}
}
}
package view;
import service.UserClient;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GUIinterface {
private static UserClient userClient= new UserClient();
public static void main(String[] args) throws Exception {
// 创建 JFrame 实例
JFrame frame = new JFrame("通信系统");
frame.setSize(350, 200);
frame.setLocation(800,400);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
// 添加面板
frame.add(panel);
panel.setLayout(null);
// 创建 JLabel
JLabel userLabel = new JLabel("User:");
userLabel.setBounds(10, 20, 80, 25);
panel.add(userLabel);
JTextField userText = new JTextField(20);
userText.setBounds(100, 20, 165, 25);
panel.add(userText);
JLabel passwordLabel = new JLabel("Password:");
passwordLabel.setBounds(10, 50, 80, 25);
panel.add(passwordLabel);
JPasswordField passwordText = new JPasswordField(20);
passwordText.setBounds(100, 50, 165, 25);
panel.add(passwordText);
JButton loginButton = new JButton("登录");
loginButton.setBounds(10, 80, 80, 25);
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean b = true;
String username = userText.getText();
String password = passwordText.getText();
try {
b = userClient.checkUser(username, password);
} catch (Exception exception) {
exception.printStackTrace();
}
if (b){
new GUIinterface2(username, userClient);
frame.setVisible(false);
}else {
JOptionPane.showMessageDialog(
null,"用户名或者密码存在错误","警告",JOptionPane.ERROR_MESSAGE);
}
}
});
JButton exitjButton = new JButton("退出");
exitjButton.setBounds(100, 80, 80, 25);
exitjButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
panel.add(loginButton);
panel.add(exitjButton);
frame.setVisible(true);
}
}
package view;
import service.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GUIinterface2 {
private MessageClient messageClient = new MessageClient();
public GUIinterface2(String username, UserClient userClient) {
ClientConnectServerThread clientThread = ManageClientThreads.getClientThread(username);
// 创建 JFrame 实例
JFrame frame = new JFrame(username);
frame.setSize(800, 900);
frame.setLocation(700,100);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(null);
// 添加面板
Font font = new Font("黑体", Font.TYPE1_FONT, 20);
frame.add(panel);
JLabel jLabel = new JLabel("你收到的消息:");
jLabel.setBounds(0,0,200,50);
jLabel.setFont(font);
panel.add(jLabel);
JTextArea jTextArea = new JTextArea();
jTextArea.setBounds(0,50,800,200);
jTextArea.setRows(6);
jTextArea.setLineWrap(true);
jTextArea.setFont(font);
clientThread.setjTextArea(jTextArea);
panel.add(jTextArea);
JButton exitButton = new JButton("退出");
exitButton.setFont(font);
exitButton.setBounds(600,0,200,50);
exitButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
userClient.logout();
frame.setVisible(false);
}
});
panel.add(exitButton);
//获取在线好友
JTextArea jTextArea1 = new JTextArea();
jTextArea1.setBounds(0,300,800,200);
jTextArea1.setLineWrap(true);
jTextArea1.setFont(font);
panel.add(jTextArea1);
JButton jButton = new JButton("获取在线好友");
jButton.setFont(font);
jButton.setBounds(0,250,200,50);
jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
userClient.onlineFriend();
String[] users = clientThread.getUsers();
jTextArea1.setText("");
for(int i = 0; i < users.length; i++){
jTextArea1.append(users[i]+"\n");
;
}
}
});
panel.add(jButton);
// 私聊功能
JLabel jLabel1 = new JLabel("输入好友用户名");
jLabel1.setFont(font);
jLabel1.setBounds(0,510,200,50);
panel.add(jLabel1);
JTextArea jTextArea2 = new JTextArea();
jTextArea2.setFont(font);
jTextArea2.setBounds(200, 510, 600, 50);
panel.add(jTextArea2);
JLabel jLabel2 = new JLabel("输入内容");
jLabel2.setFont(font);
jLabel2.setBounds(0,570,200,50);
panel.add(jLabel2);
JTextArea jTextArea3 = new JTextArea();
jTextArea3.setFont(font);
jTextArea3.setBounds(200, 570, 600, 50);
panel.add(jTextArea3);
JButton jButton1 = new JButton("私聊发送");
jButton1.setFont(font);
jButton1.setBounds(600,620,200,50);
jButton1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String content = jTextArea3.getText();
String getterId= jTextArea2.getText();
messageClient.toOne(content, username, getterId);
jTextArea2.setText("");
jTextArea3.setText("");
}
});
panel.add(jButton1);
//群聊功能
JTextArea jTextArea4 = new JTextArea();
jTextArea4.setFont(font);
jTextArea4.setBounds(0, 700, 800, 100);
panel.add(jTextArea4);
JButton jButton2 = new JButton("群聊发送");
jButton2.setFont(font);
jButton2.setBounds(600,800,200,50);
jButton2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String content2 = jTextArea4.getText();
messageClient.toAll(content2, username);
jTextArea4.setText("");
}
});
panel.add(jButton2);
frame.setVisible(true);
}
}
服务端代码:
package frame;
import server.Server;
public class Frame {
public static void main(String[] args) {
new Server();
}
}
package server;
import java.util.*;
/**
* 管理和客户端通信的线程
*/
public class ManageClientThreads {
private static HashMap<String , ServerConnectClientThread> hm = new HashMap<>();
// 添加线程对象到集合
public static void addClientThread(String ID,ServerConnectClientThread scct){
// System.out.println(ID+"线程添加成功");
hm.put(ID,scct);
}
// 根据用户名返回进程
public static ServerConnectClientThread getClientThread(String ID){
// System.out.println(ID+"线程获取成功");
return hm.get(ID);
}
// 返回所有进程
public static ArrayList<ServerConnectClientThread> getAllClientThread(){
// System.out.println(ID+"线程获取成功");
ArrayList<ServerConnectClientThread> serverConnectClientThreads =
new ArrayList();
Collection<ServerConnectClientThread> collection = hm.values();
serverConnectClientThreads.addAll(collection);
return serverConnectClientThreads;
}
// 返回在线列表
public static String getOnlineUser(){
// 集合遍历
Iterator<String> iterator = hm.keySet().iterator();
String onlineUserList = "";
while (iterator.hasNext()){
onlineUserList += iterator.next().toString()+" ";
}
return onlineUserList;
}
// 从集合移除某个线程
public static void remove(String ID){
hm.remove(ID);
}
}
package server;
import common.Message;
import common.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Scanner;
/**
* 服务端推送消息
*/
public class SendNewsToAll extends Thread{
private Scanner scanner = new Scanner(System.in);
@Override
public void run() {
//为了可以推送多次,使用while
while(true){
System.out.println("输入您需要推送的新闻(输入exit退出通信服务");
String content = scanner.next();
if(content.equals("exit")) break;
Message message = new Message();
message.setContent(content);
message.setSender("服务器");
message.setMessType(MessageType.MESSAGE_COMMON_CHATTOALL);
System.out.println("服务器推送:"+content);
ArrayList<ServerConnectClientThread> Threads =
ManageClientThreads.getAllClientThread();
for (ServerConnectClientThread thread :Threads) {
ObjectOutputStream oos =
null;
try {
oos = new ObjectOutputStream(thread.getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package server;
import common.Message;
import common.MessageType;
import common.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
/**
* 服务器监听9999,等待客户端连接
*/
public class Server {
private ServerSocket ss = null;
// 模拟数据库
// HashMap线程不安全 ConcurrentHashMap线程安全
private static ConcurrentHashMap<String, User> dataBase = new ConcurrentHashMap<>();
static {
dataBase.put("admin1", new User("admin1", "2021250"));
dataBase.put("admin2", new User("admin2", "2021251"));
dataBase.put("admin3", new User("admin3", "2021252"));
dataBase.put("admin4", new User("admin4", "2021253"));
dataBase.put("admin5", new User("admin5", "2021254"));
}
private boolean checkUser(String ID, String pwd){
User user = dataBase.get(ID);
if(user == null) return false;
if(!user.getUserPassword().equals(pwd)) return false;
return true;
}
public Server(){
System.out.println("服务器监听9999");
new Thread(new SendNewsToAll()).start();
try {
ss = new ServerSocket(9999);
while (true) {
// 当和某个客户端连接后会继续监听
Socket socket = ss.accept();//没有客户端连接阻塞在这里
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
User user = (User) ois.readObject();
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
// 定义在外面无论是否成功都要回复一个Message对象
Message message = new Message();
if (checkUser(user.getUserID(), user.getUserPassword())) {
message.setMessType(MessageType.MESSAGE_LOGIN_USCCESS);
// 回复message对象
oos.writeObject(message);
System.out.println("用户ID"+user.getUserID()+" 密码"+user.getUserPassword()+" 验证成功");
// 创建一个线程和客户端保持通信
ServerConnectClientThread scct =
new ServerConnectClientThread(socket, user.getUserID());
ManageClientThreads.addClientThread(user.getUserID(), scct);
scct.start();
} else {//登陆失败
message.setMessType(MessageType.MESSAGE_LOGIN_FAIL);
oos.writeObject(message);
System.out.println("用户ID"+user.getUserID()+" 密码"+user.getUserPassword()+" 验证失败");
// 关闭socket
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package server;
import common.Message;
import common.MessageType;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
/**
* 该类的对象和某个客户端的通信
*/
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userID; //连接到服务的的用户ID
public ServerConnectClientThread(Socket socket, String userID) {
this.socket = socket;
this.userID = userID;
}
public Socket getSocket() {
return socket;
}
@Override
public void run() {
while (true){
try {
System.out.println("服务端和客户端"+userID+"保持通信,读取数据");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message mss = (Message) ois.readObject();
// 根据message类型 做相应的业务处理
if(mss.getMessType().equals(MessageType.MESSAGE_GET_ONLINE)){
// 客户端请求在线列表
// 格式为 “admin1 admin2" 通过空格分割
System.out.println(mss.getSender()+"要求在线用户列表");
String onlineUser = ManageClientThreads.getOnlineUser();
Message message2 = new Message();
message2.setMessType(MessageType.MESSAGE_SEND_ONLINE);
message2.setContent(onlineUser);
message2.setGetter(mss.getSender());
ObjectOutputStream oos =
new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message2);
}else if(mss.getMessType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
System.out.println(mss.getSender()+"退出");
ManageClientThreads.remove(mss.getSender());
socket.close();//关闭连接
break;//退出线程
}else if(mss.getMessType().equals(MessageType.MESSAGE_COMMON_CHAT)){
// 根据message获取id,得到对应线程
ServerConnectClientThread clientThread =
ManageClientThreads.getClientThread(mss.getGetter());
ObjectOutputStream oos =
new ObjectOutputStream(clientThread.getSocket().getOutputStream());
oos.writeObject(mss);
}else if(mss.getMessType().equals(MessageType.MESSAGE_COMMON_CHATTOALL)){
ArrayList<ServerConnectClientThread> Threads =
ManageClientThreads.getAllClientThread();
for (ServerConnectClientThread thread :Threads) {
ObjectOutputStream oos =
new ObjectOutputStream(thread.getSocket().getOutputStream());
oos.writeObject(mss);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}