Java知识点整理(SE) 网络流

这篇博客详细介绍了Java中TCP的三次握手和四次挥手过程,以及如何实现客户端循环向服务端发送消息。在客户端关闭连接时,服务端会接收到断开信号。在多客户端场景下,由于服务端仅调用一次accept方法,导致只有第一个客户端能与服务端通信。问题在于内层循环读取客户端消息未结束,阻止了外层循环继续接受新的连接请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现客户端循环发消息给服务端

客户端代码:

package socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 聊天室客户端
 */
public class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
        并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
        与远端计算机的数据交互工作。
        我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
        风(输出流),通过它们就可以与对方交流了。
     */
    private Socket socket;

    /**
     * 构造方法,用来初始化客户端
     */
    public Client(){
        try {
            System.out.println("正在链接服务端...");
            /*
                实例化Socket时要传入两个参数
                参数1:服务端的地址信息
                     可以是IP地址,如果链接本机可以写"localhost"
                参数2:服务端开启的服务端口
                我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
                的服务端应用程序。
                实例化的过程就是链接的过程,如果链接失败会抛出异常:
                java.net.ConnectException: Connection refused: connect
             */
            socket = new Socket("localhost",8088);
            System.out.println("与服务端建立链接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start(){
        try {
            /*
                Socket提供了一个方法:
                OutputStream getOutputStream()
                该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
             */
            //低级流,将字节通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
            OutputStreamWriter osw = new OutputStreamWriter(out,"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();
    }
}

服务端代码:

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            System.out.println("等待客户端链接...");
            /*
                ServerSocket提供了接受客户端链接的方法:
                Socket accept()
                这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                通过这个Socket就可以与客户端进行交互了。

                可以理解为此操作是接电话,电话没响时就一直等。
             */
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端链接了!");

            /*
                Socket提供的方法:
                InputStream getInputStream()
                获取的字节输入流读取的是对方计算机发送过来的字节
             */
            InputStream in = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(in,"UTF-8");
            BufferedReader br = new BufferedReader(isr);

            String message = null;
            while((message = br.readLine())!=null) {
                System.out.println("客户端说:" + message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

需要注意的几个点:

1:当客户端不再与服务端通讯时,需要调用socket.close()断开链接,此时会发送断开链接的信号给服务端。这时服务端的br.readLine()方法会返回null,表示客户端断开了链接。

2:当客户端链接后不输入信息发送给服务端时,服务端的br.readLine()方法是出于阻塞状态的,直到读取了一行来自客户端发送的字符串。

TCP的三次握手和四次挥手

  • 客户端与服务端建立TCP链接时需要进行三次握手、

     双方断开链接时要进行四次挥手、

    多客户端链接

    之前只有第一个连接的客户端可以与服务端说话。

    原因:

    服务端只调用过一次accept方法,因此只有第一个客户端链接时服务端接受了链接并返回了Socket,此时可以与其交互。

    而第二个客户端建立链接时,由于服务端没有再次调用accept,因此无法与其交互。

    package socket;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * 聊天室服务端
     */
    public class Server {
        /**
         * 运行在服务端的ServerSocket主要完成两个工作:
         * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
         * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
         *   就可以和该客户端交互了
         *
         * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
         * 电话使得服务端与你沟通。
         */
        private ServerSocket serverSocket;
    
        /**
         * 服务端构造方法,用来初始化
         */
        public Server(){
            try {
                System.out.println("正在启动服务端...");
                /*
                    实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                    应用程序占用的端口相同,否则会抛出异常:
                    java.net.BindException:address already in use
    
                    端口是一个数字,取值范围:0-65535之间。
                    6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
                 */
                serverSocket = new ServerSocket(8088);
                System.out.println("服务端启动完毕!");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 服务端开始工作的方法
         */
        public void start(){
            try {
                while(true) {
                    System.out.println("等待客户端链接...");
                    /*
                        ServerSocket提供了接受客户端链接的方法:
                        Socket accept()
                        这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                        的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                        通过这个Socket就可以与客户端进行交互了。
    
                        可以理解为此操作是接电话,电话没响时就一直等。
                     */
                    Socket socket = serverSocket.accept();
                    System.out.println("一个客户端链接了!");
                    /*
                        Socket提供的方法:
                        InputStream getInputStream()
                        获取的字节输入流读取的是对方计算机发送过来的字节
                     */
                    InputStream in = socket.getInputStream();
                    InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                    BufferedReader br = new BufferedReader(isr);
    
                    String message = null;
                    while ((message = br.readLine()) != null) {
                        System.out.println("客户端说:" + message);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            Server server = new Server();
            server.start();
        }
    }

    添加循环操作后,发现依然无法实现。

    原因在于:

    外层的while循环里面嵌套了一个内层循环(循环读取客户端发送消息),而循环执行机制决定了里层循环不结束,外层循环则无法进入第二次操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值