从TcpSocket上读取数据的三种方式

我在一个项目中碰到了一个TcpSocket的应用。在java程序中使用TcpSocket同本机的一个服务进行进程间的通信。

由于通信路径只是单机并没有经过网络,因此两个进程之间的互通相对与网络传输是比较快速的。因此,进程间的交互使用了如下方式:

(见上传图片)

让我们看一下代码实现:


public synchronized void send(byte[] bytes) throws IOException
{
if (bytes != null && bytes.length > 0)
{
this.bos.write(bytes);
this.bos.flush();
}
}

/**
* 尝试读取一次,速度最快,但返回的信息可能不全
*/
public synchronized byte[] receiveOnce() throws IOException
{
byte[] reciveBytes = new byte[0];
int len = this.bis.read(this.b_buf, 0, this.bufferSize);

return ArrayUtils.addAll(reciveBytes, ArrayUtils.subarray(this.b_buf, 0, len));
}


/**
* 用于发送并接收数据,在返回数据比较少的情况下使用
*/
public byte[] sendAndReceiveOnce(byte[] bytes) throws IOException
{
this.send(bytes);
return this.receiveOnce();
}


我们通过调用sendAndReceiveOnce()来接收并返回数据。

这样实现会导致什么问题呢?

1. 最明显的就是发送数据,和接收数据在同一个线程里边,如果运行在主线程,那么主线程将会在执行receiveOnce()的时候停滞,除非接收到数据或者触发超时异常。但之前已经说明了,应用本函数的环境是单机的进程间的通信,因此接收到数据的时间实际上就取决于Server处理并给予响应时间的长短,不存在网络传输的滞留时间。所以,如果在Server响应快速的情况下,客户端Socket几乎感觉不到延迟太多。

2. 再一个明显问题就是,receiveOnce()根据其函数名称就可以得知,它只能读取一次,如果返回信息的长度,大于其缓冲区的长度,那它只能得到部分数据了。

综合分析以上两种情况,我们先解决问题2吧,看看如何读取完整的响应信息?

下面是我的实现方法:

   
/**
* 从流的开头开始读取,读取全部的输入数据并返回 如果执行read的时候流中没有数据则阻塞读取直到超时
* 最少经过一次超时,因此速度比较慢,但读的时候流中没有数据可以等到超时 ,因此获取数据比较准确
*
* @return
* @throws 除了SocketTimeoutException的一切IOException
*/
public synchronized byte[] blockReceive() throws IOException
{
byte[] reciveBytes = new byte[0];
// 偏移量
int offset = 0;
// 每次读取的字节数
int len = 0;
while(len != -1)
{
try
{
len = this.in.read(this.buffer, 0, this.bufferSize);
}
catch(SocketTimeoutException e)
{
break;
}

if(len != -1)
{
reciveBytes = ArrayUtils.addAll(reciveBytes, ArrayUtils
.subarray(this.buffer, 0, len));
offset = offset + len;
}
}

return reciveBytes;
}


这个方法就如同它的注释所说的那样,它总是尝试去仅可能多的读取信息,但是在触发了一次超时之后将会返回读取到的字节。

有人提问:那判断流是否已经读完不是看读到最后会返回-1么?
我的回答是:返回-1取决于处理的流的源头是什么,如果是文件流,这种想法或许是对的,因为文件流的大小是固定的,持续的读,总会读到文件的末尾(EOF),它总会返回-1的。

就像下面的文件流在读取文件的实现一样,我们这样读取文件流:
    
protected byte[] receiveBytes() throws IOException
{
byte[] reciveBytes = new byte[0];
// 偏移量
int offset = 0;
// 每次读取的字节数
int len = 0;
while(len != -1)
{
this.buffer = new byte[bufferSize];
len = this.in.read(this.buffer, 0, this.bufferSize);

if(len != -1)
{
reciveBytes = ArrayUtils.addAll(reciveBytes, this.buffer);
offset = offset + len;
}
}
return ArrayUtils.subarray(reciveBytes, 0, offset);
}


但是,如果是网络流,例如TcpSocket,这样的流是没有末尾(EOF)的,如果你想读到-1,或许在远端被关闭了,而你还在读取,还是有可能读到-1的。实际情况是:网络连接状况很复杂,很有可能远端没有正常关闭而是进程死掉了,而是连接的线路断掉了,或者任何一个原因导致连接的通路无法正常传输数据。[b]由于在这种情况下,java中BufferedInputStream的read(byte[] b, int off, int len)函数(其它流对象也一样)总是尝试读取更多的数据,如果没有设置超时,就会一直堵塞在那里,像死掉了一样。而不是像你所期待的那样,返回-1。[/b]因此,我们才才用让它最少经过一次超时,来尝试读取更多的数据。当然,这也是仅仅在网络状况足够好的情况下,或者超时对于响应结果不会影响太多的情况下的解决方法。

[i](加一个小插曲:前段时间本人曾经在电话里面被一个面试我的作开发的兄弟考到“怎么获取tcpScoket远端关闭”,我就是阐述了因为以上观点“检测远端是否关闭的最好方法就是向远端发送数据,看是否发生IO异常”,而那位仁兄一直坚持远端关闭得到-1是对的,我说不对就反问:如果不是关闭,而是把网线切断或者网络不通也能得到-1么?仁兄语塞,面试后来以尴尬结束。后来反思自己当时实在太轻狂了,没给仁兄面子。去不去应聘倒无所谓,但是态度还是不是面试时应该有的态度啊。现在想向那位仁兄道歉,也没机会了)[/i]

那现在又有一个问题了,虽然与远端的交互出现无法读取到数据的时候不会一直堵塞在那里,像死掉了一样。但是我在使用blockReceive()的时候[b]总是需要等一个超时的时间[/b]才能返回!

那我如果设超时为5秒,操作完了之后,也至少等到5秒才能达到消息的反馈。哦,天哪,慢到要死了!!!!
这个问题必须解决,那么:
让我们用更聪明的实现方法吧,我们不要阻塞了!!!


/**
* 如果流中有数据,则从流的开头开始读取,读取全部的输入数据并返回,否则马上返回空 尝试获取当前流中的最大数据量,
* 由于使用了非阻塞接收,需要保证在执行本函数的时候流中恰好有数据, 在执行此函数之前必须给后台足够的响应时间
*
* @return
* @throws 除了SocketTimeoutException的一切IOException
*/
public synchronized byte[] unblocReceive() throws IOException
{
byte[] reciveBytes = new byte[0];
// 当前流中的最大可读数
int contentLength = this.in.available();
// 偏移量
int offset = 0;
// 每次读取的字节数
int len = 0;

while(contentLength > 0 && offset < contentLength && len != -1)
{
try
{
len = this.in.read(this.buffer, 0, this.bufferSize);
}
catch(SocketTimeoutException e)
{
break;
}

if(len != -1)
{
reciveBytes = ArrayUtils.addAll(reciveBytes, ArrayUtils
.subarray(this.buffer, 0, len));
offset = offset + len;
}
}

return reciveBytes;
}


我们发现:这个方法真是不错!我们每次在读取数据之前,总是先用available()方法获取流中当前的最大可读字节数,然后再读。否则,我直接返回。但是为了以防我在读取数据的时候也出现超时问题导致堵塞,我还是小心的加入了超时的处理,虽然它在绝大部分情况下并不会发生。

好了!现在我们满怀希望的来调用所谓的“完美”解决方案:

public byte[] sendAndUnblockReceive(byte[] bytes) throws IOException
{
this.send(bytes);
return this.unblocReceive();
}


然后,测试,却发现了一个奇怪的现象:

我们在程序里面[b]连续调用了两次sendAndUnblockReceive(),并期待每次发送都会迅速并完整准确的接收它们每次的响应。[/b]但是,没有效果。事实是:[b]我第一次发送的请求,并没有接收到它需要的正确响应。而我们第二次发送的请求,却接收到了第一次的响应,还要第二次的响应,这样两条数据!!![/b]

这是为什么呢?因为:

[b]
我们第一次在发送完数据之后,马上就调用了unblocReceive()。但是由于这次我们调用的实在太快了,Server那一端没来的及处理,甚至没来的及接收,更不用说响应了。因此unblocReceive()里面我们用available()方法获取流中当前的最大可读字节数为0!!!因此,当然就不会读取了!!!而第二次再发送时,第一次的响应刚刚到达,因此,unblocReceive()再被第二次调用的时候“尽最大可能”的读取到了这两次的响应信息。
[/b]

唉,看来就没有更好的方法了么?或许,还是有的吧!!!

我们先这样改一下:


public byte[] sendAndUnblockReceive(byte[] bytes) throws IOException
{
this.send(bytes);
// 由于使用了非阻塞接收,为保证在执行read的时候流中恰好有数据,
// 必须给后台足够的响应时间
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
logger.error("InterruptedException error.", e);
}
return this.unblocReceive();
}


强制的让线程在发送完后sleep一端时间,半秒钟,给Server足够的响应时间,然后再去读取,或许,这样比那个blockReceive()的实现要好一点吧。



最后来一下总结:

我们在这个TcpScoket中,在发送和读取使用同一线程的情况下,使用了三种读取方式:

[b]一次读取,阻塞式完整读取,非阻塞式完整读取[/b]。

这三种读取方式的优缺点分析如下:

[b]一次读取receiveOnce()[/b]: 是最快速,并且在缓冲区足够大的情况下能够完整读取的方法,当然如果没有设置超时,它仍然用可能存在阻塞。

[b]阻塞式完整读取blockReceive()[/b]:在返回数据之前总是至少经过一次超时以读取更多数据,因此在网络状况足够好的情况下,速度仍然比较慢。

[b]非阻塞式完整读取unblocReceive()[/b]: 在尝试读取数据之前,首先判断可以读取的最大字节数,如果有数据则尝试去读,否则直接返回。所有是这一种不用考虑缓冲区大小,还能兼顾速度的方法。但是如果远端响应慢的情况下,依然会错过读取数据。

综合上述三中读取方式:我们可以在确定返回数据量较少,而又要求速度快而准确的情况下,使用receiveOnce()。在返回数据量较多,而又要求速度快而准确的情况下,使用unblocReceive(),不过需要留给远端足够的响应时间。在不需要响应速度很快,而需要返回大量数据,而且准确的情况下使用blockReceive()。

现在我们抛开这些,想一想:

我们真的需要这样三种读取方式吗?需要吗?

我们为什么这么罗里罗嗦使用这三种方式?

因为,[size=x-large][b]我们把发送数据,和接收数据这两个功能放在一个线程里执行了!!![/b][/size]

这才是最主要的问题!

因此,:

[size=x-large][b]尽量不要在使用Socket的流的时候,把发送数据和接收数据的调用放在一个线程里。[/b][/size]

因为,网络上的流是不稳定的,因此java在设计流的时候也是尽量去读取尽可能多的数据,很可能发生堵塞。如果放在一个线程里面,试图我发送了就会想当然的又快又准确的接收到,就会像上面的解决方案一样,用尽招数,仍然束手无策。
QtQuick QML教程(2):qml语法介绍 ​ qml一个普通的文本文件,后缀是.qml, 一个qml文件主要由两部分组成:(1) import语句 :这个就像C++中的头文件, 使用 import 导入qml需要的相关模块,可以查询Qt文档进行导入,注意每个版本的Qt模块声明可能都不同,相比于QWidget项目,QtQuick的变动是很大的;(2) QML对象树 :根对象是Window, 然后可以在Window中添加各种控件,例如按钮,文本,菜单等。 阅读详情

相关推荐

5张图,搞懂三方支付机构的“备付金账户”体系

本文将详细解析第三方支付机构各类备付金账户的功能定位、所承载的支付业务处理流程以及日常管理。

银行信息系统应用架构详解 2146

TCP通信的代码实现

TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据 表示客户端的类 java.net.Socket:此类实现客户端套接字。套接字是两台机器通信的端点。 套接字:包含了IP地址和端口号的网络单位。 构造方法 Socket(String host,int port)创建一个套接字并将其连接到指定主机上的制定端口号; 参数: host:服务器主机的名称/服务器的IP地址 port:服务器的端口号 成员方法 OutputStream.getOutputStream();返回套接字的输出

温泉 1236

tkinter使用label视频播放

tkinter是python的界面开发包 使用tkinter显示视频流的一个办法是使用label来逐帧显示图片。 from Tkinter import * from PIL import ImageTk, Image import cv2 root = Tk() # Create a frame app = Frame(root, bg="white") app.grid() # Create a label in the frame lmian = Label(app) lmain.grid(

六十英里的专栏 5527

TCP协议流程详解,抓包分析

TCP协议详解,TCP三次握手,四次挥手,TCP抓包,TCP协议头部解析,数据链路层,IP层,传输层抓包。

weixin_40355471的博客 5832

微动探测原理及仪器介绍

微动探测原理及仪器介绍 1、微动探测原理 1.1 基本理论 微动是由体波和面波组成,其中微动能量的百分之七十以上都来自于面波,并且它与体波的主要区别是在不均匀的介质中传播时面波会发生频散,而体波不存在频散现象,因此面波是微动探测的主要对象。 对于介质的弹性参数(如横波速度、层薄厚、压缩波速度、密度等),面波传播速度相对比较敏感,特别是对横波的敏感性很高,因此可以利用面波来反演横波速度。 在时间和空间上,微动信号具有以下特性:高度变化、无规律性、无重复性等,有研究发现全世界各处记录的微动信号都有相似

zw461450141的博客 1万+

SWAT模型参数太多搞不定?一篇讲清如何用SWAT-CUP筛选关键敏感参数(含结果图解读)

本文详细解析了如何使用SWAT-CUP进行参数敏感性分析,帮助水文模型初学者从上百个参数中筛选出关键敏感参数。通过实际案例和结果图解读,介绍了敏感性分析的核心价值、SWAT-CUP结果界面解读方法以及参数筛选的四步实战法,显著提升模型调试效率。文章还提供了典型问题排查手册,解决敏感参数与文献不一致、率定后模型变差等常见问题。

weixin_30385925的博客 321

Java实现使用jlibmodbus读取Modbus TCP数据

本文使用jlibmodbus获取modbus tcp数据,下文展示代码以及遇到的问题。

weixin_38863607的博客 5121

SAC-IA算法点云粗配准

SAC-IA(Sample Consensus Initial Alignment)是PCL库中的一种初始点云配准算法,它使用特征匹配和RANSAC(随机抽样一致性)框架来估计两个点云之间的初始刚性变换。其目标是通过特征描述符找到一组可靠的匹配点对,并使用这些点对估计初始对齐的变换矩阵。

twnkie的博客 1445

Java利用Socket进行数据读写

Java中和网络有关的类分为四种:InetAddress(网络信息标识)、URL(统一资源定位器,读写网络数据)、Sockets(利用TCP/IP实现网络通信)、Datagram(UDP数据报通信) 通过InetAddress可以获取计算机名、IP地址等信息,也可以通过计算机名、IP地址获取到网络上的计算机实例 public static void main(String[] arg...

theVicTory的博客 8388

【EB_Tresos专题】-06-配置一个UDS诊断DID

<--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> <--返回「个人博客」首页--> <--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> <--返回「个人博客」首页--> 结尾 ...

「汽车电子助手」的博客 4087

Python爬虫实战:中国土地市场网招拍挂数据抓取与分析

本文详细介绍了如何使用Python爬虫技术从中国土地市场网抓取土地招拍挂数据,并进行初步的数据分析与可视化。文章涵盖了爬虫的基本原理、反爬虫策略应对、数据清洗与存储、数据分析与可视化等多个方面,为土地市场研究者提供了一套完整的数据获取与分析解决方案。

2201_76125261的博客 684

socket 获取回传信息_从TcpSocket上读取数据的三种方式

由于通信路径只是单机并没有经过网络,因此两个进程之间的互通相对与网络传输是比较快速的。因此,进程间的交互使用了如下方式:(见上传图片)让我们看一下代码实现:Java代码public synchronized voidsend(byte[] bytes)throws IOException{if(bytes != null&& bytes.length> 0){this.bos...

weixin_29039773的博客 1406

stable diffusioncontrolNet 预处理器/模型的下载及安装使用教程

首先说明,controlNet 的模型 不同于stable diffusion 模型,这一点一定要区分清楚。下面提供多个ControlNet 预处理器+ 专用模型的百度网盘下载链接,都是由群友整理贡献,但各自包含的版本不同,大家可自行选择。推荐下载紫色链接内的模型,fp16版文件更小,稍微新一些的controlnet插件还添加了对control-lora的支持(注意,要求controlnet插件的版本比12月16号新(1.1.423以上)才能正常识别),推荐下载;

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 3388

52.TcpSocket

看网络第二个课件的补充参考部分 改一下Inetaddr.hpp 主机转网络封装好了之后,sockaddr_in就不需要初始化各种值,肢解创建InetAddr,直接就初始化好了。线程池内部处理任务时候已经解锁了,所以函数中成员集不属于临界资源,多线程访问会出现问题。可以把route新增,转发,删除在udpserver拆开,这样的话锁的力度就变细了。继承之后如果想拷贝,必须拷贝父类,父类禁止拷贝所以就禁止拷贝。lesson40 聊天室改成多线程。windows linux能联动。

qq_55640460的博客 73

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Qt【TCPSocket】一个简单的TCPSocket的demo

哈哈哈,在小女子的一番折腾下,终于把TCPSocket客户端与服务端的小demo整出来了!!表示很鸡冻!!如下是总结,走过路过不要错过~~(注:鄙人用的是Qt Quick,不是Qt GUI,Qt版本为Qt5.7)First,来看看客户端的实现。1.客户端创建TCP连接的步骤new一个QTcpSocket对象出来准备好端口号及IP地址之后,调用connectToHost创建连接启用waitForCo...

dumuweiyang的博客 1421

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

谈谈TCP Socket中读取数据的函数---read、recv、readv

read函数和recv函数都是阻塞调用,即在没有数据可读时会一直阻塞等待,它们的主要区别在于recv函数可以通过flags参数控制一些特殊的行为,如设置MSG_PEEK标志来预览数据而不将其从缓冲区中移除。而readv函数可以一次读取多个缓冲区中的数据,并在内核中减少了多次系统调用的开销。

m0_73537205的博客 2485

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

单机轻松支持百万并发的go协程的简单tcpsocket服务端客户端通信小程序示例源码

单机轻松支持百万并发的go协程的简单tcpsocket服务端客户端通信小程序示例源码。

Tekin 是深耕技术 20 年的全栈实战派专家,精通 Go/Python/Java 等多语言开发。博客专注技术原理与实战结合,深度解析 Python 高阶编程、Go 语言架构、数据库优化等硬核内容。涵盖并发编程、机器学习、云原生等前沿领域,通过真实案例拆 516

【Spark分布式内存计算框架——Structured Streaming】3. Structured Streaming —— 入门案例:WordCount

其中可以设置不同输出模式(OutputMode),当设置为Complete时,结果表ResultTable中所有数据都输出;第二点、数据封装Dataset/DataFrame中,分析数据时,建议使用DSL编程,调用API,很少使用SQL方式。词频统计案例:从TCP Socket实消费流式数据,进行词频统计,将结果打印在控制台Console。参数二:truncate,如果某列值字符串太长是否截取,默认为true,截取字符串;参数一:numRows,打印多少条数据,默认为20条;

优快云GuoYuying的博客 920

网络编程-TcpSocket(文件)

客户端 //客户端 public class Client { public static void main(String[] args) throws Exception { //创建连接 Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9999); //创建io流 OutputStream os = socket.getOutputStream();

weixin_46094777的博客 123

QT tcpsocket 发送/接收数据

发送数据与接收数据中文乱码处理由于有个项目需求是要求文件传输,也要求普通数据传输,而只用一个端口的情况下,如何分开对文件的处理与对数据的处理就非常重要,用官方提供的例子只有单传输文件或者单传输数据,并且传输数据对中文的支持也不是很好,所以针对项目需要,设计了一个方案,这里只列出了单发送数据模块的核心代码,很好地处理了中文传输的问题,后期整理后会慢慢补充其他模块的代码。 传输数据的结构: 待发送的数

cammyn的博客 3万+

【Linux网络系列】:网络+网络编程(UDPsocket+TCPsocket)

想要掌握网络原理以及网络编程?看这一篇文章就够了!万字详解(本文篇幅较长,知识密度极大,建议读者收藏,慢慢消化)

努力努力再努力wz的博客 3124

Windows--TCPSocket打开图片传输图片

服务器端#include <stdio.h> #include <winsock2.h> #include <winsock2.h> #define SERVER_PORT 9210 //侦听端口 #pragma comment(lib,"ws2_32.lib") struct Data //数据包 { int length; char receivemessa

似水流年 1715

NodeJS实现TCPSocket(套接字)服务器和客户端

NodeJS实现TCPSocket(套接字)服务器和客户端TCP Socket(套接字)客户端清单TCP Socket(套接字)服务器清单结果服务器客户端 TCP Socket(套接字)客户端 ①通过调用net.connect()创建套接字客户端 ②在回调函数里建立连接行为,你可以添加超时时间或编码 ③需要添加处理程序来处理data,end、error、timeout、close事件 ④把数据写到...

钱小昊的博客 1万+

高级UIKit-08(TCPSocket)

【day1001_MoviePlayer】:视频播放 需要导入MediaPlayer.framework - (void)viewDidLoad { [super viewDidLoad]; NSURL *url = [NSURL fileURLWithPath:@"/Users/tarena/yz/李明杰/OC视频/ARC/28-ARC04-循...

a1250156的博客 119

网络+网络编程(UDPsocket+TCPsocket)

假设用户A向用户B发送一条消息,其背后的过程是:用户A设备上的微信进程生成数据,经过网络传输,最终送达用户B设备上的微信进程。而这里的数据,除了携带数据本身,还会携带接收者的身份标识信息,每个设备在接收到电信号后,会先将其转换回二进制数据,然后检查数据中携带的目标标识信息。若两台计算机通过一根 双绞线 直接相连,该 双绞线 属于共享介质,但与同轴电缆不同之处在于,数据的发送和接收分别在两对不同的导线中进行,因此不会因同时发送而产生数据冲突。因此,发送方实际发出的数据,除了数据本身,还包含这些额外的信息。

2601_94898001的博客 674

Structured Streaming Sources

从TCP Socket读取数据进行wordcount,将结果输出到控制台。

jx的博客 315
上一篇: Java中三种变量的拷贝方法比较: 值变量,对象变量,字符串变量
下一篇: 学习设计模式之State
Ivar_PredixCN
博客等级 码龄22年 12粉丝 62原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值