• socket之事例


    2.面试题目总结;

    1.使用ServerSocket和Socket实现服务器端和客户端的 Socket通信:

    =========

    2.面试题目总结:

    面试题目一:编写一个网络应用程序,有客户端与服务器端,客户端向服务器发送一个字符串,服务器收到该字符串后将其打印到命令行上,然后向客户端返回该字符串的长度,最后,客户端输出服务器端返回的该字符串的长度

    答案:具体代码如下

    //服务器端代码
    public class Server {
    
        public static void main(String[] args) throws Exception {
            ServerSocket ss = new ServerSocket(65000);
            while (true) {
                Socket socket = ss.accept();
                new ServerThread(socket).start();
            }
        }

      class ServerThread extends Thread {
        private Socket socket;
    
        public ServerThread(Socket socket) {
            this.socket = socket;
        }
    
        @Override
        public void run() {
            try {
                OutputStream os = socket.getOutputStream();
                InputStream is = socket.getInputStream();
                int ch = 0; 
                byte[] buff = new byte[1024];
                ch = is.read(buff);
                String content = new String(buff, 0, ch);
                System.out.println(content);
                os.write(String.valueOf(content.length()).getBytes());
                
                is.close();
                os.close();
                socket.close(); 
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        }
    } 
    //客户端代码
    public class Client {
    
        public static void main(String[] args) throws Exception {
            Socket socket = new Socket("127.0.0.1", 65000);
            OutputStream os = socket.getOutputStream();
            InputStream is = socket.getInputStream();
            os.write(new String("hello world").getBytes());
    int ch = 0; byte[] buff = new byte[1024]; ch = is.read(buff); String content = new String(buff, 0, ch); System.out.println(content); is.close(); os.close(); socket.close(); } }

    面试题目二:请用UDP通信方式完成上述问题,并说说UDP与TCP的区别

    答案:具体代码见如下所示

    //服务端代码
    public class UDPServer {
    
        public static void main(String[] args) throws Exception {
            // 服务端接受客户端发送的数据报
            DatagramSocket socket = new DatagramSocket(65001); //监听的端口号
            byte[] buff = new byte[100]; //存储从客户端接受到的内容
            DatagramPacket pocket = new DatagramPacket(buff, buff.length);
            //接受客户端发送过来的内容,并将内容封装进DatagramPacket对象中
            socket.receive(pocket); 
    
            byte[] data = pocket.getData(); //从DatagramPacket对象中获取到真正存储的数据
            //将数据从二进制转换成字符串形式
            String content = new String(data, 0, pocket.getLength()); 
            System.out.println(content); 
            //将要发送给客户端的数据转换成二进制
            byte[] sendedContent = String.valueOf(content.length()).getBytes(); 
            // 服务端给客户端发送数据报
            //从DatagramPacket对象中获取到数据的来源地址与端口号
            DatagramPacket packetToClient = new DatagramPacket(sendedContent,
                    sendedContent.length, pocket.getAddress(), pocket.getPort()); 
            socket.send(packetToClient); //发送数据给客户端
        }
    }
    
    //客户端的代码
    public class UDPClient {
    
        public static void main(String[] args) throws Exception {
            // 客户端发数据报给服务端
            DatagramSocket socket = new DatagramSocket();
            // 要发送给服务端的数据
            byte[] buf = "Hello World".getBytes();
            // 将IP地址封装成InetAddress对象
            InetAddress address = InetAddress.getByName("127.0.0.1");
            // 将要发送给服务端的数据封装成DatagramPacket对象 需要填写上ip地址与端口号
            DatagramPacket packet = new DatagramPacket(buf, buf.length, address,
                    65001);
            // 发送数据给服务端
            socket.send(packet);
    
            // 客户端接受服务端发送过来的数据报
            byte[] data = new byte[100];
            // 创建DtagramPacket对象用来存储服务端发送过来的数据
            DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
            // 将接受到的数据存储到DatagramPacket对象中
            socket.receive(receivedPacket);
            // 将服务器端发送过来的数据取出来并打印到控制台
            String content = new String(receivedPacket.getData(), 0,
                    receivedPacket.getLength());
            System.out.println(content);
    
        }
    }
    

    UDP与TCP的区别是:UDP是用户数据报协议是无连接的,不可靠的,数据的到达时间,到达顺序,数据是否能够到达及数据到达的完整性都是无法保证的,类似于寄信,适用于视频会议等对速度要求高但对数据完整性要求不严格的场景,

    而TCP是传输控制协议,数据的传输建立在连接之上,是可靠的,TELNET、FTP、HTTP都是建立在TCP之上的,因为有连接所以在JAVA中用输入输出流来传输数据。

    用UDP编程使用到类DatagramSocket、DatagramPacket
    用TCP编程使用到类ServerSocket、Socket

    面试题目三:用socket编写一个服务端与客户端的聊天程序

    答案:具体代码如下所示

    //服务器端代码
    //监听端口,每成功连接一个客户机就开启一个线程进行读写处理
    public class Server {
    
        public static void main(String[] args) throws Exception {
            ServerSocket ss = new ServerSocket(65000);
            while (true) {
                Socket socket = ss.accept();
                new MyThread(socket).start(); //开启一个线程用于处理已连接上的socket
            }
        }
    }
    //开启两个线程分别用来处理socket中的输入输出流
    class MyThread extends Thread {
        private Socket socket;
    
        public MyThread(Socket socket) {
            this.socket = socket;
        }
    
        @Override
        public void run() {
            try {
                //开启两个线程分别来处理服务端的读写
                new OutputThread(socket.getOutputStream()).start();
                new InputThread(socket.getInputStream()).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    //处理socket的输出流
    class OutputThread extends Thread {
        private OutputStream os;
        public OutputThread(OutputStream os) {
            this.os = os;
        }
        
        @Override
        public void run() {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String line = null;
            try {
                while ((line = br.readLine()) != null) {
                    os.write((line).getBytes());
                    os.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    //处理socket的输入流
    class InputThread extends Thread {
        private InputStream is;
        public InputThread(InputStream is) {
            this.is = is;
        }
        @Override
        public void run() {
            int ch = 0; 
            byte[] buff = new byte[1024];
            try {
                while ((ch = is.read(buff)) != -1) {
                    String content = new String(buff, 0, ch);
                    System.out.println(content);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    //客户端代码
    public class Client {
    
        public static void main(String[] args) throws Exception {
            Socket socket = new Socket("127.0.0.1", 65000);
            new MyThread(socket).start(); //开启一个线程用于处理已连接上的socket
        }
     
    

    1.使用ServerSocket和Socket实现服务器端和客户端的 Socket通信:
    总结:
    1) 建立Socket连接
    2) 获得输入/输出流
    3)读/写数据
    4) 关闭输入/输出流
    5) 关闭Socket

    1. 编写一个网络应用程序,有客户端与服务器端,客户端向服务器端发送一个字符串,服务器收到该字符串后将其打印到命令行上,
    然后向客户端返回该字符串的长度,最后,客户端输出服务器端返回的该字符串的长度。

    import java.net.ServerSocket;
    import java.net.Socket;

    public class SocketServerTest
    {

    public static void main(String[] args) throws Exception
    {
    ServerSocket ss = new ServerSocket(9999);
    Socket socket = ss.accept();

    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();

    byte[] buffer = new byte[100];
    int length = is.read(buffer);
    String content = new String(buffer,0,length);

    System.out.println("read from client:" + content);
    int strLength = content.length();
    String str = String.valueOf(strLength);
    os.write(str.getBytes());

    is.close();
    os.close();
    socket.close();
    }
    }

    ---ClientTest
    import java.net.Socket;
    public class ClientTest
    {
    public static void main(String[] args) throws Exception
    {
    Socket socket = new Socket("localhost",9999);
    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();
    String content = "This comes from client";
    os.write(content.getBytes());

    byte[] b = new byte[100];
    int length = is.read(b);
    String str = new String(b,0,length);
    System.out.println("string's length:" + str);
    is.close();
    os.close();
    socket.close();
    }
    }

    Java socket 聊天室 例子

    server 服务器端,处理数据连接,并转发消息

    package chat;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
     * 聊天室服务端
    public class Server {
    
         * 运行在服务端的ServerSocket主要负责:
         * 1. 向系统申请服务端口客户端就是通过这个端口与之建立连接的
         * 2. 监听申请的服务端口,当一个客户端通过该端口尝试建立连接时,ServerSocket会在
         * 服务端创建一个Socket与客户端建立连接。
    
        private ServerSocket server;
    
         * 保存所有客户端输出流的集合
        private Map<String,PrintWriter> allOut;
        
         * 用来初始化服务端
        public Server() throws IOException{
             * 初始化的同时申请服务端口 
            server = new ServerSocket(8088);        
            allOut = new HashMap<String,PrintWriter>();
        }
    
         * 入口方法
        public static void main(String[] args) {
            try {
                Server server = new Server();
                server.start();
            }catch(Exception e) {
                e.printStackTrace();
                System.out.println("服务端启动失败");
            }
        }
    
         * 将给定的输出流添加到共享集合
        private synchronized void addOut(String nickName,PrintWriter out) {
            allOut.put(nickName, out);
        }
         * 将给定的输出流从共享集合中删除
        private synchronized void removeOut(String nickName) {
            allOut.remove(nickName);
        }
         * 将给定的消息发送给所有客户端
        private void sendMessage(String message) {
            Set<Map.Entry<String, PrintWriter>> entryset = allOut.entrySet();
            for(Map.Entry<String, PrintWriter> e: entryset) {
                e.getValue().println(message);
            }       
        }
         * 将给定消息发送给特定上线用户
        private void sendOneMessage(String whoName,String message) {
            String keyName = message.substring(1,message.indexOf(":"));
            PrintWriter out = allOut.get(keyName);
            System.out.println("out is "+out+keyName);
            if(out != null) {
                out.println(whoName+"对你说:"+message.substring(message.indexOf(":")));
            }else {
                System.out.println("发送失败");
            }
        }
    
         * 服务端开始工作的方法
        public void start() {
            try {
                 * ServerSocket的accept方法,是一个堵塞方法,作用是监听服务端口,
                 * 直到一个客户端连接并创建一个Socket,使用该Socket即可与刚连接的客户端进行交互
                while(true) {
                    System.out.println("等待客户端连接····");
                    Socket socket = server.accept();
                    System.out.println("一个客户端连接了");
                    ClientHandler handler = new ClientHandler(socket);
                    Thread t = new Thread(handler);
                    t.start();
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        
         * 该线程处理客户端的Socket
        class ClientHandler implements Runnable{
            private Socket socket;
            private String nickName;
            private String host;   * 客户端的地址信息
            
            public ClientHandler(Socket socket) {
                this.socket = socket;
    
                 * 通过Socket可以获取远端计算机的地址信息。
                InetAddress address = socket.getInetAddress();
                 * 获取ip地址
                host = address.getHostAddress();
            }
            
            public void run() {
                 * Socket提供的方法
                 * InputStream getInputStream()
                 * 该方法可以获取一个输入流,从该流读取的数据就是从远端计算机发送过来的
                PrintWriter pw = null;
                try {
                    InputStream input = socket.getInputStream();
                    InputStreamReader isr = new InputStreamReader(input,"UTF-8");
                    BufferedReader br = new BufferedReader(isr);
                    
                    OutputStream out = socket.getOutputStream();
                    OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
                    pw = new PrintWriter(osw,true);
                    
                     * br.readLine()在读取客户端发送过来的消息时,由于客户端断线,而其操作系统的不同,
                     * 这里读取的结果不同:
                     * 当windows的客户端断开时,br.readLine会抛出异常
                     * 当linux的客户端断开时,br.readLine会返回null
    
                    nickName = br.readLine();
                    System.out.println(host+" "+nickName+" 上线了");
                    
                     * 将该客户端的输出流存入到共享集合中
                    addOut(nickName,pw);
                    System.out.println(allOut);
                    System.out.println("等待接收消息");
                    while(true) {
                        String message = br.readLine();
                        if(message == null) {
                            break;
                        }
                        System.out.println(host+" "+nickName+": "+message);
                        if(message.startsWith("@")) {
                            sendOneMessage(nickName,message);
                        }else {
                            sendMessage(host+nickName+": "+message);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        socket.close();
                        removeOut(nickName);
                        System.out.println(nickName+"断开连接");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    Client 客户端,监听发送事件,启动一个线程专门处理接收数据

    package chat;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.util.Scanner;
    
     * 聊天室客户端
    public class Client {
         * 套接字,java.net.Socket
         * 封装了TCP协议,使用它就可以基于TCP协议进行网络通讯。
         * Socket是运行在客户端的
        private Socket socket;
        
         * 构造方法,用来初始化客户端
        public Client() throws Exception {
             * 实例化Socket的时候需要传入两个参数:
             * 1:服务器地址,通过IP地址可以找到服务器的计算机
             * 2:服务器端口,通过端口可以找到服务器计算机上服务端应用程序
             * 实例化Socket的过程就是连接的过程,若远程计算机没有响应会抛出异常。
            System.out.println("正在连接服务端····");
            socket = new Socket("localhost",8088);
            System.out.println("已与服务端建立连接");
        }
    
         * 入口方法
        public static void main(String[] args) {
            try {
                Client client = new Client();
                client.start();
            }catch (Exception e) {
                e.printStackTrace();
                System.out.println("客户端启动失败!");
            }
        }
    
         * 启动客户端的方法
        public void start() {
            try {
                 * Socket提供的方法:
                 * OutputStream getOutputStream
                 * 获取一个字节输出流,通过该流写出的数据会被发送至远端计算机。
    
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
                PrintWriter pw = new PrintWriter(osw,true);
                
                 * 启动读取服务端发送过来消息的线程
                ServerHandler handler = new ServerHandler();
                Thread t = new Thread(handler);
                t.start();
    
                 * 将字符串发送至服务端,,,--漏写发送nick昵称了,
                Scanner scan = new Scanner(System.in);
                while(true) {
                    System.out.print("请输入昵称:");
                    String nick = scan.nextLine();
                    if(nick.length()==0) {
                        System.out.println("输入有误,请重新输入!");
                    }else {
                        break;
                    }
                }
                while(true) {
                    System.out.print("说点什么吧:");
                    String str = scan.nextLine();
                    if("".equals(str)) {
                        break;
                    }
                    pw.println(str);
                }
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    socket.close();
                    System.out.println("连接结束");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
         * 该线程用来读取服务端发送过来的消息,并输出到客户端控制台显示。
        class ServerHandler implements Runnable{
            public void run() {
                try {
                    InputStream input = socket.getInputStream();
                    InputStreamReader isr = new InputStreamReader(input,"UTF-8");
                    BufferedReader br = new BufferedReader(isr);
                    while(true) {
                        String str = br.readLine();
                        System.out.println("服务端回复:"+str);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    


    效果如下,有个bug没解决,就是Client输入昵称无效,服务端没有收到,而是把Client说的第一句话当成了nickName····
    原来是输入的nick字符串没有发送到服务器······
     
    pic1.png

    可以 @nickName:word,与其他上线的人交流
     
    pic2.png


    整个程序很简单,要十分注意一点,就是缓冲流要设置自动刷新写入,要不然客户端就收不到消息了
    代码链接:链接:https://pan.baidu.com/s/1LyJHx6ZQiUTqtdQJpJUZUg 密码:ahux

    多人聊天,用Swing和JFrame做的聊天窗口,例子

    与上面的实现是完全不同的方式写的,但基本是一样的。

    可以实现多人聊天,一对一聊天没写。

    发送数据的方式都是经过包装的,发送文字时,首先发送一个字节的数据类型,再发送文字长度,再发送文字内容;

    发送文件时,开始的部分是跟发送文字一样的,在发送文字内容时改为四个字节的文件名称长度,再写入文件名称,后面加上文件内容,取出数据时依据文件类型来特殊取出。

    服务器server端,转发数据给所有上线的客户端

    package day19;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    public class Server {
         * Socket列表,用于存放所有上线的用户Socket
        private static List<Socket> sockets;
        
         * 循环监听8888端口,一有连接就新建线程去处理,再继续监听端口
        public static void main(String[] args) {
            ServerSocket serverSocket;
            sockets = new ArrayList<Socket>();
            try {
                serverSocket = new ServerSocket(8888);
                System.out.println("服务器已启动,等待客户端连接");
                while(true) {
                    Socket socket = serverSocket.accept();
                    sockets.add(socket);
                    
                    offlineNotice(socket, "上线了");
                    new MyThread(socket, sockets).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
         * 对所有上线的客户端消息通知
        public static void offlineNotice(Socket socket, String message) {
            String descClient = "[" + socket.getRemoteSocketAddress() + "]";
            byte[] userByte = getUsersByte();
            for(Socket s : sockets) {
                Utils.write(descClient + message, s, Utils.STRINGTYPE);
                Utils.write(userByte, s, Utils.USERSTYPE);
            }
        }
        
         * 将Sockets取出其中的IP地址和端口,转成字符串添加到List中,再序列化成byte[]返回
        public static byte[] getUsersByte() {
            byte[] userByte = null;
            try {
                List<String> users = new ArrayList<String>();
                for(Socket s : sockets) {
                    users.add("[" + s.getRemoteSocketAddress() + "]");
                }
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(users);
                
                oos.close();
                baos.close();
                userByte = baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return userByte;
        }
    }
    
     * Server对每个客户端连接都启动一个线程负责处理,转发数据
    class MyThread extends Thread{
        private Socket socket;
        private List<Socket> sockets;
        
        public MyThread(Socket socket, List<Socket> sockets) {
            this.socket = socket;
            this.sockets = sockets;
        }
        
         * 循环处理转发数据
        @Override
        public void run() {
            String descClient = "[" + socket.getRemoteSocketAddress() + "]";
            try {
                System.out.println(descClient + "上线了");
                while(true) {
                    System.out.println("上线主机列表:" + sockets);
                    
                    Map<Byte, byte[]> map = Utils.read(socket);
                    Byte type = Utils.getMapKey(map);
                    byte[] data = Utils.getMapValue(map);
                    
                    System.out.println(socket.getInetAddress() + ": 数据类型--" + type + ", 数据--" + new String(data));
                    
                     * 群发消息,排除发出消息的客户端的群发
                    for(Socket s : sockets) {
                        if(s == socket) {
                            continue;
                        }
                        if(type == Utils.STRINGTYPE) {
                            Utils.write(descClient + ":" + new String(data), s, type);
                        }else if(type == Utils.FILETYPE){
                            Utils.write(descClient + "发送文件", s, Utils.STRINGTYPE);
                            Utils.write(data, s, type);
                        }
                    }
                    if("exit".getBytes().equals(data) || data == null) {
                        break;
                    }
                }
                 * 客户端正常下线通知
                sockets.remove(socket);
                socket.close();
                Server.offlineNotice(socket, "下线了");
                System.out.println(descClient + ":下线了");
            } catch (Exception e) {
                 * 客户端异常正常下线通知
                e.printStackTrace();
                sockets.remove(socket);
                Server.offlineNotice(socket, "异常下线了");
                System.out.println(descClient + "异常下线了");
            }
        }
    }
    

    ChatUI界面,整个界面布局,对按钮的事件处理,调用客户端处理连接和显示数据

    package day19;
    
    import java.awt.FileDialog;
    import java.awt.Font;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTextArea;
    import javax.swing.JTextField;
    
    public class ChatUI extends JFrame implements ActionListener{
        private static final long serialVersionUID = 7517634945840465934L;
        public JTextArea chatArea;
        private JButton sendNewsButton;
        private JButton sendFileButton;
        public JTextArea friendsArea;
        private JTextField newsField;
        private Client client = null;
        
        public static void main(String[] args) {
            ChatUI chatui = new ChatUI();
        }
        
        public ChatUI() {
            init();
        }
        
         * 初始化聊天窗口
        public void init() {
            this.setTitle("聊天窗口");
            this.setBounds(270, 80, 800, 600);
            this.setLayout(null);
            
            Font font = new Font("宋体", Font.BOLD, 22);
            
             * 聊天对话界面
            chatArea = new JTextArea();
            JScrollPane chatPane = new JScrollPane(chatArea);
            chatPane.setBounds(30, 20, 450, 400);
            chatArea.setBounds(30, 20, 430, 380);
            chatArea.setFont(font);
            this.add(chatPane);
    
             * 填写发送消息的输入框
            newsField = new JTextField();
            newsField.setBounds(30, 460, 380, 40);
            newsField.setFont(font);
            this.add(newsField);
            
             * 发送按钮
            sendNewsButton = new JButton("发送");
            sendNewsButton.setBounds(450, 460, 60, 40);
            sendNewsButton.addActionListener(this);
            this.add(sendNewsButton);
            
             * 发送文件按钮
            sendFileButton = new JButton("发送文件");
            sendFileButton.setBounds(540, 460, 100, 40);
            sendFileButton.addActionListener(this);
            this.add(sendFileButton);
            
             * 所有上线客户端列表
            friendsArea = new JTextArea();
            friendsArea.setFont(new Font("宋体", Font.BOLD, 18));
            JScrollPane friendsPane = new JScrollPane(friendsArea);
            friendsPane.setBounds(550, 20, 200, 400);
            this.add(friendsPane);
            
             * 添加窗口事件处理程序,使用适配器,监听JFame退出事件
            this.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.out.println("关闭窗口");
                    System.exit(-1);
                }
            });
            
            this.setVisible(true);
             * 实例化一个负责通信的 Client
            client = new Client(this);
        }
        
         * 对两个按钮事件进行处理
        @Override
        public void actionPerformed(ActionEvent e) {
            if(e.getSource() == sendNewsButton) {  * 发送按钮
                String msg = newsField.getText().trim();
                if(msg.length() != 0) {
                    boolean status = Utils.write(msg, client.getSocket(), Utils.STRINGTYPE);
                    if(!status) {
                        msg = "连接出错";
                         * 关闭socket
                        client.close();
                    }
                     * 设置聊天窗口的数据
                    String old;
                    if("
    ".equals(chatArea.getText())) {
                         old = "";
                    }else {
                        old = chatArea.getText();
                    }
                    chatArea.setText(old + "我:" + msg + "
    ");
                    newsField.setText("");
                    
                }
            }else if(e.getSource() == sendFileButton) { * 发送文件按钮
                 * 对于发送文件的处理,需要重构发送的数据格式,定义文件头
                try {
                    FileDialog dialog = new FileDialog(this, "选择文件", FileDialog.LOAD);
                    dialog.setVisible(true);
                    String path = dialog.getDirectory() + dialog.getFile();
                    System.out.println(path);
                    Utils.write(path, new FileInputStream(path), client.getSocket());
                    String old;
                    if("
    ".equals(chatArea.getText())) {
                         old = "";
                    }else {
                        old = chatArea.getText();
                    }
                    chatArea.setText(old + "我发送文件:" + path + "
    ");
                } catch (FileNotFoundException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
    

    Client,ChatUI界面调用Client中的方法来与server交互

    package day19;
    
    import java.io.ByteArrayInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.net.Socket;
    import java.util.List;
    import java.util.Map;
    
     * 负责ChatUI后台与服务器Server进行通信
    public class Client {
        private Socket socket = null;
        
        public Client(ChatUI chatui) {
            getSocket();
            System.out.println("等待读取数据");
            
             * 启动一个守护线程,用于循环读取数据
            Thread t = new Thread() {
                public void run() {
                try {
                    while(true) {
                        Map<Byte, byte[]> map = Utils.read(socket, chatui);
                        Byte type = Utils.getMapKey(map);
                        byte[] buffer = Utils.getMapValue(map);
                         * 检测获得的数据是否是字符串类型
                        if(type == Utils.STRINGTYPE) {
                             * 检测是否满足退出条件
                            if("exit".equals(new String(buffer)) || buffer == null) {
                                System.out.println("连接关闭");
                                socket.close();
                                chatui.chatArea.setText(chatui.chatArea.getText() + "连接关闭" + "
    ");
                                break;
                            }
                             * 将字符串直接写入到聊天窗口中
                            System.out.println("STRINGTYPE = " + new String(buffer));
                            chatui.chatArea.setText(chatui.chatArea.getText() + new String(buffer) + "
    ");
                        }else if(type == Utils.USERSTYPE){  * 检测是否是用户列表类型
                             * 用户列表数据是经过序列化成byte的数据,所以要先转换为对象
                            ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
                            ObjectInputStream ois = new ObjectInputStream(bais);
                            List<String> usersList =  (List<String>) ois.readObject();
                            String d = "";
                            for(String str : usersList) {
                                d = d + str + "
    ";
                            }
                             * 读取所有的用户数据,构造好字符串,填入好友列表框中
                            chatui.friendsArea.setText(d);
                            ois.close();
                            bais.close();
                            
                        }else if(type == Utils.FILETYPE) {  * 检查是否是文件类型
                             * 是文件类型,需要反重构,将文件名称和文件数据分别取出来
                             * 前四个字节是文件名称的长度,接下来是文件名称,最后就是文件数据了
                            System.out.println("接收到文件了:" + new String(buffer));
                             * 文件名称长度
                            byte[] lenByte = new byte[4];
                            System.arraycopy(buffer, 0, lenByte, 0, 4);
                             * 将byte[]转成int类型
                            int lenName = Utils.byte2Int(lenByte);
                            System.out.println("lenName = " + lenName);
                            
                             * 构造一个特定长度的byte[],取出文件名称
                            byte[] nameByte = new byte[lenName];
                            System.arraycopy(buffer, 4, nameByte, 0, lenName);
                             * 构造文件数据长度的byte[],取出文件数据
                            byte[] bufferData = new byte[buffer.length-4-lenName];
                            System.arraycopy(buffer, 4+lenName, bufferData, 0, bufferData.length);
                             * 将文件数据写入文件中
                            FileOutputStream fos = new FileOutputStream("E://TestCase//day20//" + new String(nameByte));
                            fos.write(bufferData);
                            fos.close();
                             * 在聊天窗口中显示文件保存位置
                            chatui.chatArea.setText(chatui.chatArea.getText() + "文件保存在E://TestCase//day20//" + new String(nameByte) + "
    ");
                            System.out.println("文件保存位置 E://TestCase//day20//" + new String(nameByte));
                            }
                        }
                    } catch(Exception e) {
                        e.printStackTrace();
                        try {
                            socket.close();
                            chatui.chatArea.setText(chatui.chatArea.getText() + "异常,连接已关闭" + "
    ");
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
                    }
                }
            };
             * 设置为守护线程,并启动
            t.setDaemon(true);
            t.start();
        }
        
         * 检测socket是否为null,是则创建,可以避免创建多个socket
        public Socket getSocket() {
            if(socket == null) {
                try {
                    socket = new Socket("localhost", 8888);
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("服务器没有开启");
                    System.exit(-1);
                }
            }
            return socket;
        }
        
         * 关闭socket方法
        public void close() {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    Utils,这个是一个工具类,客户端和服务端都是用其内部的方法处理数据

    package day19;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    
    public class Utils {
         * 三种数据类型,用于区分发送的数据
         * 字符串标识
        public final static byte STRINGTYPE = 0;
         * 所有上线用户信息标识
        public final static byte USERSTYPE = 1;
         * 发送文件标识
        public final static byte FILETYPE = 2;
        
         * int转成四字节的byte[],便于写入到socket流中,也便于解码
        public static byte[] int2Byte(int number) {
            byte[] b = new byte[4];
            b[0] = (byte) (number >> 24);
            b[1] = (byte) (number >> 16);
            b[2] = (byte) (number >> 8);
            b[3] = (byte)number;
            return b;
        }
        
         * byte转int,和上面的int2byte对应
        public static int byte2Int(byte[] b) {
            int i3 = (b[0] & 0xFF)<< 24;
            int i2 = (b[1] & 0xFF)<< 16;
            int i1 = (b[2] & 0xFF)<< 8;
            int i0 =  b[3] & 0xFF;
            return i3 | i2 | i1 | i0;
        }
        
         * 重写read方法
        public static Map<Byte, byte[]> read(Socket socket) throws Exception {
            Map<Byte, byte[]> map = read(socket, null);
            return map;
        }
        
         * 获取字典数据(只有一个Entry)中的key,也就是Byte
        public static Byte getMapKey(Map<Byte, byte[]> map) {
            Set<Entry<Byte, byte[]>> entrySet = map.entrySet();
            Iterator<Entry<Byte, byte[]>> it = entrySet.iterator();
            Entry<Byte, byte[]> entry = it.next();
            return entry.getKey();
        }
        
         * 获取字典数据(只有一个Entry)中的value,也就是byte[]数组
        public static byte[] getMapValue(Map<Byte, byte[]> map) {
            Set<Entry<Byte, byte[]>> entrySet = map.entrySet();
            Iterator<Entry<Byte, byte[]>> it = entrySet.iterator();
            Entry<Byte, byte[]> entry = it.next();
            return entry.getValue();
        }
        
         * read方法对封装的数据进行解编码,取出封装的数据,但对数据类型不予区分,只返回类型和数据的map
         * 具体的区分数据类型在client中处理,对于文件类型需要特殊处理
        public static Map<Byte, byte[]> read(Socket socket, ChatUI chatui) throws Exception{
            Map<Byte, byte[]> map = new HashMap<Byte, byte[]>();
            InputStream in = socket.getInputStream();
            byte[] type = new byte[1];
            in.read(type);
            byte[] lengthByte = new byte[4];
            in.read(lengthByte);
            int length = Utils.byte2Int(lengthByte);
    
            byte[] buffer = new byte[length];
            in.read(buffer);
            
            map.put(type[0], buffer);
            return map;
        }
        
         * 重写write方法,使得可以写入文件,但注意不能写入过大的文件,对数据内容进一步重构
        public static void write(String path, FileInputStream fis, Socket socket) {
            try {
                 * 把文件名称也写到数组中,首先是四个字节的文件名称长度,加上文件名称,再加内容
                String fileName = path.substring(path.lastIndexOf("\")+1, path.length());
                 * 文件名称
                byte[] nameByte = fileName.getBytes();
                 * 文件名称长度
                byte[] lenByte = Utils.int2Byte(nameByte.length);
                 * 文件数据
                byte[] buffer = new byte[fis.available()];
                fis.read(buffer);
                 * 新建足够长的数组用于存放合并的数据
                byte[] newByte = new byte[4 + nameByte.length + buffer.length];
                 * 合并数组
                System.arraycopy(lenByte, 0, newByte, 0, lenByte.length);
                System.arraycopy(nameByte, 0, newByte, lenByte.length, nameByte.length);
                System.arraycopy(buffer, 0, newByte, lenByte.length + nameByte.length, buffer.length);
                
                write(newByte, socket, Utils.FILETYPE);
            } catch(Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
         * 重写write方法,使得可以写入String
        public static boolean write(String data, Socket socket, byte type) {
            return write(data.getBytes(), socket, type);
        }
        
         * 将byte类型数据写入流中, 对数据进行编码
         * 首先是数据类型一个字节,第二个是数据内容长度四个字节,第三个是数据内容
        public static boolean write(byte[] data, Socket socket, byte type) {
            try {
                OutputStream out = socket.getOutputStream();
                 * 获得数据类型,数据长度,转成byte
                int length = data.length;
                byte[] typeByte = {(byte)type};
                byte[] lengthByte = Utils.int2Byte(length);
                 * 新建足够长的数组用于存放合成的数据
                byte[] end = new byte[1 + 4 + data.length];
                 * 合并数组
                System.arraycopy(typeByte, 0, end, 0, typeByte.length);
                System.arraycopy(lengthByte, 0, end, 1, lengthByte.length);
                System.arraycopy(data, 0, end, 5, data.length);
                
                out.write(end);
                System.out.println("发送完毕:" + new String(data));
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                try {
                    socket.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                return false;
            }
        }
    }
    

    发送消息


     
    1.png

    可以获得上线的所有客户端,显示在面板上


     
    2.png

    点击发送文件按钮可以发送文件


     

     
  • 相关阅读:
    Redis接口限流
    CentOS7以下DNS失效问题
    Vue的安装与使用
    使用 REDIS保存SESSION信息
    ThinkPHP 阿里云图片上传
    Laravel6.0+easywechat4.1获取用户授权例子与分享示例
    1.oracle创建本地数据库步骤及导入数据
    16、React Native实战之TextInput组件
    14.React Native实战之Navigator组件初探
    13.React Native实战之Text组件
  • 原文地址:https://www.cnblogs.com/awkflf11/p/12588558.html
Copyright © 2020-2023  润新知