【网络编程】——Java实现(7)—— 1~6章的实践中常见问题集锦

这里是Java网络编程Java Socket编程相关的学习手记。这里按照官方的Java 8 Toturial教程的Custom Networking学习路径,对相关的一些内容进行解读(并不完全,如果有错请联系我,谢谢^ _ ^),同时在学习的过程中加入个人的理解与对代码运行的思考。

下面是整个专栏的文章链接,用于快速的导航。

【网络编程】——Java实现(3)——Java Socket(All About Sockets)实践过程中出现的问题。

我自己按照官方给出的代码实现EchoServer,EchoClient代码时,使用的I/O类为BufferedReaderBufferedWriter

咱们先贴上两个类的代码实现,然后再分析我遇到的问题在哪里。

EchoServer 类的实现:

public class EchoServer {

    public static void main(String[] args) throws Exception {

        if(args.length != 1){         // 必须要有一个 端口 参数
            System.err.println("Usage: java EchoServer <port number>");
            System.exit(1);
        }

        int portNumber = Integer.parseInt(args[0]);

        try(
            // 服务端的socket
            ServerSocket serverSocket = new ServerSocket(portNumber);

            // 通过accept来获取socket
            Socket socket = serverSocket.accept();

            // 获得输入流,允许读
            BufferedReader br = new BufferedReader(
                    new InputStreamReader( socket.getInputStream() ) );

            // 获取输出流,允许写
            BufferedWriter bw = new BufferedWriter(
                    new OutputStreamWriter( socket.getOutputStream() ) );

        ){

            String line = null;
            while( ( line = br.readLine() ) != null ){

                System.out.println("Message from Client : " + line);
                // 发送数据返回给客户端:写操作
                bw.write( line );
            }

        } catch ( Exception e ){

            System.out.println("Exception caught when trying to listen on port "
                    + portNumber + " or listening for a connection");

            System.err.println( e.getMessage() );

        }
    }
}

服务端代码很清晰,BufferedReader brBufferedWriter bw分别负责读取客户端的请求发送返回信息给客户端。主要的通信逻辑如下:

String line = null;
// br接受客户端端的信息
while( ( line = br.readLine() ) != null ){
    // 将客户端的信息显示出来
    System.out.println("Message from Client : " + line);
    // 发送数据返回给客户端:写操作
    bw.write( line );
}

下面再看客户端的实现:

public class EchoClient {

    public static void main(String[] args) {

        if(args.length != 2){
            System.err.println("Usage: java EchoClient <hostname> <portnumber>");
            System.exit(1);
        }

        String hostname = args[0];
        int portNumber = Integer.parseInt( args[1] );

        try(

            Socket clientSocket = new Socket(hostname, portNumber);

            // socket的输入(读取)   *clientSocket.getInputStream()*
            BufferedReader socketReader = new BufferedReader(
                                               new InputStreamReader( clientSocket.getInputStream() ));

            // socket的输出(写入)   *clientSocket.getOutputStream()*
            BufferedWriter socketdWriter = new BufferedWriter(
                                               new OutputStreamWriter( clientSocket.getOutputStream() ) );

            // 用户的输入 *System.in*
            BufferedReader userInput = new BufferedReader(
                    new InputStreamReader(System.in)  );

        ){

            String input = null;
            while( (input = userInput.readLine() ) != null ){

                // 提示用户输入的信息发送了
                System.out.println("Send message: " + input);

                // 发送数据:socket写操作
                socketdWriter.write(input);

                // 取出数据:socket写操作,然后将数据显示出来
                System.out.println( "Message from server: " + socketReader.readLine() );
            }

        } catch (UnknownHostException e) {
            System.err.println("Don't know about host " + hostname);
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to " +
                    hostname);
            System.exit(1);
        }
    }
}

和服务端一样,客户端也使用BufferedReaderBufferedWriter来获取和发送数据。

下面讲解我遇到的问题:

运行服务器,客户端。两端连接成功,客户端开始发消息。
发现没有任何服务端的回应,之后的输入也没有任何反应了。
echolib-client
服务端的控制台,发现没任何反应
echolib-server

下面是分析:

在我们的例子中,我们的socket通信都是,BufferedReader通过readLine()方法来读取信息,BufferedWriter通过的write方法来写入(返回信息)

注意到readLine,查看JDK关于readLine的解释:

/**
* Reads a line of text. A line is considered to be terminated by any one
* of a line feed (’\n’), a carriage return (’\r’), or a carriage return
* followed immediately by a linefeed.
* 读取一行文本信息。当文本中包含"\n"或者"\r",亦或者"\n\r",那么我们就认为一行结束了。也就是说它通过特定的字符来判定一行是否结束了。

再看到我们的客户端程序:

String input = null;
while( (input = userInput.readLine() ) != null ){

    // 提示用户输入的信息发送了
    System.out.println("Send message: " + input);

    // 发送数据:socket写操作
    socketdWriter.write(input);

    // 取出数据:socket写操作,然后将数据显示出来
    System.out.println( "Message from server: " + socketReader.readLine() );
}

这里有个userInput.readLine(),它自然就会返回一行信息赋值给inpu。然而此时input中并不会存在换行符,所以当我们write此input信息到socket缓冲区的时候,并不会包含换行符。所以当我们服务端readLine时,socket缓冲区中全是客户端发来的信息,而找不到换行符。

此时的解决方案是,我们可以在文本串的后面添加一个"\n"
另外值得一提的是,BufferedWriter提供了一个newLine()方法专门处理此问题

于是可以改进服务端,客户端的代码如下:(注释的代码可选为可选方案)

客户端代码改进:

// 发送数据:socket写操作
//socketdWriter.write(input + "\n");
socketdWriter.newLine();

同理,服务端也相应的改进代码:

// 发送数据返回给客户端:写操作
//bw.write( line + "\n" );
bw.newLine();

此时我觉得代码应没问题了,于是运行,结果。。。。。仍然不能正常运行!!

下面继续分析原因。

BufferedWriter类内部实现为一段缓冲区,也就是说,当我们向socket缓冲区写入的数据,没有把缓冲区填满的时候,它是不会将数据发送出去的。毕竟是Buffer,缓冲作用嘛。

BufferedWriter有一个方法flush(),一般的翻译是(以水)冲刷,冲洗;冲掉,除掉。在程序中我们可以理解为,刷新(清除)缓冲区,将数据写出去

于是最终的客户端,服务端代码改进如下,终于可以运行了!!
服务端:

String line = null;
while( ( line = br.readLine() ) != null ){

    System.out.println("Message from Client : " + line);

    // 发送数据返回给客户端:写操作
    //bw.write( line + "\n" );
    bw.newLine();
    bw.flush();

}

客户端:

String input = null;
while( (input = userInput.readLine() ) != null ){

    // 提示用户发送了什么
    System.out.println("Send message: " + input);

    // 发送数据:socket写操作
    //socketdWriter.write(input + "\n");
    socketdWriter.newLine();
    //socketdWriter.flush();

    // 返回数据
    System.out.println( "Message from server: " + socketReader.readLine() );
}

再回头看看官方给出的例子实现是怎样的:
它给出的例子中使用的输出流的实现类为PrintWriter
并且它的初始化代码如下:

PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

特别注意到PrintWriter的第二个参数,它传入的是true

JDK给出PrintWiter的此类型构造函数的解释:

/**
     * Creates a new PrintWriter from an existing OutputStream.  This
     * convenience constructor creates the necessary intermediate
     * OutputStreamWriter, which will convert characters into bytes using the
     * default character encoding.
     *
     * @param  out        An output stream
     * @param  autoFlush  A boolean; if true, the <tt>println</tt>,
     *                    <tt>printf</tt>, or <tt>format</tt> methods will
     *                    flush the output buffer

第二个参数autoFlush: 如果为true,那么调用此PrintWriter对象的println,printf,format方法都会自动flush缓冲区

EchoServer,EchoClient类中确实也是这样用的,它每次写操作,都是调用println方法

问题总结

出现此问题,还是我对Java I/O操作没有完全熟悉。通过此例子,会对Buffer,readLine()等实现由一个基本的认识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值