zoukankan      html  css  js  c++  java
  • 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("
    "+">>: "+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("
    "+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

  • 相关阅读:
    ACMer第7天Falling Ants
    贪心初步-FatMouse' Trade
    贪心初步-A
    ACM集训第二天
    asp.net中遍历套用母版页的页面的控件
    a 标签中调用js的几种方法
    笔记
    html控件和web控件
    ASP.NET中GUID类
    (转)常见邮件服务器(接收服务器和发送邮件服务器)地址
  • 原文地址:https://www.cnblogs.com/yuqingsong-cheng/p/12740307.html
Copyright © 2011-2022 走看看