最近由于电脑出了点问题,学校那边临近毕业又一直有事耽误了不少的时间,这里就直接把所有的代码粘贴上来。附上运行结果,我要开始下面数据库的学习了。
由于这次的demo涉及到了两个项目,这里就直接把两个项目的项目结构都粘贴上来,这个阶段的项目我都是用Eclipse来完成的,等到javaweb阶段可能我就要换IDEA了。因为就这两个工具来说。现在肯定是IDEA占的市场要大,但是Eclipse也拥有除了IDEA以外最大的市场份额,而且Eclipse与IDEA相比Eclipse是最原生的,作为初学者建议用Eclipse。

首先是两个项目中都相同的Message.java,作为一个实现了序列化接口的类,这里涉及到了序列化和反序列化的问题,不明白的话可以去看我的这一篇博文:对象流和序列化
package model;
import java.io.Serializable;
import java.util.List;
/**
* 消息对象 服务器和客户端的消息对象要完全一致(包名都要完全一致)才能反序列化
*
*/
public class Message implements Serializable{
private int type;//消息类型
//常量 常量一般是全大写 如果由多个单词构成的 就用下划线_ 分开
public static final int LOGIN = 1;//登录消息类型
public static final int FRIEND = 2;//刷新好友列表
public static final int MSG = 3;//一对一聊天的消息类型
public static final int GROUP = 4;//群聊
public static final int EXIT = 5;//退出
public static final int ERROR = 6;//错误提示
private String from; //发送消息的人
private String to; //接收消息的人
private String content; //消息的内容
private String time; //发送消息的时间
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
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;
}
private String username;
private String password;
private boolean success;//是否成功
private List<String> friends;
public List<String> getFriends() {
return friends;
}
public void setFriends(List<String> friends) {
this.friends = friends;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
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;
}
}
而后是客户端(qqClient)的线程类:MyThread.java:
package thread;
import java.io.IOException;
import java.net.Socket;
import model.Message;
import ui.Friends;
import ui.Talk;
import util.ObjectRW;
public class MyThread extends Thread{
private Socket socket;
private Friends friends;
public MyThread(Socket socket, Friends friends) {
super();
this.socket = socket;
this.friends = friends;
}
@Override
public void run() {
super.run();
while(true) {
try {
Message message = ObjectRW.readObject(socket);
if(message.getType() == Message.FRIEND) {
friends.refreshFriend(message.getFriends());
} else if(message.getType() == Message.MSG) {
//a给b发消息的时候 相对于b to就是消息的from
//显示消息对象
Talk talk = friends.getTalk(message.getFrom(), Message.MSG);
talk.showMessage(message);
talk.setVisible(true);
} else if(message.getType() == Message.GROUP) {
Talk talk = friends.getGroupTalk();
talk.showMessage(message);
talk.setVisible(true);
} else if(message.getType() == Message.EXIT) {
break;
} else if(message.getType() == Message.ERROR) {
Talk talk = friends.getTalk(message.getFrom(), Message.MSG);
talk.showMessage(message);
talk.setVisible(true);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}//关流
System.exit(0);//退出程序
}
}
客户端的3个ui界面,当然这里面也包含了数据交互:
好友界面,Friends.java:
package ui;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.Socket;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import model.Message;
import thread.MyThread;
import util.ObjectRW;
import javax.swing.JList;
import javax.swing.JButton;
public class Friends extends JFrame {
private JPanel contentPane;
private Socket socket;
private String curUser;
private JPanel panel = null;
private Talk group;//群聊窗口
private Map<String, Talk> talks = new Hashtable<String, Talk>();
/**
* Create the frame.
*/
public Friends(Socket socket,String curUser) {
this.curUser = curUser;
this.socket = socket;
setTitle(curUser);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setBounds(100, 100, 244, 520);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(new BorderLayout(0, 0));
panel = new JPanel();
panel.setLayout(new BorderLayout());
contentPane.add(panel, BorderLayout.CENTER);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
Message message = new Message();
message.setFrom(curUser);//下线的人
message.setType(Message.EXIT);//退出的消息类型
try {
ObjectRW.writeObject(socket, message);
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
JButton button = new JButton("群聊");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
group = getGroupTalk();
group.setVisible(true);
}
});
contentPane.add(button, BorderLayout.SOUTH);
//启动接收消息的线程
new MyThread(socket, this).start();
}
/**
* 刷新好友列表的方法
* @param friends
*/
public void refreshFriend(List<String> friends) {
//先将panel中的元素移除
panel.removeAll();
//好友列表中不显示自己
int index = 0;
//不包含自己的好友数据
String [] fri = new String[friends.size() - 1];
for (String string : friends) {
if(!string.equals(curUser)) {
fri[index] = string;
index++;
}
}
JList list = new JList(fri);
list.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
if(e.getClickCount() == 2) {//实现双击
//根据鼠标的位置确认点击的是第几个
int index = list.locationToIndex(e.getPoint());
//在数组中找到点击的是谁
Talk talk = getTalk(fri[index], Message.MSG);
//显示聊天窗口
talk.setVisible(true);
}
}
});
//滚动条
JScrollPane scrollPane = new JScrollPane(list);
panel.add(scrollPane);
//刷新一下
panel.repaint();
panel.updateUI();
}
/**
* 获取对话框
* @return
*/
public Talk getTalk(String to,int type) {
Talk talk = talks.get(to);
if(talk == null) {
talk = new Talk(socket, curUser, to, type);
talks.put(to, talk);
}
return talk;
}
/**
* 获取群聊窗口
* @return
*/
public Talk getGroupTalk() {
if(group == null) {
group = new Talk(socket, curUser, "群聊", Message.GROUP);
}
return group;
}
}
而后登录界面:Login.java
package ui;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import model.Message;
import util.ObjectRW;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import javax.swing.JButton;
public class Login extends JFrame {
private JPanel contentPane;
private JTextField username;
private JPasswordField password;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Login frame = new Login();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Login() {
setTitle("登录");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 282, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
JLabel label = new JLabel("账号");
label.setBounds(35, 40, 54, 15);
contentPane.add(label);
username = new JTextField();
username.setBounds(90, 37, 118, 21);
contentPane.add(username);
username.setColumns(10);
JLabel label_1 = new JLabel("密码");
label_1.setBounds(35, 104, 54, 15);
contentPane.add(label_1);
password = new JPasswordField();
password.setBounds(90, 101, 118, 21);
contentPane.add(password);
JButton button = new JButton("登录");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
Socket socket = new Socket("127.0.0.1", 9999);
//执行登录
Message message = new Message();
message.setType(Message.LOGIN);
message.setUsername(username.getText());
message.setPassword(new String(password.getPassword()));
//将对象写到服务器去
ObjectRW.writeObject(socket, message);
//接收服务器的响应,获取登录结果
try {
message = ObjectRW.readObject(socket);
if(message.isSuccess()) {//登录成功
// JOptionPane.showConfirmDialog(null, "登录成功", "提示", JOptionPane.YES_OPTION);
new Friends(socket, username.getText()).setVisible(true);//显示好友列表界面
Login.this.setVisible(false);//隐藏登录界面
}else {//登录失败
JOptionPane.showConfirmDialog(null, "登录失败", "提示", JOptionPane.YES_OPTION);
}
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
} catch (UnknownHostException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
button.setBounds(85, 170, 93, 23);
contentPane.add(button);
}
}
最后是聊天界面 Talk.java:
package ui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import model.Message;
import util.ObjectRW;
import util.StringUtils;
import javax.swing.JTextArea;
import javax.swing.JButton;
/**
* 聊天窗口
* @author HASEE
*
*/
public class Talk extends JFrame {
private JPanel contentPane;
private JTextArea show;
private JTextArea input;
private Socket socket;
private String curUser;
private String to;
private int type;// 可以是单聊,也可以是群聊的
/**
* Create the frame.
*/
public Talk(Socket socket,String curUser,String to,int type) {
this.type = type;
setTitle(to);
this.curUser = curUser;
this.socket = socket;
this.to = to;
//点击右上角的X不做任何事情
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setBounds(100, 100, 322, 448);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(new BorderLayout(0, 0));
//当点击了对话框右上角的X时,不是退出程序,只是隐藏
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
//设置不可见
Talk.this.setVisible(false);
}
});
JPanel panel = new JPanel();
contentPane.add(panel);
panel.setLayout(new BorderLayout(0, 0));
show = new JTextArea();
JScrollPane jScrollPane = new JScrollPane(show);
jScrollPane.setPreferredSize(new Dimension(0, 300));
panel.add(jScrollPane, BorderLayout.NORTH);
input = new JTextArea();
JScrollPane jScrollPane2 = new JScrollPane(input);
panel.add(jScrollPane2, BorderLayout.CENTER);
JButton button = new JButton("发送");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//构造消息对象
Message message = new Message();
message.setType(type);
message.setFrom(curUser);
message.setContent(input.getText());
message.setTo(to);
message.setTime(StringUtils.getCurrentTime());
try {
ObjectRW.writeObject(socket, message);
} catch (IOException e1) {
e1.printStackTrace();
}
//显示自己的消息
showMessage(message);
input.setText("");//清空输入框
}
});
panel.add(button, BorderLayout.SOUTH);
}
/**
* 显示消息
*/
public void showMessage(Message message) {
if(message.getType() == Message.MSG || message.getType() == Message.GROUP) {
show.append(message.getFrom() + "\t" + message.getTime() + "\r\n");
show.append(message.getContent() + "\n");
} else if(message.getType() == Message.ERROR) {
show.append("\t" + message.getContent() + "\n");
}
}
}
接着是客户端和服务器都相同的对象读写类:ObjectRW.java
package util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import model.Message;
/**
* 对象的读写操作
*
*
*/
public class ObjectRW {
/**
* 读取对象
* @param socket
* @return
* @throws ClassNotFoundException
* @throws IOException
*/
public static Message readObject(Socket socket) throws ClassNotFoundException, IOException {
return (Message) new ObjectInputStream(socket.getInputStream()).readObject();
}
/**
* 写对象出去
* @param socket
* @param obj
* @throws IOException
*/
public static void writeObject(Socket socket,Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
}
}
而后是客户端用来返回当前时间的类:StringUtils.java:
package util;
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringUtils {
/**
* 获取当前的时间
* 2020-02-12 14:23:30
* @return
*/
public static String getCurrentTime() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}
上面客户端的类基本写完了:
而后是服务器端(qqServer):
Main类:
package main;
import qqServer.Server;
public class Main {
public static void main(String[] args) {
new Server();
}
}
服务器的主类:
Server.java:
package qqServer;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import model.Message;
import thread.MyThread;
import util.ObjectRW;
/**
* 服务器
*
*
*/
public class Server {
private Map<String, Socket> sockets = new Hashtable<String, Socket>();
//线程池 假设支持10个人同时在线
private ExecutorService executorService = Executors.newFixedThreadPool(10);
public Server() {
try {
ServerSocket serverSocket = new ServerSocket(9999);
while(true) {
//客户端成功连接服务器
Socket socket = serverSocket.accept();
//服务器读取客户端发送的message对象执行登录
try {
Message message = ObjectRW.readObject(socket);
if(message.getType() == Message.LOGIN) {
//判断账号密码是否正确
//这里假设密码是123 就认为账号密码正确 即登录成功
//"123"这个常量肯定不会null 可以避免出现空指针异常
//message.getPassword()是一个变量有可能为null
if("123".equals(message.getPassword())) {
message.setSuccess(true);
ObjectRW.writeObject(socket, message);
//客户端登录成功后 服务器需要保存一下客户端
sockets.put(message.getUsername(), socket);
//然后开启线程为该客户端服务 如果不开启线程,则服务器不能再次接收用户的登录
executorService.execute(new MyThread(this,sockets, socket));
}else {//登录失败
message.setSuccess(false);
ObjectRW.writeObject(socket, message);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 刷新好友列表
* 就是将在线的人的name告诉给每一个人
*/
public void refreshFriend() {
//将map中所有的key撞到list中
List<String> friends = new ArrayList<String>();
Set<String> sets = sockets.keySet();
for (String string : sets) {
friends.add(string);
}
//然后封装一个Message
Message message = new Message();
message.setFriends(friends);
message.setType(Message.FRIEND);
//将message响应给所有在线的人
for (String string : sets) {
try {
ObjectRW.writeObject(sockets.get(string), message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最后是服务器端的线程类(注意这里和客户端的线程类不同):
package thread;
import java.io.IOException;
import java.net.Socket;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import model.Message;
import qqServer.Server;
import util.ObjectRW;
public class MyThread implements Runnable{
private Map<String, Socket> sockets;
private Socket socket;
private Server server;
public MyThread(Server server,Map<String, Socket> sockets, Socket socket) {
super();
this.server = server;
this.sockets = sockets;
this.socket = socket;
}
@Override
public void run() {
server.refreshFriend();//刷新好友列表
//不停的接收消息
while(true) {
try {
Message message = ObjectRW.readObject(socket);
if(message.getType() == Message.MSG) {
//找到接收者的socket
Socket s = sockets.get(message.getTo());
if(s != null)//如果对方在线
ObjectRW.writeObject(s, message);
else {//如果对方不在线 提示一下
//或者考虑一下离线消息 离线消息该怎么处理???
Message msg = new Message();
msg.setFrom(message.getTo());
msg.setType(Message.ERROR);
msg.setContent("对方已下线");
ObjectRW.writeObject(socket, msg);
}
} else if(message.getType() == Message.GROUP) {
Set<Entry<String,Socket>> entrys = sockets.entrySet();
for (Entry<String, Socket> entry : entrys) {
if(entry.getValue() != socket) {//除了自己都要把消息发送出去
ObjectRW.writeObject(entry.getValue(), message);
}
}
} else if(message.getType() == Message.EXIT) {
// 首先从集合中移除
sockets.remove(message.getFrom());
//刷新好友列表
server.refreshFriend();
//告诉客户端你可以退出了
ObjectRW.writeObject(socket, message);
//需要将线程停止
break;//循环结束,run方法结束,线程停止
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//循环结束后
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//关流的问题
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
首先登录操作,这里就是在一个服务器内的用户都是好友:

再跑一个单聊功能:
现在让bc互相发消息:

再跑一个群聊功能:

而后程序退出保证不报错(这里也需要一些代码量的不要以为很简单):
比如退出c以后ab的好友列表就没有c了

但是假如退出之前两个人正在聊天呢?效果如下:首先ab正常聊天,接下来将b退出,再用a给b发消息

提示已经下线。本文完。
本文详细介绍了一个基于Java的即时通讯系统的设计与实现,包括客户端与服务器的交互流程、UI界面展示、对象序列化处理及线程管理等核心内容。
364

被折叠的 条评论
为什么被折叠?



