美文网首页旅行派JAVA socketJava学习笔记
Java实现Socket网络编程(五)

Java实现Socket网络编程(五)

作者: Louis_陆 | 来源:发表于2016-04-09 15:52 被阅读771次

    在看到本文之前,如果读者没看过笔者的前文 Java实现Socket网络编程(四),请先翻阅。

    接下来,笔者对几个核心点进行剖析:
    1、如果读者是初次进行Socket网络编程开发,起初可能会因为端口的使用不当,而导致Socket无法连接。所以读者可以在编程前进行测试,这里笔者提供了一个方法:

        /**
         * 用于检测能连接到的端口号
         */
        public static void scan(String host) {
            Socket socket = null;
    
            for (int port = 1024; port < 10055; port++) {
                try {
                    socket = new Socket(host, port);
                    System.out.println(socket);
                } catch (IOException e) {
                    continue;
                } finally {
                    try {
                        if (socket != null) {
                            socket.close();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    2、当发现程序只能在本机运行,而在其他机器上不能运行时,是因为读者所使用的ip地址是本机ip地址,如果采用 loop back 地址 "127.0.0.1",则可以在任意机器上运行。(本案例实际上是本机服务器与本机客户端的Socket通信)

    3、笔者在测试过程中,发现了这样一个现象:服务器检测客户端断开,零延时;而客户端检测服务器断开,有明显延时(延时长短和运行机器的速度有关)。

    这是什么原因导致的呢?经测试发现,服务器检测客户端断开,心跳包异常的捕获速度快于读写错误的捕获,从而零延时。而客户端检测服务器断开,读写错误的捕获速度快于心跳包异常的捕获速度,导致有延时。

    这种现象的产生,是由于Java底层对”服务器监听客户端断开“及”客户端监听服务器断开“的实现机制不一样,导致了两者之间的速度差异。

    既然是实现机制的原因,那我们是否就没有解决的方法呢?显然不屈不饶的程序员不会就此善罢甘休。

    服务器检测客户端断开,零延时,我们不需要再过多干预;我们要干预客户端检测服务器断开,以使得比捕获读写异常的速度更快地发现服务器断开。

    笔者经过多次尝试:
    ①首先是想到自定义一个结束符,例如"bye",希望通过服务器传递”bye“给客户端,客户端就”意识到“服务器关闭。这可以做到,但是存在一个漏动,如果”bye“是由使用者在服务器对话框发送过去的呢?那样客户端就会“以为”服务器断开。

    既然这样,笔者就想着改进,把结束符“bye”改成转义字符,如"\\u0025",让用户不容易输入,经过数次测试发现,用户毫无一例会成功输入"\\u0025"结束符,看起来毫无Bug,但这显然还是一个漏动,哪怕只有万分之一的可能?

    ②为了进一步改进,笔者想着自定义一种数据协议格式:
    【服务器是否启动标志位】【要接收的数据】

    这看起来不错,但前面已经提到,客服端读取数据要在for循环里,如果用String类的startsWith("启动标志位")方法判断,则客户端显示数据由于无法判断服务器一次性发送内容的结束,会导致如下输出结果:

    s
    sa
    say
    say h
    say he
    say hel
    say hell
    say hello
    say hello!
    

    ③既然如此,也就是要为数据提供结束符,以判断数据长度,笔者再一次修改数据协议格式:【要接收的数据】【服务器是否启动标志位】

    通过String类的endsWith("启动标志位")进行检测

    然而,这又产生了一个新问题,当用户直接输入”服务器断开标志位“,客户端又再一次”认为“服务器已断开。

    ④笔者进一步考虑,如果加上长度判断,当输入内容s.length()>"标志为长度"且endsWith("启动标志位")进行检测。

    心想理应成功,没料到又出现这样的一种情况:当用户输入任意长度字符+”服务器断开标志位“时,客户端又再一次”认为“服务器已断开。

    ⑤历经多次挫折,笔者最后对比使用C#的Socket网络编程实现,研究了其 Send()方法和 Receive()方法(Send方法用于发送数据,Receive方法用于接收数据,C#封装了底层输入输出流的实现,而Java没有,所以需要进行输入输出流的操作)

    总结出一种方法:模拟C#的Receive函数(该函数具有检测服务器是否断开,且能返回接收数据长度)

    笔者定义了以下的数据协议格式,彻底解决了”客户端延时发现服务器断开“、”发送数据无边界“的问题:【服务器是否启动标志位】【接收数据的长度】【要接收的数据】

    在发送数据前进行包装

       String message = Common.OK; // 代表服务器正常连接
       String t = "server " + Common.IP + ":" + Common.PORT + " "
                            + jtaSendMessage.getText();
       /**
        *  封装发送数据的长度
        */
       String c = "" + t.length();
       if (c.length() < 2) {
           c = "000" + c;
       } else if (c.length() < 3) {
           c = "00" + c;
       } else if (c.length() < 4) {
           c = "0" + c;
       }
    
       message += c + t;
    

    在收数据时进行解封

       // 使用"GBK"编码读取中文
       brIn = new BufferedReader(new InputStreamReader(
                            mSocket.getInputStream(), "GBK"));
    
       String s = "";// 记录每次读取的内容
       int count = -10;// 记录每次读取内容的长度
       // 接收内容并把内容添加到信息接收区
       for (int c = brIn.read(); c != -1; c = brIn.read()) {
            s += (char) c + "";
            count++;// 读取的长度
            // 如果服务器连接且一次数据接收完成
            if (s.startsWith(Common.OK) && s.length() > 10
                    && count == Integer.parseInt((s.substring(6, 10)))) {
                            ClientMain.jtaReceivedMessage.append(s.substring(10)
                                    + "\n");
                            count = -10;
                            s = "";
            }// 服务器断开且一次数据接收完成
            else if (s.startsWith(Common.ERROR) && s.length() > 10
                    && count == Integer.parseInt((s.substring(6, 10)))) {
                            ClientMain.jtaReceivedMessage.append(s.substring(10)
                                    + "\n");
                            count = -10;
                            s = "";
                            ClientMain.jlConnect.setText("Out Of Connect.");
            }
            // 滚动到底端
            ClientMain.jtaReceivedMessage
                                .setCaretPosition(ClientMain.jtaReceivedMessage
                                        .getText().length());
        }
    

    以上为本次案例的全部内容,最后,笔者在github上给出了这个案例的完整源码和可运行文件Socket网络编程,供读者学习思考。

    谢谢支持!

    相关文章

      网友评论

        本文标题:Java实现Socket网络编程(五)

        本文链接:https://www.haomeiwen.com/subject/cmfblttx.html