前言:
本人大二新手一枚,在此发表自己的一些拙见,单纯发表一下记录自己第一次独立完成这个任务,说实话本人编程水平不强,完成此次课设的过程也是很艰难,走了很多弯路,如下图,大概说一下自己的思路,本人建议完成本项目最好实现聊天后再慢慢加功能,本人当时先做页面也没分客户端和服务端,走了很多弯路,实现聊天功能用的TCP,最好自己有整体思路后开始写。本人代码耦合性很大,有点费解,就是在各个类中跳转太多。下面开始解析我的代码。因为第一次写整个文章写的不是很好,解说的也不是很清楚,还望您见谅,所有源码都在这里面如果感兴趣麻烦您自己去看一下思路。
整体思路:
大概说一下聊天功能思路,在客户端中专门开一个线程接受信息,客户端所有的发送消息都在相应的监听中发送给服务器,在服务器端每当有一个客户端连接时就专门开一个线程处理。在服务端储存客户端的socket,发送消息和文件就找到对应的socket发送即可,我用了map存储。
注册功能:
按规定要求输入后客户端将相关信息发送给服务器后服务器存入数据库。
注册页面:
页面设置空布局,一个标签有一个框,验证码两个框。我一般使用的都是作用英文翻译的单词做对象名
class TheSignupPage {
private static RegisteredListener registerlListener = null;
static JTextField VCodeshow;
public TheSignupPage() {
JFrame jf2 = new JFrame();
jf2.setLayout(null);
jf2.setBounds(600, 300, 450, 400);
JLabel label = new JLabel("用户名:");
label.setBounds(100, 50, 100, 25);
JTextField username = new JTextField();
username.setBounds(170, 50, 150, 25);
username.setEditable(true);
jf2.add(label);
jf2.add(username);
JLabel label1 = new JLabel("账 号:");
label1.setBounds(100, 100, 100, 25);
JTextField account = new JTextField();
account.setBounds(170, 100, 150, 25);
account.setEditable(true);
jf2.add(label1);
jf2.add(account);
JLabel label2 = new JLabel("密 码:");
label2.setBounds(100, 150, 100, 25);
JPasswordField passwordField1 = new JPasswordField();
passwordField1.setBounds(170, 150, 150, 25);
passwordField1.setEditable(true);
jf2.add(label2);
jf2.add(passwordField1);
JLabel label3 = new JLabel("确认密码:");
label3.setBounds(100, 200, 100, 25);
JPasswordField passwordField2 = new JPasswordField();
passwordField2.setBounds(170, 200, 150, 25);
passwordField2.setEditable(true);
jf2.add(label3);
jf2.add(passwordField2);
JLabel label4 = new JLabel("验证码:");
label4.setBounds(100, 250, 100, 25);
JTextField VerificationCode = new JTextField();
VerificationCode.setBounds(170, 250, 100, 25);
VCodeshow = new JTextField(10);
//验证码显示的框
VCodeshow.setBounds(270, 250, 50, 25);
VerificationCode.setEditable(true);
VCodeshow.setEditable(false);
Methods vCode = new Methods();
VCodeshow.setText(vCode.randomCode());
VCodeshow.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
Methods Code = new Methods();
VCodeshow.setText(Code.randomCode());
}
});
jf2.add(label4);
jf2.add(VerificationCode);
jf2.add(VCodeshow);
JButton okButton = new JButton("确 定");
JButton cancle = new JButton("取 消");
okButton.setBounds(100, 300, 70, 25);
cancle.setBounds(250, 300, 70, 25);
registerlListener = new RegisteredListener(username, account,passwordField1,passwordField2,VerificationCode,VCodeshow,jf2);
//监听
okButton.addActionListener(registerlListener);
//为确定按钮添加监听
cancle.addActionListener(event -> jf2.dispose());
jf2.add(okButton);
jf2.add(cancle);
JButton cancleButton = new JButton(new ImageIcon("picture/退出1.png"));
cancleButton.setBounds(422, 0, 26, 26);
cancleButton.setRolloverIcon(new ImageIcon("picture/退出副本 2.png"));
// setRolloverIcon()设置当光标移动到按钮上时显示的图像
cancleButton.setBorderPainted(false);
// 是否画边框,如果用自定义图片做按钮背景可以设为 false。
jf2.add(cancleButton);
cancleButton.addActionListener(event-> jf2.dispose());
jf2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf2.setUndecorated(true);
jf2.setResizable(false);// 禁止改变窗口大小
jf2.setVisible(true);
}
}
验证码功能函数:
public String randomCode() {
String str = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int index;// = -1;// 存储字符串的下标
char c;// 存储下标对应的字符
StringBuffer code = new StringBuffer();// 创建StringBuffer对象code,存储生成的验证码
Random r = new Random();// 创建随机数对象r
for (int i = 0; i < 4; i++) {
index = r.nextInt(str.length());// nextInt生成随机数[0,str.length()]
c = str.charAt(index);// 找到index位置的字符
code.append(c);// 添加字符
}
String randomString = new String(code);
return randomString;
}
注册功能实现:
注册监听
public class RegisteredListener implements ActionListener {
private JTextField a;
private JTextField b;
private JPasswordField c;
private JPasswordField d;
private JTextField e;
private JTextField f;
private JFrame g;
public RegisteredListener(JTextField username, JTextField account, JPasswordField passwordField1,
JPasswordField passwordField2, JTextField VerificationCode, JTextField VCodeshow,JFrame jf2) {
super();
this.a = username;
this.b = account;
this.c = passwordField1;
this.d = passwordField2;
this.e = VerificationCode;
this.f = VCodeshow;
this.g=jf2;
}
public void actionPerformed(ActionEvent event) {
Methods Code = new Methods();
if (b.getText().length() >= 5 && b.getText().length() <= 10) {
if (new String(c.getPassword()).length() >= 5 || new String(c.getPassword()).length() <= 10) {
if (f.getText().equals(e.getText())) {
/*
* ==比较的是两个对象是不是同一个对象 equals比较的是两个对象的内容是不是相同 目前未能实现监督各框不能为空值
*/
MultiThreadClient.send(new StringBuffer(a.getText()).append("#135@").toString());
try {
Thread.sleep(500);
//调用sleep()原调用线程快于赋值
} catch (InterruptedException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
}
if (MultiThreadClient.UserNameCheck) {
if (new String(c.getPassword()).equals(new String((d.getPassword())))) {
/*
* 用getPassword() 代替, 返回char[] 数组类型.因为是char[]类型, 其equals方法是来自最原始的Object类,
* 其相当于"=="(比较两者的地址是否一致,即指向的内存是否相同). 所以永远都不会相等,即当遇到数组类型时,不能用equals方法来比较.
* 应该把char[] 类型转化为String类型(因为String类型的equals被String类override过, 表示对比两者的内容是否相等).
*/
StringBuffer cABuffer = new StringBuffer();
cABuffer.append(a.getText());
cABuffer.append("%account&");
cABuffer.append(b.getText());
cABuffer.append("%account&");
cABuffer.append(new String(c.getPassword()));
MultiThreadClient.send(cABuffer.toString());
g.dispose();
JOptionPane.showMessageDialog(null, "创建成功", "提示", JOptionPane.INFORMATION_MESSAGE);
TheSignupPage.VCodeshow.setText(Code.randomCode());
} else {
JOptionPane.showMessageDialog(null, "两次密码不相同", "提示", JOptionPane.INFORMATION_MESSAGE);
TheSignupPage.VCodeshow.setText(Code.randomCode());
}
} else {
JOptionPane.showMessageDialog(null, "账号已存在", "提示", JOptionPane.INFORMATION_MESSAGE);
TheSignupPage.VCodeshow.setText(Code.randomCode());
}
} else {
JOptionPane.showMessageDialog(null, "验证码错误", "警告", JOptionPane.WARNING_MESSAGE);
TheSignupPage.VCodeshow.setText(Code.randomCode());
}
} else {
JOptionPane.showMessageDialog(null, "密码长度大于10或者小于5", "提示", JOptionPane.INFORMATION_MESSAGE);
TheSignupPage.VCodeshow.setText(Code.randomCode());
}
} else {
JOptionPane.showMessageDialog(null, "账号长度大于10或者小于5", "提示", JOptionPane.INFORMATION_MESSAGE);
TheSignupPage.VCodeshow.setText(Code.randomCode());
}
}
}
登录功能:
登录界面
下拉框为数据库中已注册账号,注册账号后也会自动添加至此。
密码框有密码明示和隐藏两种功能。
界面代码这里我做的太复杂了又是做几个panel又是几个函数处理,大概说一下,我这里开始用了一个BorderLayout布局分为五个区域,五个区域又分别放了五个panel,五个函数处理panel后返回panel。这样太过复杂,就一个空布局设置自己添加就行了。
public class QQLogin {
public static JFrame jf = new JFrame();
public static void initLogin() throws SQLException {
jf.setSize(426, 300);
jf.setLocation(497, 242);
jf.setUndecorated(true);
jf.setResizable(true);// 禁止改变窗口大小
jf.setLayout(new BorderLayout());
JPanel panel_north = CreatePanel.CreateNorthPanel(jf);
jf.add(panel_north, BorderLayout.PAGE_START);
JPanel panel_center = CreatePanel.CreateCenterpanel(jf);
jf.add(panel_center, BorderLayout.CENTER);// 中间
JPanel panel_west = CreatePanel.CreateWestPanel();
jf.add(panel_west, BorderLayout.LINE_START);
JPanel panel_south = CreatePanel.CreateSouthPanel();
jf.add(panel_south, BorderLayout.PAGE_END);
JPanel panel_east = CreatePanel.CreateEastPanel();
jf.add(panel_east, BorderLayout.LINE_END);
jf.setVisible(true);
}
}
创造界面的函数:
public class CreatePanel {
private static LoginListener ll = null;
static JComboBox<Object> jcoCenter ;
public static JPanel CreateNorthPanel(JFrame jf) {
JPanel panel = new JPanel();
panel.setLayout(null);
panel.setPreferredSize(new Dimension(0, 140));
ImageIcon image = new ImageIcon("picture/雷虎.jpg");
JLabel background = new JLabel(image);
background.setBounds(0, 0, 426, image.getIconHeight());//getIconHeight()获取图标的高度。
panel.add(background);
JButton out = new JButton(new ImageIcon("picture/退出1.png"));
out.setBounds(397, 0, 32, 32);
out.setRolloverIcon(new ImageIcon("picture/退出副本 2.png"));
//setRolloverIcon()设置当光标移动到按钮上时显示的图像
out.setBorderPainted(false);
// 是否画边框,如果用自定义图片做按钮背景可以设为 false。
panel.add(out);
out.addActionListener(event->jf.dispose());
return panel;
}
public static JPanel CreateWestPanel() {
JPanel panel = new JPanel();
panel.setLayout(null);
panel.setPreferredSize(new Dimension(130, 0));//setPreferredSize仅仅是设置最好的大小
ImageIcon image = new ImageIcon("picture/空白图2.jpg");//空白图,标签图(120,110)
JLabel background = new JLabel(image);
background.setBounds(0, 0, 110, 110);
panel.add(background);
return panel;//上面图片背景
}
public static JPanel CreateCenterpanel(JFrame jf) throws SQLException {
JPanel panel = new JPanel();
panel.setLayout(null);
jcoCenter = new JComboBox<Object>();
panel.add(jcoCenter);
jcoCenter.setEditable(true);
jcoCenter.setBounds(0, 15, 175, 30);
jcoCenter.setFont(new Font("Calibri", 0, 13));
//jcoCenter账号框
ImageIcon image = new ImageIcon("picture/密码不可见.png");// 密码框中的密码显示按钮
JButton jbu = new JButton(image);
jbu.setToolTipText("显示密码");
jbu.setPreferredSize(new Dimension(26, 26));
jbu.setBorderPainted(false);
JPasswordField jpaCenter = new JPasswordField();
jpaCenter.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));
jpaCenter.setBounds(0, 44, 175, 30);
jpaCenter.setPreferredSize(new Dimension(185, 25));
jpaCenter.add(jbu);
panel.add(jpaCenter);
// jpaCenter密码框不可见
ImageIcon image2 = new ImageIcon("picture/密码可见2.png");//密码框中的密码显示按钮
JButton jbu2 = new JButton(image2);
jbu2.setToolTipText("隐藏密码");
jbu2.setPreferredSize(new Dimension(26,26));
jbu2.setBorderPainted(false);
JTextField password = new JTextField();
password.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));
password.setBounds(0, 44, 175, 30);
password.setPreferredSize(new Dimension(185, 25));
password.add(jbu2);
password.setVisible(false);
panel.add(password);
//密码显示框
jbu.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO 自动生成的方法存根
jpaCenter.setVisible(false);
String g=new String(jpaCenter.getPassword());
password.setText(g);
password.setVisible(true);
}
});
jbu2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO 自动生成的方法存根
password.setVisible(false);
String a=new String(password.getText());
jpaCenter.setText(a);
jpaCenter.setVisible(true);
}
});//从明示到隐藏
JCheckBox jch1 = new JCheckBox("记住密码");
jch1.setFocusPainted(false);
jch1.setFont(new Font("宋体", 0, 13));
jch1.setBounds(0, 85, 80, 20);
panel.add(jch1);
JCheckBox jch2 = new JCheckBox("自动登录");
jch2.setFocusPainted(false);
jch2.setFont(new Font("宋体", 0, 12));
jch2.setBounds(100, 85, 80, 20);
panel.add(jch2);
ll = new LoginListener(jcoCenter, jpaCenter,password,jf);
return panel;
}
public static JPanel CreateEastPanel() {
JPanel panel = new JPanel();
panel.setLayout(null);
panel.setPreferredSize(new Dimension(100, 0));
JLabel regeist = new JLabel("注册账号");
regeist.setForeground(new Color(100, 149, 238));
regeist.setBounds(0, 13, 70, 30);
regeist.setFont(new Font("宋体", 0, 12));
JLabel regetpwd = new JLabel("找回密码");
regetpwd.setForeground(new Color(100, 149, 238));
regetpwd.setBounds(0, 43, 70, 30);
regetpwd.setFont(new Font("宋体", 0, 12));
panel.add(regetpwd);
panel.add(regeist);
regeist.addMouseListener(new MouseAdapter() {
//注册账号这里设置监听点击就弹出注册页面
public void mouseClicked(MouseEvent e) {
// TODO 自动生成的方法存根
new TheSignupPage();
}
});
return panel;
}
public static JPanel CreateSouthPanel() {
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(0, 51));
panel.setLayout(null);
JButton jble = new JButton(new ImageIcon("picture/爱心.jpg"));
jble.setPreferredSize(new Dimension(40, 40));
jble.setFocusPainted(false);
jble.setRolloverIcon(new ImageIcon("picture/爱心副本.jpg"));
jble.setBorderPainted(false);//为false
jble.setContentAreaFilled(false);
jble.setBounds(0, 10, 40, 40);
jble.setToolTipText("多账号登录");//在控件上显示提示信息
ImageIcon image = new ImageIcon("picture/登录.png");
JButton jb = new JButton("登 录", image);//175;40
jb.setFont(new Font("宋体", 0, 13));
jb.setBounds(130, 0, 175, 40);
jb.setHorizontalTextPosition(SwingConstants.CENTER);
//setHorizontalTextPosition,登录相对于图像的位置
jb.setFocusPainted(false);
jb.setBorderPainted(false);
jb.setContentAreaFilled(false);
jb.setRolloverIcon(new ImageIcon("picture/登录 - 副本.png"));
JButton jbri = new JButton(new ImageIcon("picture/二维码登录2.png"));
jbri.setBounds(380, 10, 40, 40);
jbri.setFocusPainted(false);
jbri.setBorderPainted(false);
jbri.setContentAreaFilled(false);
jbri.setRolloverIcon(new ImageIcon("picture/二维码登录.png"));
jbri.setToolTipText("二维码登录");
panel.add(jble);
panel.add(jb);
panel.add(jbri);
jb.addActionListener(ll);
//登录按钮加入监听
//登录界面
return panel;
}
}
登录页面的监听和服务端交互
public class LoginListener implements ActionListener {
private JComboBox<Object> jco;
private JPasswordField jpa;
private JTextField jt;
static String name;
public LoginListener(JComboBox<Object> jco, JPasswordField jpa, JTextField jt, JFrame jf) {
super();// 调用actionPerformed(ActionEvent e)
this.jco = jco;// actionPerformed()中使用的jpa为LoginListener()调入的
this.jpa = jpa;
this.jt = jt;
}
public void actionPerformed(ActionEvent e) {
name = (String) jco.getSelectedItem();
String pwd = new String(jpa.getPassword());
String pwd2 = new String(jt.getText());
StringBuffer acountcheck = new StringBuffer();
acountcheck.append(name);
acountcheck.append("!#");
acountcheck.append(pwd);
acountcheck.append("!#");
acountcheck.append(pwd2);
MultiThreadClient.send(acountcheck.toString());
//发送账号密码给服务器调用客户端的send方法
}
}
聊天功能:
建议先实现群聊再实现私聊。此聊天进入后默认群聊模式。
聊天页面:
选择群聊模式消息文件发送给所有人。
私聊则需在下拉框选择对象后发送消息,此下拉框实时更新既用户上线则会自动添加,有用户下线则自动删除下线用户。
创造界面的代码:
public class TheChatPanel {
static TheChatListener theChatListener;
JComboBox<Object> jcoCenter = new JComboBox<Object>();
JPanel panel;
JTextArea out;
JPanel showJPanel;
JButton breakButton;
JButton send;
JTextArea input;
JScrollPane c2;
public TheChatPanel() {
JFrame jfn = new JFrame("当前聊天框所属账号为:"+LoginListener.name);
jfn.setSize(865, 590);
jfn.setLocation(600, 100);
panel = new JPanel();
panel.setLayout(null);
Font font = new Font("楷体", Font.BOLD, 18);
out = new JTextArea();
JScrollPane c1 = new JScrollPane(out);
out.setLineWrap(true);// 单词边界自动换行
out.setFont(font);
out.setEditable(false);
c1.setSize(700, 350);
c1.setLocation(0, 0);
showJPanel = new JPanel();
showJPanel.setLayout(null);
showJPanel.setSize(150, 550);
showJPanel.setLocation(700, 0);
JLabel label = new JLabel("请选择聊天模式");
label.setBounds(0, 10, 150, 30);
label.setFont(new Font("楷体", 0, 18));
ButtonGroup ChatState = new ButtonGroup();
JRadioButton PrivateChat = new JRadioButton("私聊");
JRadioButton GroupChat = new JRadioButton("群聊", true);
ChatState.add(PrivateChat);
ChatState.add(GroupChat);
GroupChat.setBounds(30, 40, 120, 30);
GroupChat.setFocusable(false);
GroupChat.setFont(new Font("楷体", 0, 18));
GroupChat.addActionListener(e -> {
jcoCenter.setVisible(false);
JOptionPane.showMessageDialog(jfn, "当前模式为群聊模式将会把信息发送给所有用户", "提示", JOptionPane.INFORMATION_MESSAGE);
});
PrivateChat.setBounds(30, 70, 120, 30);
PrivateChat.setFocusable(false);
PrivateChat.setFont(new Font("楷体", 0, 18));
PrivateChat.addActionListener(e -> {
jcoCenter.setVisible(true);
});
jcoCenter.addItem("请选择聊天对象");
jcoCenter.setEditable(false);
jcoCenter.setVisible(false);
jcoCenter.setBounds(0, 100, 150, 30);
jcoCenter.setFont(new Font("楷体", 0, 16));
showJPanel.add(label);
showJPanel.add(GroupChat);
showJPanel.add(PrivateChat);
showJPanel.add(jcoCenter);
JLabel ai = new JLabel("聊天输入框↓↓↓", JLabel.CENTER);
ai.setFont(font);
ai.setBounds(0, 350, 700, 30);
send = new JButton("发送");
send.setBounds(610, 0, 80, 30);
send.setFont(font);
send.setFocusable(false);
ai.add(send);
panel.add(ai);
input = new JTextArea();
input.setLineWrap(true);// 单词边界自动换行
input.setFont(font);
c2 = new JScrollPane(input);
c2.setSize(702, 170);
c2.setLocation(0, 380);
new Main2();
Main2.Filename(input);
theChatListener = new TheChatListener(jfn, out, input, jcoCenter, PrivateChat, GroupChat);
panel.add(c1);
panel.add(c2);
panel.add(showJPanel);
jfn.add(panel);
jfn.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfn.setUndecorated(false);
jfn.setResizable(false);// 禁止改变窗口大小
jfn.setVisible(true);
jfn.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
MultiThreadClient.send(new StringBuffer(LoginListener.name).append("#13@").toString());
}
});
send.addActionListener(theChatListener);
//发送按钮添加监听。
}
}
发送按钮实现监听:
包含判断群聊是否有用户在线,私聊是否选择了用户,判断是消息还是文件。
public class TheChatListener implements ActionListener {
public JTextArea jte = new JTextArea();// 聊天记录显示框
public JTextArea jte2 = new JTextArea();
StringBuffer ChatRecord;
JComboBox<Object> SelectUser;
String dateString;
JRadioButton PrivateChat;
JRadioButton GroupChat;
JFrame thechatFrame;
public TheChatListener(JFrame aa, JTextArea jteOUT, JTextArea jtePUT, JComboBox<Object> jco,
JRadioButton PrivateChat, JRadioButton GroupChat) {
super();
this.jte = jteOUT;
this.jte2 = jtePUT;
this.SelectUser = jco;
this.PrivateChat = PrivateChat;
this.GroupChat = GroupChat;
this.thechatFrame = aa;
}
public void actionPerformed(ActionEvent e) {
String check = jte2.getText();
int usernumber = SelectUser.getItemCount();
if (check == null || check.trim().length() == 0) {
// check.trim().length()==0 删除所有空格后还剩长度,输入空格也不行。
JOptionPane.showMessageDialog(thechatFrame, "发送信息为空或者为全空格", "提示", JOptionPane.INFORMATION_MESSAGE);
} else if (GroupChat.isSelected()) {
// 群聊模式
if (usernumber == 1) {
// 无人在线
JOptionPane.showMessageDialog(thechatFrame, "当前无用户在线无法群聊", "提示", JOptionPane.INFORMATION_MESSAGE);
} else if (check.contains("发送文件为:")) {
String FileAddress[] = check.split("发送文件为:");
String filename[] = new String(FileAddress[1]).split("\\\\");
MultiThreadClient.sendFile(FileAddress[1], LoginListener.name, "alluser",
filename[filename.length - 1]);
setusermessge("服务器提示", "你向全部用户发送了“" + filename[filename.length - 1]+"”");
jte2.setText("");
} else {
// 有人在线
StringBuffer Message = new StringBuffer();
Message.append(LoginListener.name);
Message.append("%&");
Message.append("alluser");
Message.append("%&");
Message.append(check);
MultiThreadClient.send(Message.toString());
setusermessge("当前用户-" + LoginListener.name, check);
jte2.setText("");
}
} else {
// 私聊模式
String item = (String) SelectUser.getSelectedItem();
if (!item.equals("请选择聊天对象")) {
if (check.contains("发送文件为:")) {
String FileAddress[] = check.split("发送文件为:");
String filename[] = new String(FileAddress[1]).split("\\\\");
MultiThreadClient.sendFile(FileAddress[1], LoginListener.name, item, filename[filename.length - 1]);
setusermessge("服务器提示" , "你向" + item + "用户发送了“" + filename[filename.length - 1]+"”");
jte2.setText("");
} else {
// 发送为消息
StringBuffer Message = new StringBuffer();
Message.append(LoginListener.name);
Message.append("%&");
Message.append(item);
Message.append("%&");
Message.append(check);
MultiThreadClient.send(Message.toString());
setusermessge("当前用户-" + LoginListener.name, check);
jte2.setText("");
}
} else if (usernumber == 1) {
JOptionPane.showMessageDialog(thechatFrame, "当前无用户在线请稍后尝试", "提示", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(thechatFrame, "请选择聊天对象后发送", "提示", JOptionPane.INFORMATION_MESSAGE);
}
}
}
public void setusermessge(String user, String usermessage) {
//设置消息面板的消息
LocalDateTime dateTime = LocalDateTime.now();
dateString = String.valueOf(dateTime).substring(0, 19);
// substring(0,19)截取二十个,原字符串带秒以后的单位太长
ChatRecord = new StringBuffer();
ChatRecord.append(jte.getText());
ChatRecord.append(" " + dateString + "\n");
ChatRecord.append(user + ":" + "\n");
ChatRecord.append(" " + "“");
ChatRecord.append(usermessage);
ChatRecord.append("”" + "\n");
jte.setText(new String(ChatRecord));
}
}
文件传输:
发送按钮实现监听已经包含文件传输的功能,在这说一下一个小操作就是直接拉文件进入聊天发送框的代码,
这里实现监听和运用了监听适配器。
public class Main2 {
public static void Filename(JTextArea textArea) {
// 创建拖拽目标监听器
DropTargetListener listener = new DropTargetListenerImpl5(textArea);
// 在 textArea 上注册拖拽目标监听器
new DropTarget(textArea, DnDConstants.ACTION_COPY_OR_MOVE, listener, true);
}
private static class DropTargetListenerImpl5 extends DropTargetAdapter {
private JTextArea textArea;
public DropTargetListenerImpl5(JTextArea textArea) {
this.textArea = textArea;
}
public void drop(DropTargetDropEvent dtde) {
// TODO 自动生成的方法存根
// 一般情况下只需要关心此方法的回调
boolean isAccept = false;
try {
/*
* 1. 文件: 判断拖拽目标是否支持文件列表数据(即拖拽的是否是文件或文件夹, 支持同时拖拽多个)
*/
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
// 接收拖拽目标数据
dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
isAccept = true;
// 以文件集合的形式获取数据
List<File> files = (List<File>) dtde.getTransferable()
.getTransferData(DataFlavor.javaFileListFlavor);
// 把文件路径输出到文本区域
if (files != null && files.size() > 0) {
StringBuilder filePaths = new StringBuilder();
for (File file : files) {
filePaths.append("发送文件为:" + file.getAbsolutePath());
}
textArea.append(filePaths.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 如果此次拖拽的数据是被接受的, 则必须设置拖拽完成(否则可能会看到拖拽目标返回原位置, 造成视觉上以为是不支持拖拽的错误效果)
if (isAccept) {
dtde.dropComplete(true);
}
}
}
}
服务器端:
主要处理来自客户端的一些操作和请求,以及一些准备操作。
public class MultiThreadServer {
public static boolean isStart = false;
private static Map<String, UserInformation> clientMap = new ConcurrentHashMap<>();
Methods methods = new Methods();
static long FileIdentification = 256341875;
static long SENDMSG = 951636247;
public MultiThreadServer() {
new Thread(new reaciveUser()).start();
}
class UserInformation {
String name;
Socket clientSocket;
UserInformation(String name, Socket clientSocket) {
this.name = name;
this.clientSocket = clientSocket;
}
}
class reaciveUser implements Runnable {
public void run() {
ServerSocket serverSocket = null;
try {
// serverSocket = null;
serverSocket = new ServerSocket(6666);
if (serverSocket != null) {
isStart = true;
}
for (int i = 0; i < 20; i++) {
Socket client = null;
client = serverSocket.accept();
// 每当有用户连接,新建线程处理
new Thread(new user(client)).start();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
serverSocket.close();
isStart = false;
} catch (IOException e) {
e.printStackTrace();
}
}
}
class user implements Runnable {
private Socket client;
private user(Socket client) {
this.client = client;
}
public void run() {
try {
while (isStart) { // 循环接受消息
DataInputStream dis = new DataInputStream(client.getInputStream());
long aaLong = dis.readLong();
if (aaLong == 256341875) {
// 文件接受
String sendString = dis.readUTF();
String reciver = dis.readUTF();
String FileName = dis.readUTF();
long Long = dis.readLong();// 文件长度结束标志
File file = new File("d:\\A服务器接受文件");
if (!file.exists()) {
file.mkdirs();
}
FileOutputStream aa = new FileOutputStream(file + "\\" + FileName);
int length = 0;
long filelong = 0;
byte[] buf = new byte[1024];
while ((length = dis.read(buf)) != -1) {
filelong = filelong + length;
aa.write(buf, 0, length);
if (filelong == Long) {
filelong = 0;
break;
}
}
aa.close();
if (reciver.equals("alluser")) {
Socket currentSocket = clientMap.get(sendString).clientSocket;
for (UserInformation clientInformation : clientMap.values()) {
Socket clientSocket = clientInformation.clientSocket;
if (!(clientSocket == currentSocket)) {
long FileIdentification = 256341875;
FileInputStream sendfile = new FileInputStream(file + "\\" + FileName);
DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());
dos.writeLong(FileIdentification);
dos.writeUTF(clientMap.get(sendString).name);
dos.writeUTF(FileName);
dos.writeLong(Long);
int lth = 0;
byte[] bu = new byte[1024];
while ((lth = sendfile.read(bu)) != -1) {
filelong = filelong + length;
dos.write(bu, 0, lth);
if (filelong == Long) {
filelong = 0;
break;
}
}
sendfile.close();
}
}
} else {
for (UserInformation clientInformation : clientMap.values()) {
if (reciver.equals(clientInformation.name)) {
Socket socket2 = clientInformation.clientSocket;
FileInputStream sendfile = new FileInputStream(file + "\\" + FileName);
DataOutputStream dos = new DataOutputStream(socket2.getOutputStream());
dos.writeLong(FileIdentification);
dos.writeUTF(clientMap.get(sendString).name);
dos.writeUTF(FileName);
dos.writeLong(Long);
int leth = 0;
byte[] bu = new byte[1024];
while ((leth = sendfile.read(bu)) != -1) {
dos.write(bu, 0, leth);
}
sendfile.close();
}
}
}
} else if (aaLong == 951636247) {
String mes = dis.readUTF();
try {
// 面板展示账号查询
if (mes.contains("#*@")) {
String OnlineUsers = methods.Project();
sendMsg(client, OnlineUsers);
}
} catch (SQLException e1) {
e1.printStackTrace();
}
// 服务器注册用户
if (mes.contains("%account&")) {
String creataccount[] = new String[10];
creataccount = mes.split("%account&");
String username = creataccount[0].toString();
String account = creataccount[1].toString();
String password = creataccount[2].toString();
try {
(new Methods()).CreateAccount(username, account, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 登录账号查询
if (mes.contains("!#")) {
String cc[] = mes.split("!#");
if (!clientMap.containsKey(cc[0])) {
try {
int len = cc.length;
Boolean b1 = methods.findUser(cc[0], cc[1]);
Boolean b2 = methods.findUser(cc[0], cc[len - 1]);
if (b1 || b2) {
String username = methods.FindName(cc[0]);
sendMsg(client, "#!true");
StringBuffer user = new StringBuffer();
if (!clientMap.isEmpty()) {
// 已在线用户
for (UserInformation key : clientMap.values()) {
user.append(key.name);
user.append("##");
}
sendMsg(client, user.toString());
sendMsgToAll(new StringBuffer(username).append("1!@#&").toString());
}
UserInformation userInformation = new UserInformation(username, client);
clientMap.put(cc[0], userInformation);
} else {
// 账号登录错误
sendMsg(client, "#!false");
}
} catch (SQLException e) {
e.printStackTrace();
}
} else {
sendMsg(client, "#!existing");
}
} // 账号查询
if (mes.contains("#135@")) {
String userName[] = new String[2];
userName = mes.split("#135@");
try {
if ((new Methods()).CheckAccoun(userName[0].toString())) {
// 账号未被注册
sendMsg(client, "++true");
} else {
// 账号已被注册
sendMsg(client, "++false");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
System.out.println("查询名称错误");
}
}
// 信息发送
if (mes.contains("%&")) {
String message[] = new String[10];
message = mes.split("%&");
int len = message.length;
String senduser = clientMap.get(message[0].toString()).name;
String reciveuser = message[1].toString();
// reciveuser为接收者名字
StringBuffer Message = new StringBuffer();
Message.append(senduser);
Message.append("%&");
Message.append(message[len - 1].toString());
if (reciveuser.equals("alluser")) {
Socket currentSocket = clientMap.get(message[0].toString()).clientSocket;
for (UserInformation clientInformation : clientMap.values()) {
Socket clientSocket = clientInformation.clientSocket;
if (!(clientSocket == currentSocket)) {
DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());
dos.writeLong(SENDMSG);
dos.writeUTF(Message.toString());
}
}
} else {
for (UserInformation clientInformation : clientMap.values()) {
if (reciveuser.equals(clientInformation.name)) {
Socket socket2 = clientInformation.clientSocket;
sendMsg(socket2, Message.toString());
}
}
}
}
if (mes.contains("#13@")) {
String closeuser[] = new String[2];
closeuser = mes.split("#13@");
// 下线用户
String user = closeuser[0].toString();
Socket closeSocket = clientMap.get(user).clientSocket;
String userbye = clientMap.get(closeuser[0].toString()).name;
sendMsg(closeSocket, "#13@");
clientMap.remove(closeuser[0].toString());
if (!(clientMap.size() == 0)) {
sendMsgToAll(new StringBuffer(userbye).append("#10*").toString());
}
isStart = false;
client.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
// System.out.println("客户端连接异常中断");
}
}
public void sendMsg(Socket usersSocket, String str) {
try {
DataOutputStream dos = new DataOutputStream(usersSocket.getOutputStream());
dos.writeLong(SENDMSG);
dos.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendMsgToAll(String str) {
try {
for (UserInformation clientInformation : clientMap.values()) {
Socket clientSocket = clientInformation.clientSocket;
DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());
dos.writeLong(SENDMSG);
dos.writeUTF(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MultiThreadServer();
}
}
客户端
主要接受来自服务器的消息然后进行处理
public class MultiThreadClient {
static Socket s;
boolean isConn = false;
TheChatPanel chat;
QQLogin qqLogin;
String aa[] = new String[100];
static boolean UserNameCheck = false;
static long SENDMSG = 951636247;
static long FileIdentification = 256341875;
public MultiThreadClient() {
try {
s = new Socket("127.0.0.1", 6666);
send("#*@");// 发送消息获取列表
isConn = true;
new Thread(new reaciveMsg()).start();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
static void sendFile(String FileAddress, String sendname, String ReceviceUser, String filename) {
DataOutputStream dos;
try {
File file2 = new File(FileAddress);
try (FileInputStream file = new FileInputStream(file2)) {
dos = new DataOutputStream(s.getOutputStream());
dos.writeLong(FileIdentification);
dos.writeUTF(sendname);
dos.writeUTF(ReceviceUser);
dos.writeUTF(filename);
dos.writeLong(file2.length());
int length = 0;
byte[] buf = new byte[1024];
while ((length = file.read(buf)) != -1) {
dos.write(buf, 0, length);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
static void send(String str) {
DataOutputStream dos;
try {
dos = new DataOutputStream(s.getOutputStream());
dos.writeLong(SENDMSG);
dos.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}
class reaciveMsg implements Runnable {
String OnlineUsers;
reaciveMsg() {
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
QQLogin.initLogin();
} catch (SQLException e2) {
e2.printStackTrace();
}
}
public void run() {
try {
while (isConn) { // 循环接受消息
DataInputStream dis = new DataInputStream(s.getInputStream());
long aaLong = dis.readLong();
if (aaLong == 256341875) {
// 文件接受
String sendString = dis.readUTF();
String FileName = dis.readUTF();
long filelength = dis.readLong();
File file = new File("d:\\A客户端接受文件");
if (!file.exists()) {
file.mkdirs();
}
FileOutputStream aa = new FileOutputStream(file + "\\" + FileName);
int length = 0;
byte[] buf = new byte[1024];
long filelong = 0;
while ((length = dis.read(buf)) != -1) {
filelong = filelong + length;
aa.write(buf, 0, length);
if (filelong == filelength) {
filelength = 0;
break;
}
}
aa.close();
TheChatPanel.theChatListener.setusermessge("服务器提示", sendString+"向你发来文件“" + FileName + "”存储于: " + file);
// 接收文字信息
} else if (aaLong == 951636247) {
String str = dis.readUTF();
if (str.contains("#337@")) {
OnlineUsers = str;
try {
String user[] = new Methods().Project().split("#337@");
for (int i = 0; i < user.length; i++) {
CreatePanel.jcoCenter.addItem(user[i]);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
if (str.equals("#!existing")) {
JOptionPane.showMessageDialog(QQLogin.jf, "该用户已经登录");
} else {
if (str.equals("#!true")) {
QQLogin.jf.dispose();
chat = new TheChatPanel();
JOptionPane.showMessageDialog(null, "登录成功,系统开启默认群聊模式,如有需要请自行更改");
} else if (str.equals("#!false")) {
JOptionPane.showMessageDialog(QQLogin.jf, "你输入的账户名和密码不正确请重新输入!");
}
}
if (str.contains("##")) {
aa = str.split("##");
for (int i = 0; i < aa.length; i++) {
chat.jcoCenter.addItem(aa[i].toString());
}
} else if (str.contains("1!@#&")) {
String OnlineUsers[] = new String[50];
OnlineUsers = str.split("1!@#&");
chat.jcoCenter.addItem(OnlineUsers[0].toString());
}
if (str.contains("%&")) {
// 设置消息
String message[] = new String[10];
message = str.split("%&");
int len = message.length;
String userString = message[0].toString();
String XinString = message[len - 1].toString();
TheChatPanel.theChatListener.setusermessge(userString, XinString);
}
// 本客户端关闭
if (str.contains("#13@")) {
isConn = false;
s.close();
}
// 其他用户下线
if (str.contains("#10*")) {
String message[] = new String[10];
message = str.split("#10*");
String RemoveUser = message[0].toString();
chat.jcoCenter.removeItem(RemoveUser);
}
if (str.equals("++true")) {
UserNameCheck = true;
} else if (str.equals("++false")) {
UserNameCheck = false;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws UnknownHostException, IOException, SQLException {
// TODO 自动生成的方法存根
new MultiThreadClient();
}
}
数据库的方法
结构有点混乱,当时将验证码方法写在了此类中,并且数据方法写的也不简便部分重复,例如数据的连接可以放外部。
class Methods {
PreparedStatement prestmt = null;
Connection conn = null;
ResultSet rs = null;
boolean aa;
String ur1 = "jdbc:mysql://localhost:3306/jdbc";
String user = "root";
String password = "200210";
public String randomCode() {
String str = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int index;// = -1;// 存储字符串的下标
char c;// 存储下标对应的字符
StringBuffer code = new StringBuffer();// 创建StringBuffer对象code,存储生成的验证码
Random r = new Random();// 创建随机数对象r
for (int i = 0; i < 4; i++) {
index = r.nextInt(str.length());// nextInt生成随机数[0,str.length()]
c = str.charAt(index);// 找到index位置的字符
code.append(c);// 添加字符
}
String randomString = new String(code);
return randomString;
}
public Boolean findUser(String qqnumber, String pwd) throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(ur1, user, password);
String sql = "select * from tb_qquser where qqnumber = ? and password = ?";
prestmt = conn.prepareStatement(sql);
prestmt.setString(1, qqnumber);
prestmt.setString(2, pwd);
rs = prestmt.executeQuery();
if (rs.next()) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {
rs.close();
}
if (prestmt != null) {
prestmt.close();
}
if (conn != null) {
conn.close();
}
}
return false;
}
boolean CreateAccount(String username, String qqnumber, String passwordtext)
throws ClassNotFoundException, SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(ur1, user, password);
String sql = "insert into tb_qquser(qqnumber,password,username)values(?,?,?);";
prestmt = conn.prepareStatement(sql);
prestmt.setString(1, qqnumber);
prestmt.setString(2, passwordtext);
prestmt.setString(3, username);
// TODO 自动生成的方法存根
aa = prestmt.execute();
if (aa) {
// next() 获得匹配元素集合中每个元素紧邻的同胞元素。如果提供选择器,则取回匹配该选择器的下一个同胞元素
return true;
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {
rs.close();
}
if (prestmt != null) {
prestmt.close();
}
if (conn != null) {
conn.close();
}
}
return false;
}
boolean CheckAccoun(String qqname) throws ClassNotFoundException, SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(ur1, user, password);
String sql1 = "select qqnumber from tb_qquser where qqnumber=? ;";
prestmt = conn.prepareStatement(sql1);
prestmt.setString(1, qqname);
rs = prestmt.executeQuery();
if (rs.next()) {
return false;
} else {
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {
rs.close();
}
if (prestmt != null) {
prestmt.close();
}
if (conn != null) {
conn.close();
}
}
// 原因是finally内不建议使用return,因为函数的执行过程是,在try中调用了
// return后,才会执行finally中的代码,所以finally中只能放一些资源释放类的代码段,不能带return
return true;
}
String Project() throws SQLException {
Connection conn = null;
java.sql.Statement stmt = null;
ResultSet rs = null;
StringBuffer Pstring = new StringBuffer();
String aaString = new String();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(ur1, user, password);
stmt = conn.createStatement();
String sql2 = "select qqnumber from tb_qquser;";
rs = stmt.executeQuery(sql2);
while (rs.next()) {
String str = rs.getString("qqnumber");
Pstring.append(str);
Pstring.append("#337@");
//Pstring.append(",");
aaString = new String(Pstring);
}
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
} finally {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}
return aaString;
}
String FindName(String account) throws SQLException {
Connection conn = null;
java.sql.Statement stmt = null;
ResultSet rs = null;
String username = new String();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(ur1, user, password);
String sql2 = "select username from tb_qquser where qqnumber=?";
prestmt = conn.prepareStatement(sql2);
prestmt.setString(1, account);
rs = prestmt.executeQuery();
while (rs.next()) {
username = rs.getString("username");
}
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
} finally {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}
return username;
}
}
作者留言:
首先此项目结构有点混乱,这是因为是一点点堆积起来的刚开始就没想整体都是实现一个功能后再加另外一个功能,想到一个什么东西就添加什么,方法函数在里面被东调用西调用的,虽然分了很多类写,但是导致调用参数不是很方便,因为第一次写这种东西没经验途中结构也改了两次,在后面时间不够,单纯为了实现功能无所不用极其,在有些地方特别紊乱,但是耐心看还是能看懂的。
收获感受:
第一次自己独立完成这种项目,首先自己还是有很多收获的,对知识的运用也有了一定的了解,其次自己找错解决问题的能力得到了提示,自己也学会去寻找错误看懂报错然后自己解决错误 。写一个项目对自己各方面都有提升,在自己掌握基本知识的情况下去写一个项目不但能巩固知识还能学会对知识整体运用。
对项目最后的解说:
-
io流很重要运用起来。非常有必要学好io流。
首先在此项目中用io流的是DataOutputStream
和DataInputStream
,这是因为它的特性:
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
数据输出流允许应用程序以与机器无关方式将Java基本数据类型写到底层输出流。
在区分消息还是文件是运用了这个特性,定义两个长整型变量每次发送首先写入其中,一个代表文件一个代表服务器操作和消息。在服务器区分消息还是用户数据时是将特殊字符加入其中,例如加入
!# 服务器接受到后意味着登录操作查询,客户端发过来就是 “账号!#密码” 调用字符串方法split()
分割开然后进入数据库查询再操作。 -
其次项目中处理了很多细节,运用了很多弹窗,例如:登录时密码错误会有弹窗显示,注册时账号和密码都有长度要求,超过长度都会有弹窗警告,登录进去默认群聊也会有弹窗显示,私聊切换群聊也会有弹窗提示等等。
-
其次就是账号登入怎么联系到名称和
socket
的,这三者的联系在这里运用结合的知识,
private static Map<String, UserInformation> clientMap = new ConcurrentHashMap<>();
创建一个类 UserInformation封装名字和socket
,运用键值对应,使得账号能找到名字和socket
, UserInformation对象使得名字也能找到socket
。
4.在本项目中还有很多小细节处理有兴趣可以去发掘,聊天框的关闭就代表用户下线,服务器就会将此用户下线消息发给所有在线用户。