基于TCP的网络编程——简单聊天程序

使用TCP网络编程可以实现简单的群聊程序【多线程】

通过《基于TCP的网络编程——基本使用》中的案例演示会发现,如果互相通信,客户端和服务器端的【处理数据】部分完全一致,所以完全可以封装一个线程类,将该过程抽出,服务器可以循环监听,处理接入的多个客户端线程。

//线程类(主要用来处理数据)
public class ChatThread extends Thread{
    private Socket socket;  //这里是构建客户端socket,在服务器类中可以循环接收,启动线程
    public ChatThread() {
    }
    public ChatThread(Socket socket) {
        this.socket = socket;
    }

    //处理数据
    @Override
    public void run() {
        BufferedReader br = null;	//这里方便在最后的finally统一关闭管道
        if (null != socket) {
            try {	//try-catch-finally必须统一处理,尽量不要try中嵌套try-catch
            	//接收部分不能放到循环里面,因为这里是服务器与客户端建立连接的地方,一次连接好就可以
            	//循环体里面仅仅处理流数据
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    String str = br.readLine();
                    if (str.equals("88")) {	//打出88,说明客户端退出了
                        System.out.println(socket.getInetAddress().getHostAddress() + "退出了");
                        break;
                    }
                    System.out.println(socket.getInetAddress().getHostAddress() + "说:" + str);
                }
            } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println(socket.getInetAddress().getHostAddress() + "异常退出");
            } finally {
                try {
                        br.close();
                        socket.close();
                } catch (IOException e) {
                        e.printStackTrace();
                }
            }
        }
    }
}

//服务器端
public class ChatServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(9990);
        System.out.println("服务器启动");
        try {
            while (true) {
                Socket socket = ss.accept();    //循环监听
                new ChatThread(socket).start(); //这里的socket不用最后关闭,因为线程中已经关闭了
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("聊天结束。。");
        } finally {
            ss.close();
        }
    }
}

//客户端
public class ChatClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(InetAddress.getLocalHost().getHostName(), 9990);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        Scanner input = new Scanner(System.in);

        while (true) {
            String str = input.next();
            bw.write(str);
            bw.newLine();   //必须换行,否则无法让服务器读取一行
            bw.flush();
            if (str.equals("88")) {
                break;
            }
        }
        input.close();
        bw.close();
        socket.close();
    }
}

这种聊天程序,只适合群聊,如果还要保证私聊,就必须让客户端以某一格式发送信息,服务器接收,判断到有该字符串,就发生转发到目标客户端,并提供发送者的信息。
让服务器作为中继站,实现客户端和客户端之间的通信。

//线程类,处理数据,包括转发
public class ChatThread extends Thread {
    private Socket socket;
    //创建线程安全的并发集合
    private ConcurrentHashMap<String, Socket> maps;
    public ChatThread() {
    }
    public ChatThread(Socket socket, ConcurrentHashMap<String, Socket> maps) {
        this.socket = socket;
        this.maps = maps;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        if (socket != null) {
            try {
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    String data = br.readLine();
                    if (data == null) {
                        System.out.println(socket.getInetAddress().getHostAddress() + "退出了聊天");
                        break;
                    }
                    System.out.println(socket.getInetAddress().getHostAddress() + "说:" + data);
                    if (data.equals("88")) {
                        System.out.println(socket.getInetAddress().getHostAddress() + "退出了聊天");
                    }

                    //处理转发
                    //要判断是否有需要转发的情况
                    if (data.indexOf(":") < 0) {
                        continue;
                    }
                    //获取ip地址,如果转发,客户的必须按照 “ip地址+ :” 的形式
                    String ip = data.substring(0, data.indexOf(":"));
                    //判断ip地址合法性的格式
                    String pattern = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}";
                    if (ip != null) {
                        //ip不是空,并且ip地址合法
                        if (ip.matches(pattern)) {
                            //从集合中找出对应的socket,属于接受者的socket,建立连接
                            Socket socket = maps.get(ip);
                            if (socket != null) {
                                synchronized (socket) {
                                    //客户端和客户端之间的连接必须要保证同步,否则发生多个客户端同时抢占一个客户端临界资源,发生问题
                                    //可以进行转发,建立输出流
                                    //流不可以关闭,否则return退出,报错
                                    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                                    //内容必须有发送者的信息,这里使用this.socket调取类中sokcet也就是发送者的socket
                                    //内容必须是冒号后面的话
                                    bw.write(this.socket.getInetAddress().getHostAddress() + "说:" + data.substring(data.indexOf(":") + 1));
                                    bw.newLine();
                                    bw.flush();
                                    System.out.println(this.socket.getInetAddress().getHostAddress() + "向" + ip + "发送了信息");
                                }
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println(socket.getInetAddress().getHostAddress() + "退出了聊天");
                //退出聊天后,将其从集合中移除
                maps.remove(socket.getInetAddress().getHostAddress());
            } finally {
                try {
                    br.close();
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

//服务器
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket listener = new ServerSocket(8880);
        //创建集合,必须是线程安全的集合,保证客户端和客户端之间的访问的安全,后面put存储管理多个客户端
        ConcurrentHashMap<String, Socket> maps = new ConcurrentHashMap<>();
        System.out.println("服务器已启动");
        try {
            while (true) {
                Socket socket = listener.accept();
                System.out.println("连接成功:" + socket.getInetAddress().getHostAddress());
                maps.put(socket.getInetAddress().getHostAddress(), socket);
                new ChatThread(socket, maps).start();
            }
        } catch (IOException e) {
            System.out.println("聊天室结束了");
        } finally {
            listener.close();
        }
    }
}

//客户端
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(InetAddress.getLocalHost().getHostName(), 8880);

        //由于客户端会发送也必须能接收,所以必须建立两个线程,发送线程和接收线程
        //发送线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                BufferedWriter bw = null;
                Scanner input = new Scanner(System.in);
                try {
                    //仅仅是建立连接,一次就好
                    bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    while (true) {
                        String data = input.nextLine();
                        bw.write(data);
                        bw.newLine();
                        bw.flush();
                        if (data.equals("88")) {
                            break;
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        bw.close();
                        input.close();
                        //
                        if (!socket.isClosed()) {
                            socket.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        //接收线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                BufferedReader br = null;
                try {
                    br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    //读取线程一直开启,不关闭
                    while (true) {
                        String data = br.readLine();
                        System.out.println(data);
                    }
                } catch (IOException e) {
                    System.out.println("已经退出");
                } finally {
                    try {
                        br.close();
                        //避免socket重复关闭,进行判定
                        if (!socket.isClosed()) {
                            socket.close();
                        }
                    } catch (IOException e) {
                        System.out.println("已经退出");
                        //e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

你可能感兴趣的:(JAVA,Basics,java,多线程)