点击下载源代码
目录
JFrame类的几个重要方法
持久层
服务端创建
用户注册模块
用户登录窗口
好友列表和群聊列表模块
用户私聊逻辑
群聊创建
群聊消息
用户对象和数据库对应
package com.lin.client.entity;
import java.util.Set;
/**
* Description:
* Author: llf
* Created in 2019/8/23 16:30
*/
public class User {
private Integer id;
private String userName;
private String password;
private String brief;
private Set userNames;
public User() {}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBrief() {
return brief;
}
public void setBrief(String brief) {
this.brief = brief;
}
public Set getUserNames() {
return userNames;
}
public void setUserNames(Set userNames) {
this.userNames = userNames;
}
}
dao层基类
package com.lin.client.dao;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.lin.client.entity.User;
import com.lin.util.CommUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Description:dao层基础类i,封装数据源,获取连接,关闭资源等操作
* Author: llf
* Created in 2019/8/23 16:30
*/
public class BasedDao {
//获取数据源
private static DruidDataSource dataSource;
static{
Properties properties= CommUtils.loadProperties("db.properties");
try {
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
System.out.println("获取数据源失败");
e.printStackTrace();
}
}
//获取连接
protected DruidPooledConnection getConnection() {
try {
return (DruidPooledConnection) dataSource.getPooledConnection();
} catch (SQLException e) {
System.out.println("数据库连接失败");
e.printStackTrace();
}
return null;
}
//关闭资源
protected void closeResources(Connection connection,
Statement statement) {
try {
if (connection != null) connection.close();
if (statement != null) statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
protected void closeResources(Connection connection,
Statement statement,
ResultSet resultSet) {
closeResources(connection,statement);
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
用户查询和插入DAO模块
package com.lin.client.dao;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.lin.client.entity.User;
import org.apache.commons.codec.digest.DigestUtils;
import java.sql.*;
/**
* Description:
* Author: llf
* Created in 2019/8/23 16:30
*/
public class AccountDao extends BasedDao{
public User userLogin(String userName, String password) {
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try{
connection=getConnection();
String sql="select * from user where username=? and password=?";
statement = connection.prepareStatement(sql);
statement.setString(1,userName);
//******解密 ********************
statement.setString(2, DigestUtils.md5Hex(password));
resultSet=statement.executeQuery();
if (resultSet.next()) {
User user=getUser(resultSet);
return user;
}
}catch (SQLException e) {
System.out.println("用户登陆失败");
e.printStackTrace();
}finally {
closeResources(connection,statement,resultSet);
}
return null;
}
private User getUser(ResultSet resultSet) throws SQLException {
User user=new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setBrief(resultSet.getString("brief"));
return user;
}
public boolean userReg(User user) {
Connection connection=null;
PreparedStatement statement=null;
try{
connection = getConnection();
String str="insert into user(username,password,brief) values (?,?,?)";
//接收执行sql语句影响的行数,
statement=connection.prepareStatement(str, Statement.RETURN_GENERATED_KEYS);
statement.setString(1,user.getUserName());
//加密 *************
statement.setString(2,DigestUtils.md5Hex(user.getPassword()));
statement.setString(3,user.getBrief());
int rows = statement.executeUpdate();
if (rows==1) {
return true;
}
}catch (SQLException ex) {
System.out.println("用户注册失败");
ex.printStackTrace();
}finally {
closeResources(connection,statement);
}
return false;
}
}
public class CommUtils {
//这里我们使用的是谷歌推出的GSON类,创建对象(建造者模式)
private static final Gson GSON=new GsonBuilder().create();
//加载配置文件
public static Properties loadProperties(String fileName) {
Properties properties=new Properties();
InputStream in=CommUtils.class.getClassLoader().getResourceAsStream(fileName);
try {
properties.load(in);
} catch (IOException e) {
return null;
}
return properties;
}
//将实例化对象转换为JSON格式
public static String object2Json(Object obj) {
return GSON.toJson(obj);
}
//将JSON格式转换为实例化对象
public static Object json2Object(String jsonStr, Class objClass) {
return GSON.fromJson(jsonStr,objClass);
}
}
创建客户端和服务端之间通信的VO对象
public class MessageVO {
/**
* 客户端和服务器之间的协议
*/
private String type;
/**
* 客户端与服务端之间发送数据的内容
*/
private String content;
/**
* 客户端告诉服务器要将信息发送给何处,在群聊中也可封装群聊用户集合等数据
*/
private String to;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
}
创建服务端
public class MultiThreadServer {
private static final String IP;
private static final int PORT;
static {
//读取socket配置文件
Properties pros= CommUtils.loadProperties("socket.properties");
IP=pros.getProperty("address");
PORT=Integer.parseInt(pros.getProperty("port"));
}
public static void main(String[] args) throws IOException {
//创建服务端
ServerSocket serverSocket=new ServerSocket(PORT);
//创建线程池设置连接服务端的人数
ExecutorService executors = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
System.out.println("等待客户端连接...");
Socket client = serverSocket.accept();
System.out.println("有新的客户端连接,端口号为"+client.getPort());
//有客户端连接就创建线程不断监听该客户端发来的信息
executors.submit(new ExecuteClient(client));
}
}
}
封装客户端与服务端的连接
public class Connect2Server {
private static final String IP;
private static final int PORT;
static {
Properties properties = CommUtils.loadProperties("socket.properties");
IP=properties.getProperty("address");
PORT= Integer.parseInt(properties.getProperty("port"));
}
private Socket client;
private InputStream in;
private OutputStream out;
public Connect2Server() {
try {
//连接服务器的端口,所以此处与服务器端读取同一个配置文件
client=new Socket(IP,PORT);
//获取连接的输入流
in=client.getInputStream();
//获取连接的输出流
out=client.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
public InputStream getIn() {
return in;
}
public OutputStream getOut() {
return out;
}
}
创建GUI Form画布(窗口):设置相应的窗体名称,Idea软件会为我们自动生成窗口的对应的属性,
分析
当点击注册按钮之后获取窗体数据,封装成User对象,调用数据库进行创建,
创建成功,显示创建成功,创建用户窗口关闭
创建失败,显示失败信息
public class UserReg {
private JTextField userNameText;
private JPasswordField passwordText;
private JTextField brifeText;
private JButton regBtn;
private JPanel userRegPanel;
private AccountDao accountDao=new AccountDao();
public UserReg() {
JFrame frame = new JFrame("用户注册");
//包含这个窗口下的所有窗体
frame.setContentPane(userRegPanel);
//设置关闭即该窗口永久消失
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//使窗口显示在正中央
frame.setLocationRelativeTo(null);
//根据布局来确定窗口的最佳大小
frame.pack();
//窗口图形界面可视, 默认不可视
frame.setVisible(true);
//点击注册按钮,将信息持久化到db中,成功弹出提示框
regBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取表单参数
String userName = userNameText.getText();
//获取加密的密码
String password = String.valueOf(passwordText.getPassword());
String brief=brifeText.getText();
//封装User保存到数据库中
User user=new User();
user.setUserName(userName);
user.setPassword(password);
user.setBrief(brief);
if (accountDao.userReg(user)) {
//注册成功
JOptionPane.showMessageDialog(frame,"注册成功!","提示信息",JOptionPane.INFORMATION_MESSAGE);
frame.setVisible(false);
}else {
//注册失败
JOptionPane.showMessageDialog(frame,"注册失败!","错误信息",JOptionPane.ERROR_MESSAGE);
}
}
});
}
}
用画布画出该窗口
分析
用户在登陆界面当点击注册按钮时,创建注册按钮,跳转到注册页面逻辑
用户在登陆页面点击登陆按钮时,获取窗体数据,调用数据库根据密码和用户名查询是否存在该用户
若存在:提示登陆成功信息,
点击确定,创建连接,将自己的信息以协议“1”发送到服务端,标识为登陆逻辑,方便服务端转向,
* 此时 type = 1 content = userName
登陆页面关闭,创建用户列表界面
若不存在
提示错误信息即可
package com.lin.client.service;
import com.lin.client.dao.AccountDao;
import com.lin.client.entity.User;
import com.lin.util.CommUtils;
import com.lin.vo.MessageVO;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Scanner;
import java.util.Set;
/**
* Description:登陆成功,将当前用户所有的在线好友发送到服务器
* Author: llf
* Created in 2019/8/23 19:04
*/
public class UserLogin {
private JButton regButton;
private JButton loginButton;
private JTextField userNameText;
private JPasswordField passwordText;
private JPanel userPanel;
private JPanel UserLoginPanel;
private JFrame frame;
private AccountDao accountDao=new AccountDao();
public UserLogin() {
frame = new JFrame("用户登陆");
frame.setContentPane(UserLoginPanel);
//退出即关闭该对象
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//将窗口置于屏幕中央
frame.setLocationRelativeTo(null);
//根据页面布局设置窗口的大小
frame.pack();
//图形界面可视
frame.setVisible(true);
//创建监听事件,注册按钮,当点击该按钮,创建出一个注册页面
regButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new UserReg();
}
});
//创建监听事件,登陆按钮
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取表单参数 校验用户信息
String userName = userNameText.getText();
String password = String.valueOf(passwordText.getPassword());
User user = accountDao.userLogin(userName,password);
if (user!=null) {
//显示成功信息**************
JOptionPane.showMessageDialog(frame,"登陆成功","提示信息",JOptionPane.INFORMATION_MESSAGE);
//登陆页面关闭
frame.setVisible(false);
/**
* 与服务器建立连接
* 读取服务端所有好友信息
* 新建一个后台进程不断读取服务器发来的信息
* type 1
* content userName
* to 私聊 群聊
*/
//以协议1的方式向服务器发送数据
Connect2Server connect2Server=new Connect2Server();
MessageVO msg2Server=new MessageVO();
msg2Server.setType("1");
msg2Server.setContent(userName);
String json2Server=CommUtils.object2Json(msg2Server);
try {
PrintStream out=new PrintStream(connect2Server.getOut(),true,"UTF-8");
//发送数据到服务器
out.println(json2Server);
//接收服务器发来的消息,显示所有在线好友,加载用户列表
/**
* type 1
* content 在线用户信息
* to
*/
Scanner in=new Scanner(connect2Server.getIn());
if (in.hasNextLine()) {
String msgFromServerStr = in.nextLine();
MessageVO msgFromServer=(MessageVO) CommUtils.json2Object(msgFromServerStr,MessageVO.class);
//将服务器发送过来的信息封装成一个储存user对象的Set集合,传入好友列表进行显示,
Set users = (Set) CommUtils.json2Object(msgFromServer.getContent(), Set.class);
System.out.println("所有在线用户为:" + users);
//加载用户列表界面,将当前用户名,所有在线好友,与服务器建立连接,传递到好友列表界面***********
new FriendsList(userName,users,connect2Server);
}
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
}else {
//登陆失败,留在当前登陆页面,显示错误信息
JOptionPane.showMessageDialog(frame,"登陆失败!","错误信息",JOptionPane.ERROR_MESSAGE);
}
}
});
}
public static void main(String[] args) {
UserLogin userLogin=new UserLogin();
}
}
服务器端程序
//type:“1”
//content 所有在线好友
//缓存当前服务器所有在线的客户信息
private static Map clients=new ConcurrentHashMap<>();
private static class ExecuteClient implements Runnable{
private Socket client;
private Scanner in;
private PrintStream out;
public ExecuteClient(Socket client) {
this.client=client;
try {
this.in=new Scanner(client.getInputStream());
this.out=new PrintStream(client.getOutputStream(),true,"UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
if (in.hasNextLine()) {
String jsonStrFromClient = in.nextLine();
MessageVO msgFromClient = (MessageVO) CommUtils.json2Object(jsonStrFromClient, MessageVO.class);
if (msgFromClient.getType().equals("1")) {
//获取客户发来的用户名
String userName=msgFromClient.getContent();
/**
* 将当前所有在线用户发送给新用户
* 将新用户上限信息发送给其他用户(上线提醒)
* 保存新用户上线信息
*/
//将当前在线的所有用户发回客户端
MessageVO msg2Client=new MessageVO();
msg2Client.setType("1");
msg2Client.setContent(CommUtils.object2Json(clients.keySet()));
out.println(CommUtils.object2Json(msg2Client));
//将新上线的用户信息发回给当前以在线的所有用户
sendUserLogin("newLogin:"+userName);
//将当前用户注册到服务端缓存
clients.put(userName,client);
System.out.println(userName+"上线了!");
System.out.println("当前聊天室共有"+clients.size()+"人");
}
}
}
}
private void sendUserLogin(String msg) {
for(Map.Entry entry:clients.entrySet()) {
Socket socket=entry.getValue();
try {
PrintStream out=new PrintStream(socket.getOutputStream(),true,"UTF-8");
out.println(msg);
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
用画布画出好友列表和群聊列表窗口的静态部分,动态部分由代码实现
分析
登陆成功后程序逻辑
服务端
1.保存新用户上线信息 request
2.将当前所有在线用户发回给新用户 response
3.将新用户上线信息发给所有其他用户(上线提醒)
客户端
1.与服务器建立连接,将自己的用户名与Socket保存到服务端缓存
2.读取服务端的所有在线好友信息
3.新建一个后台线程不断读取服务器发来的信息
public class FriendsList {
private JButton createGroupBtn;
private JScrollPane friendsList;
private JScrollPane groupListPanel;
private JPanel friendsPanel;
private JFrame frame;
private String userName;
private Set users;
private Connect2Server connect2Server;
public FriendsList(String userName, Set users, Connect2Server connect2Server) {
this.userName = userName;
this.users = users;
this.connect2Server = connect2Server;
frame = new JFrame(userName);
frame.setContentPane(friendsPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
//使窗口显示在正中央
frame.setLocationRelativeTo(null);
//图形界面可视, 默认不可视
frame.setVisible(true);
//加载所有在线用户信息
loadUsers();
//启动后台线程不断监听服务器发来的消息*************************************
Thread daemonThread = new Thread(new DaemonTask());
//设置为守护线程
daemonThread.setDaemon(true);
daemonThread.start();
}
//好友列表后台任务,不断监听服务器发来的信息,好友上限提醒,用户私聊,群聊
private class DaemonTask implements Runnable {
private Scanner in = new Scanner(connect2Server.getIn());
@Override
public void run() {
while (true) {
if (in.hasNextLine()) {
//接收服务到发来的信息
String strFromServer = in.nextLine();
//此时服务器端发来一个Json字串
if (strFromServer.startsWith("{")) {
} else {
if (strFromServer.startsWith("newLogin:")) {
//好友上线逻辑
String newFriendName = strFromServer.split(":")[1];
users.add(newFriendName);
JOptionPane.showMessageDialog(frame,
newFriendName + "上线了!", "上线提醒",
JOptionPane.INFORMATION_MESSAGE);
//刷新好友列表
loadUsers();
}
}
}
}
}
}
private void loadUsers() {
//创建好友列表的盘子
JLabel[] userLables = new JLabel[users.size()];
JPanel friends = new JPanel();
//垂直布局
friends.setLayout(new BoxLayout(friends, BoxLayout.Y_AXIS));
Iterator iterator = users.iterator();
int i = 0;
while (iterator.hasNext()) {
String userName = iterator.next();
userLables[i] = new JLabel(userName);
//将好友列表加到盘子中
friends.add(userLables[i]);
i++;
}
//将所有的好友的数组盘子加到friendsList的大盘子
friendsList.setViewportView(friends);
//设置滚动条垂直滚动 friendsList.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
//刷新
friends.revalidate();
friendsList.revalidate();
}
}
用画布画出该窗口的静态部分,显示聊天信息部分由代码实现
//缓存所有私聊界面
private Map privateChatGUIList = new ConcurrentHashMap<>();
//私聊点击事件
private class PrivateLabelAction implements MouseListener {
private String labelName;
//根据标签名称跳转私聊界面
public PrivateLabelAction(String labelName) {
this.labelName = labelName;
}
//鼠标点击事件
@Override
public void mouseClicked(MouseEvent e) {
//判断好友列表是否缓存私聊标签,
if (privateChatGUIList.containsKey(labelName)) {
//找到该标签使其显示
PrivateChatGUI privateChatGUI = privateChatGUIList.get(labelName);
privateChatGUI.getFreame().setVisible(true);
} else {
//第一次点击,创建私聊界面
PrivateChatGUI privateChatGUI = new PrivateChatGUI(
labelName, userName, connect2Server);
privateChatGUIList.put(labelName, privateChatGUI);
}
}
//鼠标一直按下事件
@Override
public void mousePressed(MouseEvent e) {
}
//鼠标按下释放事件
@Override
public void mouseReleased(MouseEvent e) {
}
//鼠标移入执行事件
@Override
public void mouseEntered(MouseEvent e) {
}
//鼠标移出事件
@Override
public void mouseExited(MouseEvent e) {
}
}
分析
客户端:
主动发送方
1.点击要私聊的用户标签,弹出私聊界面 (缓存私聊界面对象)
2.按照指定的协议向服务器发送私聊信息
// type:2
// content:sender-msg
// to:目标客户端的用户名
package com.lin.client.service;
import com.lin.util.CommUtils;
import com.lin.vo.MessageVO;
import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
/**
* Description:
* Author: llf
* Created in 2019/8/24 18:55
*/
public class PrivateChatGUI {
private JTextArea readFromServer;
private JPanel privateChatPanel;
private JTextField send2Server;
private String friendName;
private String myName;
private Connect2Server connect2Server;
private JFrame frame;
private PrintStream out;
public PrivateChatGUI(String friendName,
String myName,
Connect2Server connect2Server) {
this.friendName=friendName;
this.connect2Server=connect2Server;
this.myName=myName;
try {
this.out=new PrintStream(connect2Server.getOut(),true,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
frame = new JFrame("与"+friendName+"私聊中...");
frame.setContentPane(privateChatPanel);
//设置窗口关闭操作,将其设置为隐藏
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
frame.setSize(400,400);
frame.setVisible(true);
//捕捉输入框的键盘输入
send2Server.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
StringBuilder sb=new StringBuilder();
sb.append(send2Server.getText());
//当捕捉到按下enter
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
String msg=sb.toString();
MessageVO messageVO=new MessageVO();
messageVO.setType("2");
messageVO.setContent(myName+"-"+msg);
messageVO.setTo(friendName);
PrivateChatGUI.this.out.println(CommUtils.object2Json(messageVO));
//将自己发送的信息展示到当前私聊界面*******
readFromServer(myName+"说:"+ msg);
send2Server.setText("");
}
}
});
}
//给信息接收者
public void readFromServer(String s) {
readFromServer.append(s+"\n");
}
public JFrame getFreame() {
return this.frame;
}
}
服务端:
1.收到客户端发来的私聊信息,取出目标端Socket,做信息转发
// type:2
// content:sender-msg
// to
private static class ExecuteClient implements Runnable{
private Socket client;
private Scanner in;
private PrintStream out;
public ExecuteClient(Socket client) {
this.client=client;
try {
this.in=new Scanner(client.getInputStream());
this.out=new PrintStream(client.getOutputStream(),true,"UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
if (in.hasNextLine()) {
String jsonStrFromClient = in.nextLine();
MessageVO msgFromClient = (MessageVO) CommUtils.json2Object(jsonStrFromClient, MessageVO.class);
if (msgFromClient.getType().equals("1")) {
//用户登录逻辑
}else if (msgFromClient.getType().equals("2")) {
//用户私聊 Type2
//Content:myName-msg
//to:friendName
String friendName=msgFromClient.getTo();
Socket clientSocket=clients.get(friendName);
try{
PrintStream out=new PrintStream(clientSocket.getOutputStream(),true,"UTF-8");
MessageVO msg2Client=new MessageVO();
msg2Client.setType("2");
msg2Client.setContent(msgFromClient.getContent());
System.out.println("收到私聊消息,内容为:"+msgFromClient.getContent());
out.println(CommUtils.object2Json(msg2Client));
}catch (IOException ex){
ex.printStackTrace();
}
}
}
}
}
信息接收方:
1.判断服务端发来的消息是否是私聊信息,若是,判断缓存中是否有该界面
2.按照指定协议来读取内容,发送消息
//缓存所有私聊界面
private Map privateChatGUIList = new ConcurrentHashMap<>();
private class DaemonTask implements Runnable {
private Scanner in = new Scanner(connect2Server.getIn());
@Override
public void run() {
while (true) {
if (in.hasNextLine()) {
//接收服务到发来的信息
String strFromServer = in.nextLine();
//此时服务器端发来一个Json字串
if (strFromServer.startsWith("{")) {
MessageVO messageVO = (MessageVO) CommUtils.json2Object(strFromServer, MessageVO.class);
if (messageVO.getType().equals("2")) {
String friendName = messageVO.getContent().split("-")[0];
String msg = messageVO.getContent().split("-")[1];
//判断此私聊是不是第一次创建
if (privateChatGUIList.containsKey(friendName)) {
PrivateChatGUI privateChatGUI = privateChatGUIList.get(friendName);
privateChatGUI.getFreame().setVisible(true);
privateChatGUI.readFromServer(friendName + "说:" + msg);
} else {
PrivateChatGUI privateChatGUI = new PrivateChatGUI(friendName, userName, connect2Server);
privateChatGUIList.put(friendName, privateChatGUI);
privateChatGUI.readFromServer(friendName + "说:" + msg);
}
}
} else {
if (strFromServer.startsWith("newLogin:")) { //********************** 好友上线逻辑
String newFriendName = strFromServer.split(":")[1];
users.add(newFriendName);
JOptionPane.showMessageDialog(frame,
newFriendName + "上线了!", "上线提醒", JOptionPane.INFORMATION_MESSAGE);
//刷新好友列表
loadUsers();
}
}
}
}
}
}
用画布画出该窗口的静态部分,显示好友列表的复选框部分由代码实现
分析:点击提交信息,获取表单参数,发送到服务端
客户端
// type:3
// content:groupName
// to:selectedFriends
package com.lin.client.service;
import com.lin.util.CommUtils;
import com.lin.vo.MessageVO;
import javafx.scene.effect.SepiaTone;
import sun.plugin2.message.Message;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Description:
* Author: llf
* Created in 2019/8/29 18:43
*/
public class CreateGroupGUI {
private JPanel createGroupPanel;
private JTextField groupNameText;
private JButton conformBtn;
private JPanel friendLabelPanel;
private String myName;
private Set friends;
private Connect2Server connect2Server;
private FriendsList friendsList;
public CreateGroupGUI(String myName,
Set friends,
Connect2Server connect2Server,
FriendsList friendsList){
this.myName=myName;
this.friends=friends;
this.connect2Server=connect2Server;
this.friendsList=friendsList;
JFrame frame = new JFrame("创建群聊");
frame.setContentPane(createGroupPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400,300);
//居中
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//将在线好友以checkBox展示到界面中
friendLabelPanel.setLayout(new BoxLayout(friendLabelPanel,BoxLayout.Y_AXIS));
Iterator iterator=friends.iterator();
while (iterator.hasNext()) {
String lableName=iterator.next();
JCheckBox checkBox=new JCheckBox(lableName);
friendLabelPanel.add(checkBox);
}
friendLabelPanel.revalidate();
conformBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Set selectedFriends=new HashSet<>();
Component[] comps=friendLabelPanel.getComponents();
for (Component comp : comps) {
//向下转型
JCheckBox checkBox= (JCheckBox) comp;
if (checkBox.isSelected()) {
String labelName= checkBox.getText();
selectedFriends.add(labelName);
}
}
//创建记得加入自己的名字
selectedFriends.add(myName);
//获取群名
String groupName=groupNameText.getText();
//发送给服务器
MessageVO messageVO=new MessageVO();
/**
* 协议3
*/
messageVO.setType("3");
messageVO.setContent(groupName);
messageVO.setTo(CommUtils.object2Json(selectedFriends));
try {
PrintStream out=new PrintStream(connect2Server.getOut(),true,"UTF-8");
out.println(CommUtils.object2Json(messageVO));
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
//将当前群聊界面隐藏
frame.setVisible(false);
friendsList.addGroup(groupName,selectedFriends);
friendsList.loadGroupList();
}
});
}
}
服务端
//缓存当前服务器所有的群名以及群好友
private static Map> groups=new ConcurrentHashMap<>();
private static class ExecuteClient implements Runnable{
private Socket client;
private Scanner in;
private PrintStream out;
public ExecuteClient(Socket client) {
this.client=client;
try {
this.in=new Scanner(client.getInputStream());
this.out=new PrintStream(client.getOutputStream(),true,"UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
if (in.hasNextLine()) {
String jsonStrFromClient = in.nextLine();
MessageVO msgFromClient = (MessageVO) CommUtils.json2Object(jsonStrFromClient, MessageVO.class);
if (msgFromClient.getType().equals("1")) {
//用户登陆逻辑
}else if (msgFromClient.getType().equals("2")) {
//用户私聊逻辑
}else if (msgFromClient.getType().equals("3")) {
String groupName=msgFromClient.getContent();
Set friends= (Set) CommUtils.json2Object(msgFromClient.getTo(), Set.class);
groups.put(groupName,friends);
System.out.println("有新的群注册成功,群名为:"+groupName
+",一共有"+groups.size()+"个群");
}
}
}
}
好友列表窗口
//存储所有的群名称和群好友
private Map> groupMap = new ConcurrentHashMap<>();
public void addGroup(String groupName, Set friends) {
groupMap.put(groupName, friends);
}
public void loadGroupList() {
//存储所有群名称的标签Jpanel
JPanel groupNamePanel = new JPanel();
groupNamePanel.setLayout(new BoxLayout(groupNamePanel, BoxLayout.Y_AXIS));
JLabel[] labels = new JLabel[groupMap.size()];
//Map遍历
Set entries = groupMap.keySet();
Iterator iterator = entries.iterator();
int i = 0;
while (iterator.hasNext()) {
String groupName = iterator.next();
labels[i] = new JLabel(groupName);
//添加到自定义的盘子
groupNamePanel.add(labels[i]);
i++;
}
//将自定义盘子加到滚动条中
groupListPanel.setViewportView(groupNamePanel);
//设置垂直滚动
groupListPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
//刷新
groupListPanel.revalidate();
}
用画布画出该窗口的静态部分,显示好友列表和群聊消息的部分由代码实现
//缓存所有的群聊页面 群聊名 和群聊界面
private Map groupChatGUIMap=new ConcurrentHashMap<>();
//群聊点击事件
private class GroupLabelAction implements MouseListener {
private String groupName;
public GroupLabelAction(String groupName) {
this.groupName=groupName;
}
@Override
public void mouseClicked(MouseEvent e) {
if (groupChatGUIMap.containsKey(groupName)) {
GroupChatGUI groupChatGUI=groupChatGUIMap.get(groupName);
groupChatGUI.getFrame().setVisible(true);
}else {
// 获得所有的群聊成员
Set names=groupMap.get(groupName);
GroupChatGUI groupChatGUI=new GroupChatGUI(groupName,names,userName,connect2Server);
groupChatGUIMap.put(groupName,groupChatGUI);
}
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
分析
客户端
群聊发起者
1.点击群名称标签,弹出群聊界面
2.将群聊内容发送到服务端
// type:4
// content:senderName-msg
// to:groupName
package com.lin.client.service;
import com.lin.util.CommUtils;
import com.lin.vo.MessageVO;
import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Set;
/**
* Description:
* Author: llf
* Created in 2019/8/29 20:30
*/
public class GroupChatGUI {
private JPanel groupPanel;
private JTextArea readFromServer;
private JTextField send2Server;
private JPanel friendsPanel;
private String groupName;
private Set friends;
private String myName;
private Connect2Server connect2Server;
private JFrame frame;
public GroupChatGUI(String groupName,
Set friends,
String myName,
Connect2Server connect2Server) {
frame= new JFrame("GroupChatGUI");
frame.setContentPane(groupPanel);
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(400,400);
frame.setVisible(true);
friendsPanel.setLayout(new BoxLayout(friendsPanel,
BoxLayout.Y_AXIS));
Iterator iterator=friends.iterator();
while (iterator.hasNext()) {
String lableName=iterator.next();
JLabel jLabel=new JLabel(lableName);
friendsPanel.add(jLabel);
}
send2Server.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
StringBuilder sb=new StringBuilder();
sb.append(send2Server.getText());
//捕捉回车按键
if (e.getKeyCode()==KeyEvent.VK_ENTER) {
String str2Server=sb.toString();
/**
* type:4
* content:myName-msg
* to:groupName
*/
MessageVO messageVO=new MessageVO();
messageVO.setType("4");
messageVO.setContent(myName+"-"+str2Server);
messageVO.setTo(groupName);
try {
PrintStream out=new PrintStream(connect2Server.getOut(),true,"UTF-8");
out.println(CommUtils.object2Json(messageVO));
send2Server.setText("");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
}
}
});
}
public void readFromServer(String msg) {
readFromServer.append(msg+"\n");
}
public JFrame getFrame() {
return frame;
}
}
服务端
1.收到群聊信息,解出群名
2.根据群名称找到该群的所有用户的用户名
3.再根据用户名找到该用户的Socket,发送群聊信息
// type:4
// content:senderName-msg
// to:groupName-[该群的所有群成员]
@Override
public void run() {
while (true) {
if (in.hasNextLine()) {
String jsonStrFromClient = in.nextLine();
MessageVO msgFromClient = (MessageVO) CommUtils.json2Object(jsonStrFromClient, MessageVO.class);
if (msgFromClient.getType().equals("1")) {
//登陆成功逻辑
}else if (msgFromClient.getType().equals("2")) {
//用户私聊
}else if (msgFromClient.getType().equals("3")) {
//创建群组
}else if (msgFromClient.getType().equals("4")) {
// type:4
// content:myName-msg
// to:groupName
String groupName=msgFromClient.getTo();
Set names=groups.get(groupName);
Iterator iterator=names.iterator();
while (iterator.hasNext()) {
String socketName=iterator.next();
Socket client=clients.get(socketName);
try {
PrintStream out=new PrintStream(client.getOutputStream(),true,"UTF-8");
/**
* typr=4
* content直接转发
* 组名-所有的群成员
*/
MessageVO messageVO=new MessageVO();
messageVO.setType("4");
messageVO.setContent(msgFromClient.getContent());
messageVO.setTo(groupName+"-"+CommUtils.object2Json(names));
out.println(CommUtils.object2Json(messageVO));
System.out.println("服务器发送的群聊消息为:"+messageVO);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
群聊接收者
1.当第一次收到群信息,先刷新本客户端的群聊列表信息
2.弹出群聊界面
private class DaemonTask implements Runnable {
private Scanner in = new Scanner(connect2Server.getIn());
@Override
public void run() {
while (true) {
if (in.hasNextLine()) {
//接收服务到发来的信息
String strFromServer = in.nextLine();
//此时服务器端发来一个Json字串
if (strFromServer.startsWith("{")) {
MessageVO messageVO = (MessageVO) CommUtils.json2Object(strFromServer, MessageVO.class);
if (messageVO.getType().equals("2")) {
//私聊客户端逻辑
}else if (messageVO.getType().equals("4")) {
/**
* 收到服务器发来的群聊信息
* type
* content:sender-msg
* to:groupName-[]
*/
String groupName = messageVO.getTo().split("-")[0];
String senderName = messageVO.getContent().split("-")[0];
String groupMsg = messageVO.getContent().split("-")[1];
//若此名称在群聊列表
if (groupMap.containsKey(groupName)) {
if (groupChatGUIMap.containsKey(groupName)) {
//缓存中有群聊界面 弹出
GroupChatGUI groupChatGUI = groupChatGUIMap.get(groupName);
groupChatGUI.getFrame().setVisible(true);
groupChatGUI.readFromServer(senderName+"说:"+groupMsg);
}else {
//缓存中没有
Set names=groupMap.get(groupName);
GroupChatGUI groupChatGUI=new GroupChatGUI(groupName,names,userName,connect2Server);
groupChatGUIMap.put(groupName,groupChatGUI);
groupChatGUI.readFromServer(senderName+"说:"+groupMsg);
}
}else {
/**
* 用户第一次收到群聊消息
*/
Set friends= (Set) CommUtils.json2Object(messageVO.getTo().split("-")[1],
Set.class);
groupMap.put(groupName,friends);
loadGroupList();
//弹出群聊界面
GroupChatGUI groupChatGUI = new GroupChatGUI(groupName,
friends,userName,connect2Server);
groupChatGUIMap.put(groupName,groupChatGUI);
groupChatGUI.readFromServer(senderName+"说:"+groupMsg);
}
}
} else {
if (strFromServer.startsWith("newLogin:")) { //********************** 好友上线逻辑
String newFriendName = strFromServer.split(":")[1];
users.add(newFriendName);
JOptionPane.showMessageDialog(frame,
newFriendName + "上线了!", "上线提醒", JOptionPane.INFORMATION_MESSAGE);
//刷新好友列表
loadUsers();
}
}
}
}
}
}