客户端服务端互通小项目

本文展示了如何使用Java实现一个简单的聊天室客户端和服务端。客户端通过Socket连接服务端,服务端监听特定端口并为每个连接创建新线程进行交互。双方通过输入输出流进行消息传递,支持多客户端同时在线并广播消息。
摘要由CSDN通过智能技术生成
/**
 * 聊天室客户端
 */
public class Client {
    /*
        java.net.Socket 插座 套接字
        Socket封装了TCP协议的通讯细节,使用它可以和服务端建立TCP连接,并基于两个流的
        读写完成数据交换。
     */
    private Socket socket;

    /**
     * 构造方法,用于初始化客户端
     */
    public Client(){
        try {
            /*
                实例化Socket时常用的构造方法:
                Socket(String host,int port)
                这个构造器实例化Socket的过程就是与服务端建立连接的过程。
                参数1:服务端的IP地址
                参数2:服务端开启的服务端口
                我们通过服务端的IP可以找到网络上服务端所在的计算机。通过端口号可以找到
                该机器上的服务端应用程序从而与之建立连接。
             */
            //本机IP地址的写法可以是“localhost”
            System.out.println("正在连接服务端...");
            socket = new Socket("localhost",8088);
            System.out.println("与服务端建立连接了!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start(){
        try {
            //启动用于读取服务端发送过来消息的线程
            ServerHandler handler = new ServerHandler();
            Thread t = new Thread(handler);
            t.start();

            /*
                通过socket获取的字节输出流写出的字节会通过网络发送给远端计算机
             */
            OutputStream out = socket.getOutputStream();
            OutputStreamWriter osw
                    = new OutputStreamWriter(out, StandardCharsets.UTF_8);
            BufferedWriter bw = new BufferedWriter(osw);
            PrintWriter pw = new PrintWriter(bw,true);

            Scanner scanner = new Scanner(System.in);
            while(true) {
                String line = scanner.nextLine();
                if("exit".equalsIgnoreCase(line)){
                    break;
                }
                pw.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /*
                    Socket提供了close方法,可以与远端计算机断开连接。
                    该方法调用时,也会自动关闭通过它获取的输入流和输出流。
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }

    /**
     * 该线程任务负责处理服务端发送过来的消息
     */
    private class ServerHandler implements Runnable{
        public void run(){
            try{
                //通过socket获取输入流读取服务端发送过来的消息
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                String line;
                //循环读取服务端发送过来的每一行字符串
                while((line = br.readLine())!=null){
                    System.out.println(line);
                }

            }catch(IOException e){

            }
        }
    }

}
/**
 * 聊天室服务端
 */
public class Server {
    /**
     * java.net.ServerSocket
     * ServerSocket是运行在服务端上的。其主要有两个作用
     * 1:向服务端申请服务端口(客户端Socket就是通过这个端口与服务端建立连接的)
     * 2:监听服务端口,一旦客户端连接会立即常见一个Socket,通过该Socket与客户端交互
     *
     * 如果我们将Socket比喻为"电话",那么ServerSocket相当于"总机"
     */
    private ServerSocket serverSocket;
    private PrintWriter[] allOut = {};


    public Server(){
        try {
            /*
                ServerSocket在创建的时候要申请一个固定的端口号,客户端才能通过这个
                端口建立连接。
                如果该端口被当前操作系统中其他程序使用了,那么这里实例化会抛出异常:
                java.net.BindException:address already in use
                绑定异常:地址被使用了
             */
            System.out.println("正在启动服务端...");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            /*
                ServerSocket的accept方法是一个阻塞方法。
                开始等待客户端的连接,一旦一个客户端通过端口建立连接,此时accept方法
                会立即返回一个Socket实例。通过该实例可以与该客户端进行交互。
                相当于是接电话的动作。
                阻塞方法:调用后,程序就"卡住"不往下执行了。
             */
            while(true) {
                System.out.println("等待客户端连接");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了!");
                //启动一个线程来处理该客户端的交互
                //Client:客户端  Handler:处理器
                ClientHandler clientHandler = new ClientHandler(socket);
                Thread thread = new Thread(clientHandler);
                thread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 该线程任务是用一个线程来处理一个客户端的交互工作
     */
    private class ClientHandler implements Runnable{
        private Socket socket;
        private String host;//记录远端计算机的地址信息

        public ClientHandler(Socket socket){
            this.socket = socket;
            host = socket.getInetAddress().getHostAddress();
        }

        public void run(){
            PrintWriter pw = null;
            try {
                //通过socket获取输入流读取对方发送过来的消息
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                //通过socket获取输出流用于给对方发送消息
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw
                        = new OutputStreamWriter(out, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw,true);

                //将该输出流存入共享数组allOut中
                //在内部类访问外部类对象的时候用外部类名.this
                //所以将本类设置成内部类
                synchronized (Server.this) {
                    //1扩容allOut
                    allOut = Arrays.copyOf(allOut, allOut.length + 1);
                    //2将pw放到数组最后一个格子里
                    allOut[allOut.length - 1] = pw;
                }

                //通知所有客户端,该用户上线了!
                sendMessage(host+"上线了,当前在线人数:"+allOut.length);

                String line;
                /*
                    这里的BufferedReader读取时低下连接的流是通过Socket获取的输入流,
                    当远端计算机还处于连接状态,但是暂时没有发送内容时,readLine方法会
                    处于阻塞状态,直到对方发送过来一行字符串为止。
                    如果返回值为null,则表示对方断开了连接(对方调用了socket.close())。

                    对于windows的客户端而言,如果是强行杀死的进程,服务端这里readLine方法
                    会抛出下面异常:
                    java.net.SocketException: connection reset
                    服务端无法避免这个异常。
                 */
                while ((line = br.readLine()) != null) {
                    //遍历allOut数组,将消息发送给所有客户端
                    sendMessage(host+"说:" + line);
                }
            }catch(IOException e){
//                e.printStackTrace();
            }finally {
                //处理客户端断开连接后的操作
                synchronized (Server.this) {
                    //将pw从数组allOut中删除
                    for (int i = 0; i < allOut.length; i++) {
                        if (allOut[i] == pw) {
                            allOut[i] = allOut[allOut.length - 1];
                            allOut = Arrays.copyOf(allOut, allOut.length - 1);
                            break;
                        }
                    }
                }
                //通知所有客户端,该用户下线了!
                sendMessage(host+"下线了,当前在线人数:"+allOut.length);


                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 将消息群发给所有客户端
         * @param line
         */
        private void sendMessage(String line){
            synchronized (Server.this) {
                System.out.println(line);
                //遍历allOut数组,将消息发送给所有客户端
                for (int i = 0; i < allOut.length; i++) {
                    allOut[i].println(line);
                }
            }
        }

    }


}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值