目录
6.连接到服务器端的socket需要监听自己客户端有没有向服务器端发送消息,然后将消息转发给每一个客户端
一.涉及的知识点
包含:java基础语法,异常处理,IO流,网络编程,多线程,GUI界面,内部类,匿名类等,
有不懂的地方可以边学习边进行阅读
二.功能
1.登录界面
2.聊天页面
显示用户账户,发送信息,聊天记录
3.客户端界面
显示在线人数,发布同告,查询聊天记录
三.整体编程思路
由于整体流程过大,我们进行分步编程,通过完成一步一步小任务,最后完成整个任务
四.客户端与服务器端
博主这里用的是idea官方的插件来进行制作的图形界面,不做过多讲解,有需要的可以在csdn搜索java图形界面插件,有详细安装教程
1.在注册页面加入事件监听和登录按钮事件
loginbtn.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {//事件监听
try {
String account = accountFiled.getText();//获取文本框内容
String password = passwordFiled.getText();
if (password.equals("")) {
JOptionPane.showMessageDialog(null, "密码不能为空", "来自系统的提示", JOptionPane.WARNING_MESSAGE);
return;
}
if (account.equals("")) {
JOptionPane.showMessageDialog(null, "账户不能为空", "来自系统的提示", JOptionPane.WARNING_MESSAGE);
//(,内容,标题,警告类型);
return;
}
//连接数据库
//连接服务器端
Socket socket = new Socket("127.0.0.1", 9999);
dispose();//释放关闭当前窗口
new ChatFrame(accountFiled.getText(),socket).setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}catch(IOException ex){
ex.printStackTrace();
JOptionPane.showMessageDialog(null,"网络连接失败!","来自系统的提示",JOptionPane.WARNING_MESSAGE);
}catch(Exception ex){
ex.printStackTrace();//打印异常信息
JOptionPane.showMessageDialog(null,"系统忙,请稍后再试!","来自系统的提示",JOptionPane.WARNING_MESSAGE);
}
}
});
regbnt.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
//dispose();
new RegFrame();
}
});
2.在聊天页面显示当前登录人账户
将注册页面的用于记录账户的标签传入登录页面,然后在登录页面调用setText方法
3.服务器端创建
public void startServer() {
try {
//创建服务器并监听客户端的连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器启动成功!");
while (true) {
Socket socket = serverSocket.accept();
arrayList.add(socket);
System.out.println("客户端连接成功:" + arrayList.size());
//为每一个连接到服务器端的Socket创建一个线程,让它独立的运行,不影响死循环监听客户端的连接
new thread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败!");
}
}
这里将accept写入死循环当中是因为,要持续监听是否有新的客户端,如果监听到了客户端就将他们加入arrayList中,arrayList当中每一个元素都Socket对象
4.客户端创建
在登录页面创建一个Socket对象,这样每打开一个注册页面都会创建一个客户端
//连接服务器端
Socket socket = new Socket("127.0.0.1", 9999);
5.为发送按钮添加事件
//为发送按钮创建事件
button1.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
String s = textArea1.getText();
if(s.length()==0){
JOptionPane.showMessageDialog(null,"聊天内容不能为空");
return;
}
//发送聊天内容 发送人+时间+内容
try {
s = account+" "+ DateUtil.DateUtil1(new Date())+"\n"+s+"\n";
dataOutputStream.writeUTF(s);//发送给了服务器端
textArea1.setText("");
} catch (IOException ex) {
JOptionPane.showMessageDialog(null,"发送失败");
}
}
});
6.连接到服务器端的socket需要监听自己客户端有没有向服务器端发送消息,然后将消息转发给每一个客户端
这里必须将服务端监听消息写入线程中,其一是一个类中最多只能有一个死循环,其二,每一个连接到服务器端的客户端都需要被监听
//创建了一个线程类,一旦有客户端socket连接到服务器,把socket传入到一个线程中,在线程中独立的运行
private class thread extends Thread {
private DataInputStream dataInputStream;
public thread(Socket socket) throws IOException {
this.dataInputStream = new DataInputStream(socket.getInputStream());//服务器端接收
}
public void run() {
try {
FileWriter fileWriter = new FileWriter("C:/1javaSE/Chat.txt", false);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
while (true) {
try {
//这里保存着每一个客户端发送的记录,由服务器端作为中转站,将a客户端的消息转发给b,c 同理
String s = dataInputStream.readUTF();
System.out.println("服务器:" + s);
bufferedWriter.write(s);
bufferedWriter.flush();
//转发给其他客户端
for (Socket socket : arrayList) {//得到每一个socket对象,因为服务器与客户端是同一个socket对象
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataOutputStream.writeUTF(s);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException exception) {
exception.printStackTrace();
}
}
}catch(IOException ex){
ex.printStackTrace();
}
}
}
7.客户端接收来自服务器端的消息
public class thread1 extends Thread{
boolean mark = true;
public void run()
{
while(mark)
{
try {
String s = dataInputStream.readUTF();
System.out.println("客户端"+s);
textArea2.append(s);
} catch (IOException e) {
mark = false;
throw new RuntimeException(e);
}
}
}
}
五.完整代码
1.注册/登录页面
public class LoginFrame extends JFrame {
public LoginFrame() {
initComponents();
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
panel5 = new JPanel();
label2 = new JLabel();
label3 = new JLabel();
accountFiled = new JTextField();
passwordFiled = new JTextField();
loginbtn = new JButton();
regbnt = new JButton();
//======== this ========
setTitle("Hello,World");
setBackground(Color.black);
Container contentPane = getContentPane();
contentPane.setLayout(null);
//======== panel5 ========
{
panel5.setBackground(new Color(0x666666));
panel5.setLayout(null);
//---- label2 ----
label2.setText("\u8d26\u53f7:");
label2.setFont(label2.getFont().deriveFont(Font.BOLD|Font.ITALIC, label2.getFont().getSize() + 4f));
label2.setBackground(Color.gray);
label2.setForeground(Color.white);
panel5.add(label2);
label2.setBounds(new Rectangle(new Point(95, 45), label2.getPreferredSize()));
//---- label3 ----
label3.setText("\u5bc6\u7801:");
label3.setFont(label3.getFont().deriveFont(label3.getFont().getStyle() | Font.ITALIC, label3.getFont().getSize() + 4f));
label3.setBackground(Color.gray);
label3.setForeground(Color.white);
panel5.add(label3);
label3.setBounds(new Rectangle(new Point(95, 80), label3.getPreferredSize()));
panel5.add(accountFiled);
accountFiled.setBounds(140, 45, 135, accountFiled.getPreferredSize().height);
panel5.add(passwordFiled);
passwordFiled.setBounds(140, 80, 135, passwordFiled.getPreferredSize().height);
//---- loginbtn ----
loginbtn.setText("\u767b\u5f55");
loginbtn.setForeground(new Color(0x666666));
loginbtn.setBackground(new Color(0xcccccc));
panel5.add(loginbtn);
loginbtn.setBounds(new Rectangle(new Point(240, 145), loginbtn.getPreferredSize()));
//---- regbnt ----
regbnt.setText("\u6ce8\u518c");
regbnt.setForeground(new Color(0x666666));
regbnt.setBackground(new Color(0xcccccc));
panel5.add(regbnt);
regbnt.setBounds(new Rectangle(new Point(75, 145), regbnt.getPreferredSize()));
}
contentPane.add(panel5);
panel5.setBounds(0, 0, 385, 275);
contentPane.setPreferredSize(new Dimension(385, 240));
pack();
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
//按钮的事件处理程序 new 接口,是java中的一种简化的写法,创建了一个接口的匿名内部类对象
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setVisible(true);//显示窗口,此行放在最后一行设置
loginbtn.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {//事件监听
try {
String account = accountFiled.getText();//获取文本框内容
String password = passwordFiled.getText();
if (password.equals("")) {
JOptionPane.showMessageDialog(null, "密码不能为空", "来自系统的提示", JOptionPane.WARNING_MESSAGE);
return;
}
if (account.equals("")) {
JOptionPane.showMessageDialog(null, "账户不能为空", "来自系统的提示", JOptionPane.WARNING_MESSAGE);
//(,内容,标题,警告类型);
return;
}
//连接数据库
//连接服务器端
Socket socket = new Socket("127.0.0.1", 9999);
dispose();//释放关闭当前窗口
new ChatFrame(accountFiled.getText(),socket).setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}catch(IOException ex){
ex.printStackTrace();
JOptionPane.showMessageDialog(null,"网络连接失败!","来自系统的提示",JOptionPane.WARNING_MESSAGE);
}catch(Exception ex){
ex.printStackTrace();//打印异常信息
JOptionPane.showMessageDialog(null,"系统忙,请稍后再试!","来自系统的提示",JOptionPane.WARNING_MESSAGE);
}
}
});
regbnt.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
//dispose();
new RegFrame();
}
});
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JPanel panel5;
private JLabel label2;
private JLabel label3;
private JTextField accountFiled;
private JTextField passwordFiled;
private JButton loginbtn;
private JButton regbnt;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
public static void main(String[] args) {
LoginFrame loginFrame = new LoginFrame();
}
}
2.聊天页面
public class ChatFrame extends JFrame {
private String account;
private DataOutputStream dataOutputStream;
private DataInputStream dataInputStream;
public ChatFrame(String account,Socket socket) {
this.account = account;
try {
this.dataOutputStream = new DataOutputStream(socket.getOutputStream());
this.dataInputStream = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
initComponents();
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
scrollPane1 = new JScrollPane();
textArea2 = new JTextArea();
label1 = new JLabel();
scrollPane2 = new JScrollPane();
textArea1 = new JTextArea();
button1 = new JButton();
button2 = new JButton();
label2 = new JLabel();
//======== this ========
setTitle("\u804a\u5929\u5ba4");
Container contentPane = getContentPane();
contentPane.setLayout(null);
//======== scrollPane1 ========
{
scrollPane1.setEnabled(false);
scrollPane1.setViewportView(textArea2);
}
contentPane.add(scrollPane1);
scrollPane1.setBounds(0, 0, 490, 280);
//---- label1 ----
label1.setText(" \u597d\u53cb");
contentPane.add(label1);
label1.setBounds(490, 215, 110, 65);
//======== scrollPane2 ========
{
scrollPane2.setViewportView(textArea1);
}
contentPane.add(scrollPane2);
scrollPane2.setBounds(0, 280, 490, 120);
//---- button1 ----
button1.setText("\u53d1\u9001");
contentPane.add(button1);
button1.setBounds(490, 280, 110, 120);
//---- button2 ----
button2.setText("\u8fd4\u56de\u767b\u5f55\u9875\u9762");
contentPane.add(button2);
button2.setBounds(490, 0, 110, 90);
//---- label2 ----
label2.setText("\u7528\u6237\u4fe1\u606f:");
contentPane.add(label2);
label2.setBounds(490, 165, 110, 50);
{
// compute preferred size
Dimension preferredSize = new Dimension();
for(int i = 0; i < contentPane.getComponentCount(); i++) {
Rectangle bounds = contentPane.getComponent(i).getBounds();
preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
}
Insets insets = contentPane.getInsets();
preferredSize.width += insets.right;
preferredSize.height += insets.bottom;
contentPane.setMinimumSize(preferredSize);
contentPane.setPreferredSize(preferredSize);
}
pack();
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setVisible(true);//显示窗口,此行放在最后一行设置
label1.setText(account);
new thread1().start();//你把开启线程写到 发送按钮里面了
//为发送按钮创建事件
button1.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
String s = textArea1.getText();
if(s.length()==0){
JOptionPane.showMessageDialog(null,"聊天内容不能为空");
return;
}
//发送聊天内容 发送人+时间+内容
try {
s = account+" "+ DateUtil.DateUtil1(new Date())+"\n"+s+"\n";
dataOutputStream.writeUTF(s);//发送给了服务器端
textArea1.setText("");
} catch (IOException ex) {
JOptionPane.showMessageDialog(null,"发送失败");
}
}
});
button2.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();//关闭页面
new LoginFrame();
}
});
//来到聊天窗口后,就会随时有别的客户端发送信息,在聊天窗口中,循环监听来自服务器端发送的消息
//因为窗口不能和监听同时进行,我们需要加一个内部类线程监听
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JScrollPane scrollPane1;
private JTextArea textArea2;
private JLabel label1;
private JScrollPane scrollPane2;
private JTextArea textArea1;
private JButton button1;
private JButton button2;
private JLabel label2;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
public class thread1 extends Thread{
boolean mark = true;
public void run()
{
while(mark)
{
try {
String s = dataInputStream.readUTF();
System.out.println("客户端"+s);
textArea2.append(s);
} catch (IOException e) {
mark = false;
throw new RuntimeException(e);
}
}
}
}
}
3.客户端页面
public class ServerJFrame extends JFrame {
public Server server;
//创建一个客户端用来监听
//public Socket socket;
//public ServerJFrame(Socket socket){
// this.socket = socket;
//}
public ServerJFrame() throws IOException {
try {
initComponents();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void initComponents() throws IOException {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
label1 = new JLabel();
textField1 = new JTextField();
scrollPane1 = new JScrollPane();
textArea1 = new JTextArea();
button1 = new JButton();
textField2 = new JTextField();
button2 = new JButton();
button3 = new JButton();
//======== this ========
setTitle("\u670d\u52a1\u5668\u7a97\u53e3");
Container contentPane = getContentPane();
contentPane.setLayout(null);
//---- label1 ----
label1.setText("\u5728\u7ebf\u4eba\u6570:");
contentPane.add(label1);
label1.setBounds(new Rectangle(new Point(35, 20), label1.getPreferredSize()));
contentPane.add(textField1);
textField1.setBounds(30, 50, 235, 70);
//======== scrollPane1 ========
{
scrollPane1.setViewportView(textArea1);
}
contentPane.add(scrollPane1);
scrollPane1.setBounds(30, 130, 300, 200);
//---- button1 ----
button1.setText("\u53d1\u9001\u516c\u544a");
contentPane.add(button1);
button1.setBounds(305, 65, button1.getPreferredSize().width, 40);
contentPane.add(textField2);
textField2.setBounds(95, 15, 155, 25);
//---- button2 ----
button2.setText("\u66f4\u65b0\u4eba\u6570");
contentPane.add(button2);
button2.setBounds(305, 10, 85, 40);
//---- button3 ----
button3.setText("\u670d\u52a1\u7aef\u67e5\u8be2");
contentPane.add(button3);
button3.setBounds(335, 245, button3.getPreferredSize().width, 80);
contentPane.setPreferredSize(new Dimension(445, 380));
pack();
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
//显示在线人数
button1.addActionListener(new AbstractAction() {
//从服务器端发送一个公告到各个客户端
@Override
public void actionPerformed(ActionEvent e) {
String s = "来自服务器端发布的通告:" + textField1.getText() + "\n";
textField1.setText("");
if (s.length() == 0) {
JOptionPane.showMessageDialog(null, "聊天内容不能为空");
return;
}
for (Socket socket : server.arrayList) {
try {
//向客户端发送通告
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataOutputStream.writeUTF(s);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
});
//启动聊天后天监视
//new thread1().start();
button2.addActionListener((new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
new thread().start();
}
}));
button3.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
textArea1.setText("");
new thread1().start();
}
});
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setVisible(true);//显示窗口,此行放在最后一行设置
//在文本框中显示聊天内容
}
class thread extends Thread {
public void run() {
while (true) {
textField2.setText(" " + String.valueOf(server.arrayList.size()) + " ");
}
}
}
class thread1 extends Thread{
public void run(){
try {
FileReader fileReader = new FileReader("C:/1javaSE/Chat.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
while(bufferedReader.readLine()!=null){
String line = bufferedReader.readLine();
textArea1.append(line+"\n");
}
}catch(IOException exception){
exception.printStackTrace();
}
}
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JLabel label1;
private JTextField textField1;
private JScrollPane scrollPane1;
private JTextArea textArea1;
private JButton button1;
private JTextField textField2;
private JButton button2;
private JButton button3;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
public static void main(String[] args) throws IOException {
ServerJFrame serverJFrame = null;
serverJFrame = new ServerJFrame();
serverJFrame.server = new Server();
serverJFrame.server.startServer();
}
}
4.客户端
public class Server {
//存储连接到服务器端的多个客户端对象
public ArrayList<Socket> arrayList = new ArrayList<>();
public void startServer() {
try {
//创建服务器并监听客户端的连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器启动成功!");
while (true) {
Socket socket = serverSocket.accept();
arrayList.add(socket);
System.out.println("客户端连接成功:" + arrayList.size());
//为每一个连接到服务器端的Socket创建一个线程,让它独立的运行,不影响死循环监听客户端的连接
new thread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败!");
}
}
//创建了一个线程类,一旦有客户端socket连接到服务器,把socket传入到一个线程中,在线程中独立的运行
private class thread extends Thread {
private DataInputStream dataInputStream;
public thread(Socket socket) throws IOException {
this.dataInputStream = new DataInputStream(socket.getInputStream());//服务器端接收
}
public void run() {
try {
FileWriter fileWriter = new FileWriter("C:/1javaSE/Chat.txt", false);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
while (true) {
try {
//这里保存着每一个客户端发送的记录,由服务器端作为中转站,将a客户端的消息转发给b,c 同理
String s = dataInputStream.readUTF();
System.out.println("服务器:" + s);
bufferedWriter.write(s);
bufferedWriter.flush();
//转发给其他客户端
for (Socket socket : arrayList) {//得到每一个socket对象,因为服务器与客户端是同一个socket对象
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataOutputStream.writeUTF(s);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException exception) {
exception.printStackTrace();
}
}
}catch(IOException ex){
ex.printStackTrace();
}
}
}
}