第一节:网络编程02

第一节:网络编程02

三. UDP通信过程

【1】发送Send

  • 创建DatagramSocket, 随机端口号
  • 创建DatagramPacket, 指定数据, 长度, 地址, 端口
  • 使用DatagramSocket发送DatagramPacket
  • 关闭DatagramSocket

【2】接收Receive

  • String ip = packet.getAddress().getHostAddress();
  • int port = packet.getPort();

1. 具体实现细节(案例)

目的:让一个发送端发送数据,接收端接受数据。

发送方
注意:发送数据必须封装成DatagramPacket

/**
 * 发送端
 * 1. 使用DatagramSocket 指定端口 创建发送端
 * 2. 准备数据 转成字节数组
 * 3.封装成DatagramPacket包裹,需要指定目的地
 * 4.发送包裹send(DatagramPacket p)
 * 5.释放资源
 */
public class UDPClient {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("发送方启动中.....");
         //1. 使用DatagramSocket 指定端口 创建发送端
        DatagramSocket client = new DatagramSocket(8888);
         //2. 准备数据 转成字节数组
        byte [] datas = "你好java".getBytes();
         //3.封装成DatagramPacket包裹,需要指定目的地,送往端口号为:9999
        DatagramPacket packet = new DatagramPacket(datas,0,datas.length,
                new InetSocketAddress("localhost",9999));
         //4.发送包裹send(DatagramPacket p)
        client.send(packet);
         //5.释放资源
        client.close();

    }
}

接受端
注意:接受端必须对发送端的数据进行解析

/**
 * 接受端  Address already in use: bind 同一个协议下端口号不允许冲突
 * 1. 使用DatagramSocket 指定端口 创建接受端
 * 2. 准备容器 封装成DatagramPacket包裹
 * 3.阻塞式接受包裹 void receive(DatagramPacket p)
 * 4.分析数据
 *    byte[] getData()
 *     int getLength()
 * 5.释放资源
 */
public class UDPServer {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("接受方启动中.....");
        // 1. 使用DatagramSocket 指定端口 创建接受端
        DatagramSocket server = new DatagramSocket(9999);
        // 2. 准备容器 封装成DatagramPacket包裹
        byte[] container = new byte[1024*60];
        DatagramPacket packet = new DatagramPacket(container,0,container.length);
        //3.阻塞式接受包裹 void receive(DatagramPacket p)
        server.receive(packet);
        //4.分析数据
        //    byte[] getData()
        //     int getLength()
        byte [] datas = packet.getData();
        int len = packet.getLength();
        System.out.println(new String(datas,0,len));
        //5.释放资源
        server.close();
    }
}

2. 多次发送与多次接受(案例)

目的:让发送端多次发送数据,接收端多次接受数据。
接收端

public class UDPServer {
    
    
    public static void main(String[] args) throws Exception{
    
    
        //1. 使用DatagramSocket 指定端口 创建接受端
        DatagramSocket server = new DatagramSocket(9999);
        //2. 准备容器 封装成DatagramPacket包裹
        byte[] container = new byte[1024*60];
        DatagramPacket packet = new DatagramPacket(container,0,container.length);
        //3.阻塞式接受包裹 void receive(DatagramPacket p)
        while (true){
    
    
            server.receive(packet);
            //4.分析数据
            //   byte[] getData()
            //   int getLength()
            byte[] datas = packet.getData();
            int len = packet.getLength();
            String data = new String(datas,0,len);
            System.out.println(data);
            if (data.equals("bye")){
    
    
                break;
            }
        }
        //5.释放资源
        server.close();
    }
}

发送端

/*
    发送端可以利用控制台向接收端发送多次
 */
public class UDPClient {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("发送方启动中.....");
        //1. 使用DatagramSocket 指定端口 创建发送端
        DatagramSocket client = new DatagramSocket(8888);
        //2. 准备数据 转成字节数组
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while (true){
    
    
            //3.封装成DatagramPacket包裹,需要指定目的地
            String data = reader.readLine();
            byte[] datas = data.getBytes();
            DatagramPacket packet = new DatagramPacket(datas,0,datas.length,
                    new InetSocketAddress("localhost",9999));
            //4.发送包裹send(DatagramPacket p)
            client.send(packet);
            if (data.equals("bye")) {
    
    
                break;
            }
        }
        //5.释放资源
        client.close();
    }
}

3. 让一个客户与另一个客户通信。

目的:每个客户都具有发送和接受功能
客户一
注意:客户一的自身端口号为:7777,发往的端口号为:9999,接收数据的端口号为:9999.

/*
    采用多线程的方式,使得任何一个客户端都能够收发数据
        两个线程:
            一个线程负责接受
            一个线程负责发
 */
public class UDPSend {
    
    
    public static void main(String[] args) {
    
       //确定自身端口号为:7777,送往端口号9999,接受数据的端口号8888
        /*
            先做一个发的线程
         */
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //1. 使用DatagramSocket 指定端口 创建发送端
                DatagramSocket client = null;
                try {
    
    
                    client = new DatagramSocket(7777);
                    //2. 准备数据 转成字节数组
                    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                    while (true){
    
    
                        //3.封装成DatagramPacket包裹,需要指定目的地
                        String data = reader.readLine();
                        byte[] datas = data.getBytes();
                        DatagramPacket packet = new DatagramPacket(datas,0,datas.length,
                                new InetSocketAddress("localhost",9999));
                        //4.发送包裹send(DatagramPacket p)
                        client.send(packet);
                        if (data.equals("bye")) {
    
    
                            break;
                        }
                    }
                } catch (SocketException e) {
    
    
                    e.printStackTrace();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //5.释放资源
                    client.close();
                }
            }
        }).start();
        /*
            再做一个收的线程
         */
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //1. 使用DatagramSocket 指定端口 创建接受端
                DatagramSocket server = null;
                try {
    
    
                    server = new DatagramSocket(8888);
                    //2. 准备容器 封装成DatagramPacket包裹
                    byte[] container = new byte[1024*60];
                    DatagramPacket packet = new DatagramPacket(container,0,container.length);
                    //3.阻塞式接受包裹 void receive(DatagramPacket p)
                    while (true){
    
    
                        server.receive(packet);
                        //4.分析数据
                        //   byte[] getData()
                        //   int getLength()
                        byte[] datas = packet.getData();
                        int len = packet.getLength();
                        String data = new String(datas,0,len);
                        System.out.println("老师说:" + data);
                        if (data.equals("bye")){
    
    
                            break;
                        }
                    }
                } catch (SocketException e) {
    
    
                    e.printStackTrace();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //5.释放资源
                    server.close();
                }
            }
        }).start();
    }
}

客户二
注意:客户二自身端口号为:5555,发往端口号8888,接收数据端口号9999

public class UDPReceive {
    
    
    public static void main(String[] args) {
    
      //确定自身端口号为:5555,发往端口号8888,接收数据端口号9999
         /*
            先做一个发的线程
         */
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //1. 使用DatagramSocket 指定端口 创建发送端
                DatagramSocket client = null;
                try {
    
    
                    client = new DatagramSocket(5555);
                    //2. 准备数据 转成字节数组
                    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                    while (true){
    
    
                        //3.封装成DatagramPacket包裹,需要指定目的地
                        String data = reader.readLine();
                        byte[] datas = data.getBytes();
                        DatagramPacket packet = new DatagramPacket(datas,0,datas.length,
                                new InetSocketAddress("localhost",8888));
                        //4.发送包裹send(DatagramPacket p)
                        client.send(packet);
                        if (data.equals("bye")) {
    
    
                            break;
                        }
                    }
                } catch (SocketException e) {
    
    
                    e.printStackTrace();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //5.释放资源
                    client.close();
                }
            }
        }).start();
        /*
            再做一个收的线程
         */
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //1. 使用DatagramSocket 指定端口 创建接受端
                DatagramSocket server = null;
                try {
    
    
                    server = new DatagramSocket(9999);
                    //2. 准备容器 封装成DatagramPacket包裹
                    byte[] container = new byte[1024*60];
                    DatagramPacket packet = new DatagramPacket(container,0,container.length);
                    //3.阻塞式接受包裹 void receive(DatagramPacket p)
                    while (true){
    
    
                        server.receive(packet);
                        //4.分析数据
                        //   byte[] getData()
                        //   int getLength()
                        byte[] datas = packet.getData();
                        int len = packet.getLength();
                        String data = new String(datas,0,len);
                        System.out.println("学生说:" + data);
                        if (data.equals("bye")){
    
    
                            break;
                        }
                    }
                } catch (SocketException e) {
    
    
                    e.printStackTrace();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //5.释放资源
                    server.close();
                }
            }
        }).start();
    }
}

四. 采用TCP通信原理,实现一个简单的群聊案例。

1. 第一版:实现一个客户可以收发一条消息。

服务端

/**
 * 在线聊天室:服务器
 * 目标:实现一个客户可以正常收发信息
 */
public class Chat {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("----------Server------------");
        //1.指定端口 使用ServerSocket创建服务器
        ServerSocket server = new ServerSocket(8888);
        //2.阻塞式等待连接accept
        Socket client = server.accept();
        System.out.println("一个客户建立了连接");
        //3.接受消息
        DataInputStream dis = new DataInputStream(client.getInputStream());
        String msg = dis.readUTF();
        //4.返回消息
        DataOutputStream dos = new DataOutputStream(client.getOutputStream());
        dos.writeUTF(msg);
        dos.flush();

        //释放资源
        dos.close();
        dis.close();
        server.close();
    }
}

客户端

扫描二维码关注公众号,回复: 13591067 查看本文章
public class Client {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("------Client--------");
        //1.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
        Socket client = new Socket("localhost",8888);
        //2. 客户端发送消息
        BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
        String msg = console.readLine();

        DataOutputStream dos = new DataOutputStream(client.getOutputStream());
        dos.writeUTF(msg);
        dos.flush();

        //3.获取消息
        DataInputStream dis = new DataInputStream(client.getInputStream());
        msg = dis.readUTF();
        System.out.println(msg);

        //释放资源
        dos.close();
        dis.close();
        client.close();
    }
}

存在问题:虽然可以客户端和服务器可以连通,但是只能收发一条消息。
解决方法:采用While循环的方式,实现可以收发多条消息。

2. 第二版:实现一个客户可以收发多条消息。

/**
 * 在线聊天室:服务器
 * 目标:实现一个客户可以正常收发多条信息
 */
public class MultiChat {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("----------Server------------");
        //1.指定端口 使用ServerSocket创建服务器
        ServerSocket server = new ServerSocket(8888);
        //2.阻塞式等待连接accept
        Socket client = server.accept();
        DataInputStream dis = new DataInputStream(client.getInputStream());
        DataOutputStream dos = new DataOutputStream(client.getOutputStream());
        System.out.println("一个客户建立了连接");

        boolean isRunning = true;
        while (isRunning){
    
    
            //3.接受消息
            String msg = dis.readUTF();
            //4.返回消息
            dos.writeUTF(msg);
        }
        dos.flush();
        //释放资源
        dos.close();
        dis.close();
        server.close();

    }
}
public class MultiClient {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("------Client--------");
        //1.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
        Socket client = new Socket("localhost",8888);
        //2. 客户端发送消息
        BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
        DataOutputStream dos = new DataOutputStream(client.getOutputStream());
        DataInputStream dis = new DataInputStream(client.getInputStream());
        boolean isRunning = true;
        while (isRunning){
    
    
            String msg = console.readLine();
            dos.writeUTF(msg);
            //3.获取消息
            msg = dis.readUTF();
            System.out.println(msg);
        }
        dos.flush();
        //释放资源
        dos.close();
        dis.close();
        client.close();
    }
}

存在问题:虽然实现了一个用户可以正常收发消息,但是服务器只能接入一个用户。
解决方法:采用While循环的方式,持续接收用户。

3. 第三版:实现一个服务器可以接受多个用户。

/**
 * 在线聊天室:服务器
 * 目标:实现多个客户可以正常收发多条信息  :外层加入while循环后可以一直的去获取其他用户的连接(实现了多个用户的使用)
 *
 * 问题:在当前的main线程中必须等待第一个接入的用户退出后才能接入第二个用户(虽然实现了多个用户的接入,
 *      但是存在用户之前排队的问题)即没有使用多线程
 */
public class MultiChat {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("----------Server------------");
        //1.指定端口 使用ServerSocket创建服务器
        ServerSocket server = new ServerSocket(8888);
        //2.阻塞式等待连接accept
        while (true){
    
    
            Socket client = server.accept();
            DataInputStream dis = new DataInputStream(client.getInputStream());
            DataOutputStream dos = new DataOutputStream(client.getOutputStream());
            System.out.println("一个客户建立了连接");
            boolean isRunning = true;
            while (isRunning){
    
    
                //3.接受消息
                String msg = dis.readUTF();
                //4.返回消息
                dos.writeUTF(msg);
            }
            //释放资源
            dos.flush();
            dos.close();
            dis.close();
            server.close();
        }
    }
}

所存在问题:虽然实现了可以接受多个用户的接入,但是无法同时接入过个用户(即存在用户等待缺陷:必须让第一个用户关闭连接,下一个用户才能接入)
解决方法:让服务端每接入一个用户就开启一个线程(即采用多线程的方式)。

4. 第四版:实现一个服务器可以接受多个用户并且不存在用户排队问题。

/**
 * 在线聊天室:服务器
 * 目标:使用多线程实现多个客户可以正常收发多条信息
 * 问题 1. 代码不好维护
 *      2.客户端读写没有分开 必须先写后读
 */
public class ThreadMultiChat {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("----------Server------------");
        //1.指定端口 使用ServerSocket创建服务器
        ServerSocket server = new ServerSocket(8888);
        //2.阻塞式等待连接accept
        while (true){
    
    
            Socket client = server.accept();

            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    DataInputStream dis = null;
                    DataOutputStream dos = null;

                    try {
    
    
                        dis = new DataInputStream(client.getInputStream());
                        dos = new DataOutputStream(client.getOutputStream());
                        System.out.println("一个客户建立了连接");
                        boolean isRunning = true;
                        while (isRunning){
    
    
                            //3.接受消息
                            String msg = dis.readUTF();
                            //4.返回消息
                            dos.writeUTF(msg);
                        }
                        dos.flush();
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }finally {
    
    
                        //释放资源
                        try {
    
    
                            if (dos != null) {
    
    
                                dos.close();
                            }
                        } catch (IOException e) {
    
    
                            e.printStackTrace();
                        }
                        try {
    
    
                            if (dis != null) {
    
    
                                dis.close();

                            }
                        } catch (IOException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        //server.close();服务器不用关闭了
    }
}

存在问题:虽然解决了用户排队问题,但是服务端代码不好维护,代码没有封装。
解决方法:对服务端代码进行封装。

5. 第五版:对服务端代码进行封装

/**
 * 在线聊天室:服务器
 * 目标:使用静态内部类的方式对“多线程实现多个客户可以正常收发多条信息”进行封装
 */
public class ThreadMultiChat {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("----------Server------------");
        //1.指定端口 使用ServerSocket创建服务器
        ServerSocket server = new ServerSocket(8888);
        //2.阻塞式等待连接accept
        while (true){
    
    
            Socket client = server.accept();
            System.out.println("一个客户端建立了连接");
            new Thread(new Channel(client)).start();
        }
        //server.close();服务器不用关闭了
    }
    //一个客户一个Channel
    static class Channel implements Runnable{
    
    
        private DataInputStream dis;
        private DataOutputStream dos;
        private Socket client;
        private boolean isRunning;

        public Channel(Socket client) {
    
    
            this.client = client;
            try {
    
    
                dis = new DataInputStream(client.getInputStream());
                dos = new DataOutputStream(client.getOutputStream());
                isRunning = true;
            } catch (IOException e) {
    
    
                //e.printStackTrace();
                //如果这里出现了异常,直接退出释放资源
                System.out.println("构造方法出问题");
                release();
            }
        }

        //接受消息
        private String receive(){
    
    
            String msg = "";
            try {
    
    
                msg= dis.readUTF();
            } catch (IOException e) {
    
    
                //如果这里出现了异常,直接退出释放资源
                System.out.println("接受消息出问题");
                release();
            }
            return msg;
        }
        //发送消息
        private void send(String msg){
    
    
            //4.返回消息
            try {
    
    
                dos.writeUTF(msg);
                dos.flush();
            } catch (IOException e) {
    
    
                //e.printStackTrace();
                //如果这里出现了异常,直接退出释放资源
                System.out.println("发送消息出问题了");
                release();
            }
        }
        //释放资源
        private void release(){
    
    
            this.isRunning = false;
            Utils.close(dis,dos,client);
        }
        @Override
        public void run() {
    
    
            while (isRunning){
    
    
                String msg = receive();
                //当我们读取到的消息不为空时我们才回复消息,避免空指针异常
                if (!msg.equals("")){
    
    
                    send(msg);
                }
            }
        }
    }
}
/**
 * 工具类
 *  用与释放资源
 */
public class Utils {
    
    
    public static void close(Closeable... targets) {
    
    
        for (Closeable target:targets) {
    
    
            try {
    
    
                if (null != target ) {
    
    
                    target.close();
                }
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

存在问题:虽然对服务端代码进行了封装,但是客户端代码没有进行封装,按照oop原则来说,客户端存在读和写两种状态,因此,应该对客户端进行读写分离
解决方法:对客户端代码分别创建两个工具类,一个接受信息的类Receive,一个发送信息的类Send。
思考:为什么要对读和写两个类进行多线程改造??

  • 读和写虽然分离了,但我们在创建一个客户端时,总不能够必须让读完才能写,或者是写完才能读。即必须同时进行。
  • 当我们创建多个用户时,如果不采用多线程的形式,那么在创建用户的类中就存在了等待的状态,因此必须使用多线程。

6. 第六版:对客户端代码进行读写分离并封装。

发送端

/**思考:1.如果我们想创建多个用户进行发送时:总不能重写多个用户
 *      2.为什么用户需要继承Runnable接口呢??
 *          因为当我们在一个类中创建多个用户时,如果这个用户不是线程类的话我们就会陷入线程等待环节,
 *          虽然我们可以直接使用匿名内部类的形式创建,但是我们需要重写了太对的run方法,所以要继承Runnable接口
 *        
 *  使用多线程分装了发送端
 */
public class Send implements Runnable{
    
    
    private BufferedReader console;
    private DataOutputStream dos;
    private Socket client;
    private  boolean isRunning;

    //构造方法
    public Send(Socket client) {
    
    
        this.client = client;
        console = new BufferedReader(new InputStreamReader(System.in));
        try {
    
    
            dos = new DataOutputStream(client.getOutputStream());
            isRunning = true;
        } catch (IOException e) {
    
    
            //e.printStackTrace();
            System.out.println("客户端构造方法出现问题");
            release();
        }
    }

    @Override
    public void run() {
    
    
        while (isRunning){
    
    
            //发送端
            String msg = getStrFromConsole();
            if (!msg.equals("")) {
    
    
                send(msg);
            }
        }
    }

    //发送消息
    private void send(String msg){
    
    
        try {
    
    
            dos.writeUTF(msg);
        } catch (IOException e) {
    
    
            System.out.println("发送端出现问题");
            release();
        }
    }

    //从控制台获取消息
    private String getStrFromConsole(){
    
    
        try {
    
    
            return console.readLine();
        } catch (IOException e) {
    
    
            e.printStackTrace();
            System.out.println("读取消息出现问题");
            release();
        }
        return "";
    }
    //释放资源
    private void release(){
    
    
        this.isRunning = false;
        Utils.close(dos,client);
    }
}

接收端

public class Receive implements Runnable{
    
    
    private DataInputStream dis;
    Socket client;
    private boolean isRunning;
    public Receive(Socket client) {
    
    
        this.client = client;
        try {
    
    
            dis = new DataInputStream(client.getInputStream());
            isRunning = true;
        } catch (IOException e) {
    
    
            System.out.println("构造方法出问题");
            release();
        }
    }
    @Override
    public void run() {
    
    
        while (isRunning){
    
    
            String msg = receive();
            if (!msg.equals("")) {
    
    
                System.out.println(msg);
            }
        }
    }
    //接收消息
    private String receive(){
    
    
        String msg = "";
        try {
    
    
            msg = dis.readUTF();
            return msg;
        } catch (IOException e) {
    
    
            System.out.println("接收消息出现问题");
            release();
        }
        return "";
    }
    //释放资源
    private void release(){
    
    
        this.isRunning = false;
        Utils.close(dis,client);
    }
}

客户端

public class ThreadMultiClient {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("------Client--------");
        //1.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
        Socket client = new Socket("localhost",8888);
        //2. 一个客户端有接收端和发送端两个端口
        //发送端
        new Thread(new Send(client)).start();
        //接收端
        new Thread(new Receive(client)).start();
    }
}

存在问题:虽然解决了一系列问题,但是我们并没有实现群聊,只能够让一个用户进行读写,其他用户读取不到另一个用户所发送的消息。
解决方法:在服务端中,利用容器的方式,将接入的用户全存放在容器当中,当用户A向服务端发送消息时,我们利用容器获取到用户B,然后将用户A发送给服务器的消息转发给用户B。

6. 第七版:实现群聊。

  • 客户端发送消息端口
public class Send implements Runnable{
    
    
    private BufferedReader console;
    private DataOutputStream dos;
    private Socket client;
    private  boolean isRunning;
    private String name;

    //构造方法
    public Send(Socket client,String name) {
    
    
        this.name = name;
        this.client = client;
        console = new BufferedReader(new InputStreamReader(System.in));
        try {
    
    
            dos = new DataOutputStream(client.getOutputStream());
            send(name);
            isRunning = true;
        } catch (IOException e) {
    
    
            //e.printStackTrace();
            System.out.println("客户端构造方法出现问题");
            release();
        }
    }

    @Override
    public void run() {
    
    
        while (isRunning){
    
    
            //发送端
            String msg = getStrFromConsole();
            if (!msg.equals("")) {
    
    
                send(msg);
            }
        }
    }

    //发送消息
    private void send(String msg){
    
    
        try {
    
    
            dos.writeUTF(msg);
        } catch (IOException e) {
    
    
            System.out.println("发送端出现问题");
            release();
        }
    }

    //从控制台获取消息
    private String getStrFromConsole(){
    
    
        try {
    
    
            return console.readLine();
        } catch (IOException e) {
    
    
            e.printStackTrace();
            System.out.println("读取消息出现问题");
            release();
        }
        return "";
    }
    //释放资源
    private void release(){
    
    
        this.isRunning = false;
        Utils.close(dos,client);
    }
}
  • 客户端接收消息端口
public class Receive implements Runnable{
    
    
    private DataInputStream dis;
    Socket client;
    private boolean isRunning;
    public Receive(Socket client) {
    
    
        this.client = client;
        try {
    
    
            dis = new DataInputStream(client.getInputStream());
            isRunning = true;
        } catch (IOException e) {
    
    
            System.out.println("构造方法出问题");
            release();
        }
    }

    @Override
    public void run() {
    
    
        while (isRunning){
    
    
            String msg = receive();
            if (!msg.equals("")) {
    
    
                System.out.println(msg);
            }
        }

    }
    //接收消息
    private String receive(){
    
    
        String msg = "";
        try {
    
    
            msg = dis.readUTF();
            return msg;
        } catch (IOException e) {
    
    
            System.out.println("接收消息出现问题");
            release();
        }
        return "";
    }
    //释放资源
    private void release(){
    
    
        this.isRunning = false;
        Utils.close(dis,client);
    }
}
  • 服务器端
public class Chat {
    
    
    private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<>();
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("----------Server------------");
        //1.指定端口 使用ServerSocket创建服务器
        ServerSocket server = new ServerSocket(8888);
        //2.阻塞式等待连接accept
        while (true){
    
    
            Socket client = server.accept();
            System.out.println("一个客户端建立了连接");
            Channel c = new Channel(client);
            all.add(c);//将所有的客户端加入到容器里面,管理所有的客户端
            new Thread(c).start();
        }
    }
    //一个客户一个Channel
    static class Channel implements Runnable{
    
    
        private DataInputStream dis;
        private DataOutputStream dos;
        private Socket client;
        private boolean isRunning;
        private String name;
        public Channel(Socket client) {
    
    
            this.client = client;
            try {
    
    
                dis = new DataInputStream(client.getInputStream());
                dos = new DataOutputStream(client.getOutputStream());
                isRunning = true;
                //获取名称
                this.name = receive();
                //欢迎你的到来
                this.send("欢迎你的到来");
                sendOthers(this.name + "来了聊天室",true);

            } catch (IOException e) {
    
    
                //e.printStackTrace();
                //如果这里出现了异常,直接退出释放资源
                System.out.println("构造方法出问题");
                release();
            }
        }
        //接受消息
        private String receive(){
    
    
            String msg = "";
            try {
    
    
                msg= dis.readUTF();
            } catch (IOException e) {
    
    
                //如果这里出现了异常,直接退出释放资源
                System.out.println("接受消息出问题");
                release();
            }
            return msg;
        }
        //发送消息
        private void send(String msg){
    
    
            //4.返回消息
            try {
    
    
                dos.writeUTF(msg);
                dos.flush();
            } catch (IOException e) {
    
    
                //e.printStackTrace();
                //如果这里出现了异常,直接退出释放资源
                System.out.println("发送消息出问题了");
                release();
            }
        }
        /**
         * 获取自己的消息,发给其他人
         * @param msg
         * 私聊:数据格式为:@xxx:msg
         */
        private void sendOthers(String msg,boolean isSys) {
    
    
            boolean isPrivate = msg.startsWith("@");
            if (isPrivate) {
    
    //私聊
                int idx = msg.indexOf(":");
                //获取目标和数据
                String targetName = msg.substring(1, idx);
                msg = msg.substring(idx + 1);
                for (Channel other : all) {
    
    
                    if (other.name.equals(targetName)) {
    
    
                        other.send(this.name + "悄悄对您说:" + msg);
                    }
                }
            } else {
    
    
                for (Channel other : all) {
    
    
                    if (other == this) {
    
    
                        continue;
                    }
                    if (!isSys) {
    
    
                        other.send(this.name + "对所有人说:" + msg);
                    } else {
    
    
                        other.send(msg);//系统消息
                    }
                }
            }
        }
        //释放资源
        private void release(){
    
    
            this.isRunning = false;
            Utils.close(dis,dos,client);
            //退出
            all.remove(this);
            sendOthers(this.name + "离开了大家庭。。。。",true);
        }
        @Override
        public void run() {
    
    
            while (isRunning){
    
    
                String msg = receive();
                //当我们读取到的消息不为空时我们才回复消息,避免空指针异常
                if (!msg.equals("")){
    
    
                    sendOthers(msg,false);
                }
            }
        }
    }
}
  • 客户端
public class Client {
    
    
    public static void main(String[] args) throws Exception{
    
    
        System.out.println("------Client--------");
        //1.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
        Socket client = new Socket("localhost",8888);
        //2. 一个客户端有接收端和发送端两个端口
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入用户名");
        String name = br.readLine();
        //发送端
        new Thread(new Send(client,name)).start();
        //接收端
        new Thread(new Receive(client)).start();
    }
}
  • 工具类
public class Utils {
    
    
    public static void close(Closeable... targets) {
    
    
        for (Closeable target:targets) {
    
    
            try {
    
    
                if (null != target ) {
    
    
                    target.close();
                }
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_28384023/article/details/110391404
今日推荐