千家信息网

Java如何使用Socket正确读取数据姿势

发表于:2025-11-16 作者:千家信息网编辑
千家信息网最后更新 2025年11月16日,小编给大家分享一下Java如何使用Socket正确读取数据姿势,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!前言平时日常开发用得最多是Http通讯,接口调试也比较简单的,也有比较强大
千家信息网最后更新 2025年11月16日Java如何使用Socket正确读取数据姿势

小编给大家分享一下Java如何使用Socket正确读取数据姿势,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

    前言

    平时日常开发用得最多是Http通讯,接口调试也比较简单的,也有比较强大的框架支持(OkHttp)。

    个人平时用到socket通讯的地方是Android与外设通讯,Android与ssl服务通讯,这种都是基于TCP/IP通讯,而且服务端和设备端协议都是不能修改的,只能按照相关报文格式进行通信。

    但使用socket通讯问题不少,一般有两个难点:

    1、socket通讯层要自己写及IO流不正确使用,遇到读取不到数据或者阻塞卡死现象或者数据读取不完整

    2、请求和响应报文格式多变(json,xml,其它),解析麻烦,如果是前面两种格式都简单,有对应框架处理,其它格式一般都需要自己手动处理。

    本次基于第1点问题做了总结,归根结底是使用read()或readLine()导致的问题

    Socket使用流程

    1、创建socket

    2、连接socket

    3、获取输入输出流

    字节流:

       InputStream  mInputStream = mSocket.getInputStream();   OutputStream  mOutputStream = mSocket.getOutputStream();

    字符流:

      BufferedReader mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "UTF-8"));  PrintWriter mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), "UTF-8")), true);

    至于实际使用字节流还是字符流,看实际情况使用。如果返回是字符串及读写与报文结束符(/r或/n或/r/n)有关,使用字符流读取,否则字节流。

    4、读写数据

    5、关闭socket

    如果是Socket短连接,上面五个步骤都要走一遍;

    如果是Socket长连接,只需关注第4点即可,第4点使用不慎就会遇到上面出现的问题。

    实际开发中,长连接使用居多,一次连接,进行多次收发数据。

    特别注意:使用长连接不能读完数据后立马关闭输入输出流,必须再最后不使用的时候关闭

    Socket数据读写

    当socket阻塞时,必须设置读取超时时间,防止调试时,socket读取数据长期挂起。

    mSocket.setSoTimeout(10* 1000);  //设置客户端读取服务器数据超时时间

    使用read()读取阻塞问题

    日常写法1:

     mOutputStream.write(bytes); mOutputStream.flush();byte[] buffer = new byte[1024];int n = 0;ByteArrayOutputStream output = new ByteArrayOutputStream();while (-1 != (n = mInputStream .read(buffer))) {    output.write(buffer, 0, n);}//处理数据  output.close();byte[] result = output.toByteArray();

    上面看似没有什么问题,但有时候会出现mInputStream .read(buffer)阻塞,导致while循环体里面不会执行

    日常写法2:

    mOutputStream.write(bytes);mOutputStream.flush();int  available = mInputStream.available();byte[] buffer = new byte[available];in.read(buffer);

    上面虽然不阻塞,但不一定能读取到数据,available 可能为0,由于是网络通讯,发送数据后不一定马上返回。

    或者对mInputStream.available()修改为:

     int available = 0;while (available == 0) {    available = mInputStream.available();}

    上面虽然能读取到数据,但数据不一定完整。

    而且,available方法返回估计的当前流可用长度,不是当前通讯流的总长度,而且是估计值;read方法读取流中数据到buffer中,但读取长度为1至buffer.length,若流结束或遇到异常则返回-1。

    最终写法(递归读取):

     /**     * 递归读取流     *     * @param output     * @param inStream     * @return     * @throws Exception     */    public void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws Exception {        long start = System.currentTimeMillis();        while (inStream.available() == 0) {            if ((System.currentTimeMillis() - start) > 20* 1000) {//超时退出                throw new SocketTimeoutException("超时读取");            }        }        byte[] buffer = new byte[2048];        int read = inStream.read(buffer);        output.write(buffer, 0, read);        SystemClock.sleep(100);//需要延时以下,不然还是有概率漏读        int a = inStream.available();//再判断一下,是否有可用字节数或者根据实际情况验证报文完整性        if (a > 0) {            LogUtils.w("========还有剩余:" + a + "个字节数据没读");            readStreamWithRecursion(output, inStream);        }    }    /**     * 读取字节     *     * @param inStream     * @return     * @throws Exception     */    private byte[] readStream(InputStream inStream) throws Exception {        ByteArrayOutputStream output = new ByteArrayOutputStream();        readStreamWithRecursion(output, inStream);        output.close();        int size = output.size();        LogUtils.i("本次读取字节总数:" + size);        return output.toByteArray();    }

    上面这种方法读取完成一次后,固定等待时间,等待完不一定有数据,若没有有数据,响应时间过长,会影响用户体验。我们可以再优化一下:

     /**     * 递归读取流     *     * @param output     * @param inStream     * @return     * @throws Exception     */    public void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws Exception {        long start = System.currentTimeMillis();        int time =500;//毫秒,间看实际情况        while (inStream.available() == 0) {            if ((System.currentTimeMillis() - start) >time) {//超时退出                throw new SocketTimeoutException("超时读取");            }        }        byte[] buffer = new byte[2048];        int read = inStream.read(buffer);        output.write(buffer, 0, read);       int wait = readWait();        long startWait = System.currentTimeMillis();        boolean checkExist = false;        while (System.currentTimeMillis() - startWait <= wait) {            int a = inStream.available();            if (a > 0) {                checkExist = true;                //            LogUtils.w("========还有剩余:" + a + "个字节数据没读");                break;            }        }        if (checkExist) {            if (!checkMessage(buffer, read)) {                readStreamWithRecursion(output, inStream, timeout);            }        }            }     /**     * 读取等待时间,单位毫秒     */    protected int readWait() {        return 100;    }        /**     * 读取字节     *     * @param inStream     * @return     * @throws Exception     */    private byte[] readStream(InputStream inStream) throws Exception {        ByteArrayOutputStream output = new ByteArrayOutputStream();        readStreamWithRecursion(output, inStream);        output.close();        int size = output.size();        LogUtils.i("本次读取字节总数:" + size);        return output.toByteArray();    }

    上面这种延迟率大幅降低,目前正在使用该方法读取,再也没有出现数据读取不完整和阻塞现象。不过这种,读取也要注意报文结束符问题,何时读取完毕问题。

    使用readreadLine()读取阻塞问题

    日常写法:

     mPrintWriter.print(sendData+ "\r\n");    mPrintWriter.flush(); String msg = mBufferedReader.readLine(); //处理数据

    细心的你发现,发送数据时添加了结束符,如果不加结束符,导致readLine()阻塞,读不到任何数据,最终抛出SocketTimeoutException异常

    特别注意:

    报文结束符:根据实际服务器规定的来添加,必要时问后端开发人员或者看接口文档是否有说明

    不然在接口调试上会浪费很多宝贵的时间,影响后期功能开发。

    使用readLine()注意事项:

    • 1、读入的数据要注意有/r或/n或/r/n

    这句话意思是服务端写完数据后,会打印报文结束符/r或/n或/r/n;

    同理,客户端写数据时也要打印报文结束符,这样服务端才能读取到数据。

    • 2、没有数据时会阻塞,在数据流异常或断开时才会返回null

    • 3、使用socket之类的数据流时,要避免使用readLine(),以免为了等待一个换行/回车符而一直阻塞

    上面长连接是发送一次数据和读一次数据,保证了当次通讯的完整性,必须要时需要同步处理。

    也有长连接,客户端开线程循环阻塞等待服务端数据发送数据过来,比如:消息推送。平时使用长连接都是分别使用不同的命令发送数据且接收数据,来完成不同的任务。

    看完了这篇文章,相信你对"Java如何使用Socket正确读取数据姿势"有了一定的了解,如果想了解更多相关知识,欢迎关注行业资讯频道,感谢各位的阅读!

    数据 阻塞 通讯 问题 报文 字节 结束符 服务 实际 时间 处理 写法 字符 方法 格式 开发 客户 客户端 情况 接口 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 做苹果数据库需要什么 投影仪中心服务器联机失败 软件开发经营合同 江苏省网络安全竞赛决赛 哪些公司会把软件开发外包出去 计算机网络技术有哪些职业兴趣 网络安全警察查黑客 c 显示sql数据库数据 数据库如何计算金额 数据库月报总结 三菱服务器风扇一定要转吗 预算项目表格开发软件开发费用 企业网络安全培训会议纪要 数控软件开发上市公司 软件开发面试什么意思 图片如何存到数据库中 预约时间表数据库设计 陕西金泰网络技术公司 不懂编程语言怎么建立数据库 简单说明客户端服务器模型 战地5如何找到外挂少的服务器 上海浪潮服务器哪个厂家质量好 汉王文本王软件开发 美食商城数据库设计 抖音服务器更新怎么回事 软件开发工具分成哪几大类 深泽应用软件开发服务技术规范 惠州支付软件开发设计 数据库设计开源项目 服务器硬盘怎么安全
    0