java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing

最近在复习java的io流及网络编程。但复习写那些样板程序总是乏味的。便准备写个项目来巩固。想来想去还是聊天项目比较好玩。如果日后完成的比较好自己也可以用(哈哈哈)。并且自己后面也要继续巩固java多线程和集合(这两部分学的很差)。

我给这个项目命名为很大众的名字——" chat "

这算是"chat 1.0" 吧目前只实现了群聊+文件传输功能,没有用户注册模块。

因为自己在阿里云上的服务器之前到期没有续费,所以就无法将服务端实现两个局域网之间的信息传输。只能在自己电脑上开多个客户端自己和自己聊天。

不是很甘心,上网找到了一个比较好的方法,内网穿透。某神,某花,都是免费可以映射一两个公网ip的。这样就可以实现不同局域网之间的通信了

 

效果演示:

打开图形界面不会立刻执行,会有启动服务器,连接服务器按钮。图中2昵称仅支持本地自己的界面显示来分辨是自己发的消息(哈哈哈)

 

 

点击发送文件按钮,会显示文件对话框,来选择要发送的本地文件。

 

 

发送完毕后其它客户端会收到文件消息,并询问是否接收文件。

 

接收时也会弹出文件对话框,并选择存储的路径和文件名称

 

字不多打,上代码。(主要使用tcp通信)

服务端:

Server类实现一直监听客户端的套接字连接,并开启服务端读写线程

 1 import java.io.IOException;
 2 import java.net.ServerSocket;
 3 import java.net.Socket;
 4 import java.util.ArrayList;
 5 import java.util.List;
 6 
 7 import view.ServerStart_View;
 8 
 9 public class Server implements Runnable{
10 
11     public static ArrayList<Socket> socketList = new ArrayList<>();    //存储连接的Socket对象
12     private static int num = 0;
13     static ServerStart_View gui;    //定义为静态的
14 //    public static final Server server = new Server();
15     
16     public void get(ServerStart_View gui) {
17         this.gui=gui;
18     }
19     
20     public void run() {
21         ServerSocket socket=null;
22         Socket socket2 = null;
23         try {
24             socket = new ServerSocket(12345);
25         } catch (IOException e) {
26             e.printStackTrace();
27         }
28 
29         while(true) {
30 //            System.out.println("didi");
31             //无限监听客户端连接,并为此开启读写线程。
32             try {
33                 Thread.sleep(1);
34                 socket2=socket.accept();
35                 socketList.add(socket2);
36                 ++num;
37             } catch (IOException e) {
38                 e.printStackTrace();
39             } catch (InterruptedException e1) {
40                 e1.printStackTrace();
41             }
42             gui.setText("第"+num+"个客户端已经连接");
43             try {
44                 //开启消息读入转发线程。
45                 Server_IO io = new Server_IO(socket2,gui);
46                 Thread thread  = new Thread(io);
47                 thread.start();
48             } catch (IOException e) {
49                 e.printStackTrace();
50             }
51         }
52     }
53 }

 

Server_IO类是实现服务端的读写功能,读取客户端传来的字节并转发给其它客户端

 1 import java.io.DataInputStream;
 2 import java.io.DataOutputStream;
 3 import java.io.IOException;
 4 import java.net.Socket;
 5 
 6 import view.ServerStart_View;
 7 
 8 public class Server_IO implements Runnable {
 9     private DataInputStream stream = null;
10     private Socket socket = null;
11     private static ServerStart_View start_View;
12     
13     public Server_IO(Socket socket,ServerStart_View start_View) throws IOException {
14         this.start_View = start_View;
15         this.socket = socket;
16         stream = new DataInputStream(socket.getInputStream());
17     }
18     
19     @Override
20     public void run() {
21         String rece = null;
22 //        boolean judg = true;
23         while(true) {
24             try {
25                 Thread.sleep(1);
26                 if (stream.available()!=0) {
27                     try {
28                         rece = stream.readUTF();
29                         start_View.setText(rece);
30                     } catch (IOException e) {
31                         e.printStackTrace();
32 //                        judg=false;
33                     }
34                     for (Socket Ssocket:Server.socketList) 
35                         new DataOutputStream(Ssocket.getOutputStream()).writeUTF(rece);
36                     
37 //                    System.out.println(rece);
38                 }
39             } catch (IOException e) {
40                 e.printStackTrace();
41             } catch (InterruptedException e1) {
42                 // TODO Auto-generated catch block
43                 e1.printStackTrace();
44             }
45         }
46     }
47 }

 

服务端文件传输:

服务端消息和文件传输是分开的,两个不同的端口。但原理一样,都是服务端不断监听客户端发来的连接请求。然后三次握手建立连接通信,开启输入流,输出流。

File类和Server类功能大致相同,只不过是用于接收转发文件信息

 1 import java.io.IOException;
 2 import java.net.ServerSocket;
 3 import java.net.Socket;
 4 import java.util.ArrayList;
 5 import java.util.List;
 6 
 7 public class File implements Runnable{
 8     
 9     public static ArrayList<Socket> socketList_IO = new ArrayList<>();
10     ServerSocket ssocket;
11     Socket socket;
12 
13     public void run() {
14         //文件传输开启另一个端口监听。
15         try {
16             ssocket = new ServerSocket(12306);
17         } catch (IOException e) {
18             e.printStackTrace();
19         }
20         while(true) {
21             try {
22                 Thread.sleep(1);
23                 socket = ssocket.accept();
24                 socketList_IO.add(socket);
25             } catch (IOException e) {
26                 e.printStackTrace();
27             } catch (InterruptedException e1) {
28                 e1.printStackTrace();
29             }
30             File_IO file_IO;
31             try {
32                 file_IO = new File_IO(socket);
33                 Thread thread = new Thread(file_IO);
34                 thread.start();
35             } catch (IOException e) {
36                 e.printStackTrace();
37             }
38             
39         }
40     }
41 
42 }

 

File_IO类 接收客户端的文件,并转发给其他客户端

 1 import java.io.DataInputStream;
 2 import java.io.DataOutputStream;
 3 import java.io.IOException;
 4 import java.net.Socket;
 5 
 6 public class File_IO implements Runnable {
 7     DataInputStream input;
 8     DataOutputStream output;
 9     Socket socket;
10 
11     public File_IO(Socket socket) throws IOException{
12         this.socket = socket;
13         System.out.println(socket.getInetAddress());
14     }
15 
16     @Override
17     public void run() {
18         byte[] read = new byte[1024];
19         String name;
20         try {
21             input = new DataInputStream(socket.getInputStream());
22         } catch (IOException e1) {
23             e1.printStackTrace();
24         }
25         while(true) {
26             try {
27                 Thread.sleep(1);
28                 if(input.available()!=0) {
29                     name=input.readUTF();
30                     System.out.println(name);
31                     int len=0;
32                     while((len=input.read(read,0,read.length))>0) {
33                         for(Socket soc:File.socketList_IO) {
34                             if(soc != socket)
35                             { 
36                                 output = new DataOutputStream(soc.getOutputStream());
37                                 output.writeUTF(name);
38                                 output.write(read,0,len);
39                                 output.flush();
40 //                                System.out.println("开始向客户机转发");
41                             }
42                         }
43 //                        System.out.println("执行");
44 //                        System.out.println(len);
45                     }
46                 }
47             } catch (IOException e) {
48                 e.printStackTrace();
49             } catch (InterruptedException e) {
50                 e.printStackTrace();
51             }
52         }
53     }
54 }

 

Swing图形界面。服务端如果是放在Linux系统的服务器中是不需要图形界面的。但我这主要在自己电脑上运行所以做个简单的可视化界面比较好。

客户端还是服务端的图形界面我都是借助于eclipse的windowbuilder插件进行绘制的,所以图形界面类我贴上主要的方法及按钮的事件监听代码部分。

服务端的界面

public class ServerStart_View extends JFrame {

    private JPanel contentPane;
    private JScrollPane scrollPane;
    private JTextArea textArea;
    private static Server server = new Server();  //定义为静态的
    private static File file = new File();  
    private final Action action = new SwingAction();
    private JButton btnNewButton;
    
//打印收到的消息在服务端的文本域中
    public void setText(String string) {
        textArea.append("\n"+">>: "+string);
    }

   //
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                ServerStart_View frame = new ServerStart_View();
                frame.setVisible(true);
                server.get(frame);   //传递对象变量
            }
        });
    }

............
............
............

//启动服务按钮的事件处理。
    private class SwingAction extends AbstractAction {
        public SwingAction() {
            putValue(NAME, "启动服务");
            putValue(SHORT_DESCRIPTION, "Some short description");
        }
        public void actionPerformed(ActionEvent e) {
            //开启服务端监听客户端连接线程(消息服务)
            Thread thread = new Thread(server);
            thread.start();
           //同上 (文件服务)
            Thread thread2 = new Thread(file);
            thread2.start();
        }
    }

}

服务端代码到此结束。主要是消息和文件分别各监听一个端口的等待连接。

 

客户端:

本来是定义Client类的但因为某些前期原因导致客户端的类名不是很规范。

chat_Client类

 1 package client;
 2 
 3 import view.ClientStart_View;
 4 import java.io.DataOutputStream;
 5 import java.io.IOException;
 6 import java.net.Socket;
 7 import java.net.UnknownHostException;
 8 
 9 public class chat_Client{
10     public chat_Client() {
11     }
12 
13     private Socket socket = null;
14     //    private int Port = 0;
15     private static ClientStart_View jframe;  //定义为静态的
16 
17     public void text(ClientStart_View jframe) {
18         this.jframe = jframe;
19     }
20 
21     //“连接服务器” button 按钮点击事件,调用此方法
22     public void con(int port) throws UnknownHostException, IOException {
23         try {
24             socket = new Socket("127.0.0.1", port);
25 //            System.out.println("客户端------------------------");
26 //            System.out.println("连接到的地址"+socket.getInetAddress()+":"+socket.getLocalPort());  //打印在控制台
27             jframe.TextA("已连接");
28         } catch (IOException e1) {
29             e1.printStackTrace();
30         }
31 
32         Runnable runnable = new chat_Client_I(socket,jframe);  //启动消息读写线程,并将传递socket,jframe对象变量
33         Thread thread = new Thread(runnable);
34         thread.start();
35     }
36 
37     //send()方法,发送消息给服务器。 “发送”button 按钮点击事件,调用此方法
38     public void send(String send) throws IOException {
39         DataOutputStream stream = new DataOutputStream(socket.getOutputStream());
40         stream.writeUTF(send);
41     }
42 
43 }

 

chat_Client_I类  客户端的消息字符读取类。读取服务端发来的消息字符并打印在界面的文本域中

 1 import view.ClientStart_View;
 2 
 3 import java.io.DataInputStream;
 4 import java.io.IOException;
 5 import java.net.Socket;
 6 
 7 public class chat_Client_I implements Runnable{
 8     private String rece = null;
 9     private static DataInputStream stream;
10     private ClientStart_View jframe;
11 
12     public chat_Client_I(Socket socket,ClientStart_View jframe) {
13         this.jframe = jframe;
14         try {
15             stream = new DataInputStream(socket.getInputStream());
16         } catch (IOException e2) {
17             e2.printStackTrace();
18         }
19     }
20 
21     @Override
22     public void run() {
23         while(true) {
24             try {
25                 //if条件判断是否有可获取的字节 ,如果不是判断而一直读取的话,在结束时会有读取异常抛出。运行时是没有的。
26                 if(stream.available()!=0) {
27                     rece = stream.readUTF();
28                     jframe.TextA(rece);    //调用 ClientStart_View.java类中方法将读入的字符串打印在文本域中
29                 }
30             } catch (IOException e) {
31                 // TODO Auto-generated catch block
32                 e.printStackTrace();
33             }
34         }
35     }
36 }

 

客户端的文件模块写的有点乱。客户端文件模块要实现发送和接收保存

发送要从计算机本地通过输入流读取文件,并通过输出流发送给服务端。

接收要从服务端通过输入流进行读入,然后通过输出流写入本地保存。

 

File_start类,连接服务端,并开启输入流读取服务端发来的文件

 1 import java.io.IOException;
 2 import java.net.Socket;
 3 import java.net.UnknownHostException;
 4 
 5 public class File_start implements Runnable{
 6     Socket socket=null;
 7 
 8     @Override
 9     public void run() {
10         try {
11             socket = new Socket("127.0.0.1", 12306);
12 //            System.out.println("已连接");
13 //            System.out.println(socket.getInetAddress()+":"+socket.getLocalPort());
14         } catch (UnknownHostException e) {
15             // TODO Auto-generated catch block
16             e.printStackTrace();
17         } catch (IOException e) {
18             e.printStackTrace();
19         }
20 
21         File_I file_i;
22         try {
23             File_O file_O = new File_O(socket);
24             //开启从服务器的输入流线程
25             file_i = new File_I(socket);
26             Thread thread = new Thread(file_i);
27             thread.start();
28         } catch (IOException e) {
29             e.printStackTrace();
30         }
31 
32     }
33 }

 

File_O类,读取本地文件并发送到服务端

 1 import java.io.DataInputStream;
 2 import java.io.DataOutputStream;
 3 import java.io.FileInputStream;
 4 import java.io.IOException;
 5 import java.net.Socket;
 6 
 7 public class File_O{
 8     public static File_O file_O = new File_O();
 9     //    private Socket socket=null;
10     private byte[] readAllBytes = new byte[1024];
11     private static DataOutputStream outputstream;
12     private DataInputStream inputstream;
13 
14     public File_O() {}
15     public File_O(Socket socket) throws IOException {
16 //        this.socket = socket;
17         outputstream = new DataOutputStream(socket.getOutputStream());
18     }
19 
20     //读取本地文件,并发送到服务端。面板中发送文件按钮在事件处理时会调用此函数
21     public void read(String url,String name) throws IOException {
22         outputstream.writeUTF(name);
23         inputstream = new DataInputStream(new FileInputStream(url));
24         int len=0;
25         while((len=inputstream.read(readAllBytes))>0) {
26             outputstream.write(readAllBytes, 0, len);
27             outputstream.flush();
28         }
29         //outputstream.close();
30 //        System.out.println("发送完成");
31     }
32 
33     //此方法暂时没有用到
34     public byte[] getread() {
35         return readAllBytes;
36     }
37 }

 

File_I类 从服务端的输出流读取文件,并保存到本地。

 1 import view.ClientStart_View;
 2 
 3 import java.awt.HeadlessException;
 4 import java.io.DataInputStream;
 5 import java.io.DataOutputStream;
 6 import java.io.File;
 7 import java.io.FileOutputStream;
 8 import java.io.IOException;
 9 import java.net.Socket;
10 
11 import javax.swing.JOptionPane;
12 
13 public class File_I implements Runnable{
14 
15     //    public static File_I file_I = new File_I();
16     private static ClientStart_View jframe;
17     private DataOutputStream output;
18     private static DataInputStream input;
19     private Socket socket;
20 
21     public File_I() {}
22     public File_I(Socket socket) throws IOException {
23         this.socket = socket;
24         input = new DataInputStream(socket.getInputStream());
25     }
26 
27     public void getfile(ClientStart_View jframe) {
28         this.jframe = jframe;
29     }
30 
31     @Override
32     public void run() {
33         String url = null;
34         String name = null;
35         byte[] rece = new byte[1024];
36         while(true) {
37             try {
38                 //if判断服务端输出流中是否有可读取字节
39                 if (input.available()!=0) {
40                     name = input.readUTF();
41                     //                System.out.println(name);
42 
43                     //是否接受此文件,接收才会进行输入流读入保存
44                     JOptionPane jOptionPane = new JOptionPane();
45                     int result=jOptionPane.showConfirmDialog(null, "是否接收该文件", "嘀嘀:一份文件消息", jOptionPane.YES_NO_OPTION);
46                     //                System.out.println(result);
47                     if (result == jOptionPane.YES_OPTION) {
48                         //默认存储路径及从服务端获取的文件名称
49                         File file = new File("D:\\", name);
50                         //                    file_I.jframe.getJF().setCurrentDirectory(new File("D:\\"));
51                         jframe.getJF().setSelectedFile(file);
52                         jframe.getJF().showSaveDialog(null);
53                         url = jframe.getJF().getSelectedFile().getPath();
54                         //                    name =file_I.jframe.getJF().getSelectedFile().getName();
55 
56                         int len;
57                         try {
58                             while((len=input.read(rece,0,rece.length))!=-1) {
59 //                                System.out.println("zhixing-1");
60                                 output = new DataOutputStream(new FileOutputStream(url));
61                                 output.write(rece, 0, len);
62                                 output.flush();
63                             }
64                         } catch (IOException e) {
65                             e.printStackTrace();
66                         }
67                     }
68                 }
69             } catch (HeadlessException | IOException e) {
70                 e.printStackTrace();
71             }
72         }
73     }
74 }

 

客户端的图形界面也是使用插件,所以只贴上主要的代码段

public class ClientStart_View extends JFrame {

    public JPanel contentPane;
    private JTextField textField;
    JTextArea textArea;
    private static client.chat_Client chat_Client=new chat_Client();
    private static File_I file_I = new File_I();
    private JFileChooser fileChooser =new JFileChooser();
    private final Action action = new SwingAction();
    private final Action action_2 = new SwingAction_2();
    private JTextField textField_1;
    private final Action action_3 = new SwingAction_3();
//JFileChooser 用于创建文件对话框
    public JFileChooser getJF() {
        return fileChooser;
    }
    
   //此方法将接收到服务端的消息打印文本域中
    public void TextA(String rece) {
        textArea.append("\n"+textField_1.getText()+" > "+rece);
        System.out.println(rece);
    }

public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    ClientStart_View frame = new ClientStart_View();
                    frame.setVisible(true);
                    chat_Client.text(frame);
//                    chat_Client.get().text(frame);
                    file_I.getfile(frame);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

.........
.........
.........
.........

//事件处理,连接服务端,并开启客户端的文件读取线程
 private class SwingAction extends AbstractAction {
        public SwingAction() {
            putValue(NAME, "连接服务器");
            putValue(SHORT_DESCRIPTION, "Some short description");
        }
        public void actionPerformed(ActionEvent e) {
            try {
                //chat_Client.get().con(12345);  //QQ
                chat_Client.con(12345);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            File_start file_start = new File_start();
            Thread thread = new Thread(file_start);
            thread.start();

        }
    }

//发送按钮的事件处理,从输入文本中读取输入的字符,发送至服务端
    private class SwingAction_2 extends AbstractAction {
        public SwingAction_2() {
            putValue(NAME, "发送");
            putValue(SHORT_DESCRIPTION, "Some short description");
        }
        public void actionPerformed(ActionEvent e) {
            String text = null;
            text = textField.getText();
            if(text.length()!=0) {
                try {
//                chat_Client.get().send(text);
                    chat_Client.send(text);
                } catch (IOException e1) {
                    e1.printStackTrace();
                }}
            textField.setText(null);   //输入文本框置空
        }
    }

//发送文件按钮事件处理,从本地读取文件信息,发送至服务端
    private class SwingAction_3 extends AbstractAction {
        public SwingAction_3() {
            putValue(NAME, "发送文件/图片");
            putValue(SHORT_DESCRIPTION, "Some short description");
        }
        public void actionPerformed(ActionEvent e) {
            String name=null;    String paths=null;
//            fileChooser = new JFileChooser();
            fileChooser.setCurrentDirectory(new java.io.File("E:\\"));
            int result=fileChooser.showOpenDialog(null);
            if (result == fileChooser.APPROVE_OPTION) {
                paths = fileChooser.getSelectedFile().getPath();  //返回所选文件的路径
                name= fileChooser.getSelectedFile().getName(); //返回所选文件的名称
//                System.out.println(name); System.out.println(paths);
                try {
                    File_O.file_O.read(paths,name);
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
        }
    }

 

至此代码结束

这段代码只是实现一部分简单功能,还参杂着不少问题,”线程开的太多,并且是死循环,在运行是cpu使用总是达到100%“,”文件的发送只有文本txt能成功打开,其他发送后格式乱码无法打开“。,,,,

欢迎大家交流和指教。

ps : 项目更新,增加登录,注册功能,发现并优化了一些已知bug 

进入主页查看阅读最新文章

链接:https://www.cnblogs.com/yuqingsong-cheng/p/12822628.html

posted @ 2020-04-20 21:25  裕轻松哈哈  阅读(2845)  评论(0编辑  收藏  举报