Java实现Socket网络编程(五)

在看到本文之前,如果读者没看过笔者的前文 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网络编程,供读者学习思考。

谢谢支持!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,968评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • 1、TCP状态linux查看tcp的状态命令:1)、netstat -nat 查看TCP各个状态的数量2)、lso...
    北辰青阅读 9,423评论 0 11
  • 缘起 上节气课老师推荐的,以前看《金字塔原理》没注意有这本书。从图书馆借后,书要到期了,看掉还了。 内容 导论:何...
    im天行阅读 1,210评论 0 4
  • 湿画法:水分比例多,而颜料的比例少。利用水分使颜色充分融合。 作画时,画笔上的水分较多。趁前一笔还没干时,接入下一...
    鱼的微记忆阅读 1,466评论 1 6