从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在设计流的时候也是尽量去读取尽可能多的数据,很可能发生堵塞。如果放在一个线程里面,试图我发送了就会想当然的又快又准确的接收到,就会像上面的解决方案一样,用尽招数,仍然束手无策。
高速SerDes均衡技术解析 均衡技术是高速SerDes链路中补偿信道损耗和失真的核心技术,通过FFE、CTLE和DFE三种主要方法对抗码间干扰(ISI)。FFE在发射端预补偿前光标干扰,CTLE通过提升高频增益补偿信道损耗,DFE则精准消除后光标干扰。三种技术各具优势:FFE实现简单且不放大噪声,CTLE功耗低易于集成,DFE能自适应调整且保持信噪比。这些技术协同工作可有效提升系统传输速率和可靠性。 阅读详情

相关推荐

专栏丨华为HN8145XR光猫获取超级管理员密码

摘要:本文详细介绍了华为HN8145xr光猫获取超级管理员权限的完整步骤。首先通过ONT使能工具补全shell,然后利用Telnet登录光猫后台进行操作。文章提供了多种获取超级密码的方法,包括配置文件解密、一键查询工具等,并针对联通光猫特别说明了默认密码格式(CUAdmin+后八位数字)。此外,还介绍了恢复出厂设置、修改SSID等实用操作,帮助用户完全掌控光猫设备。文末附有详细的命令行操作指南,适合有一定技术基础的用户参考。

8278

TCP通信的代码实现

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

温泉 1236

PTrade数据交互全流程详解:从本地文件上传到定时导出交易数据

本文详细解析了PTrade量化交易平台的数据交互全流程,涵盖从本地文件自动上传交易指令到定时导出成交数据的完整链路。通过文件媒介实现本地策略与云端实盘的安全、稳定对接,并提供了构建自动化、稳健数据流水线的实战配置方案与代码示例,助力量化工作流平滑过渡至实盘。

gg901的博客 882

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

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

weixin_40355471的博客 5832

闲置服务器之代理服务器篇

这台服务器是在阿里云免费白嫖的,天工开物计划,阿里给的300块无门槛优惠券,买了一台新加坡的境外服务器,288,刚刚抵扣完,因为是境外服务器,能够访问一些国内不能访问的网站,所以本文章仅从技术的角度来搭建代理服务器,使用时,请自觉遵守国家相关规定,如有任何违法行为,与本人无任何关系!!!。本文使用tinyproxy来搭建代理服务器。

m0_50281408的博客 2104

CAPL自动化测试

通过本文的学习,读者应该对CAPL自动化测试有了初步的了解,并能够编写复杂的CAPL脚本。在实际项目中,CAPL的应用非常广泛,希望读者能够通过不断的学习和实践,掌握更多的CAPL技巧,提高自己的开发能力。

闲人编程的博客 1528

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

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

weixin_38863607的博客 5121

pb插入ole控件点击insert+control时自动关闭解决办法

也就是说pb搜索CLSID的子项保存在OLE Object Bitmaps中,当搜索CLSID中的DLL或OCX控件和PB有冲突时,CLSID后面的DLL、OCX控件就不会保存在OLE Object Bitmaps中,也就是说OLE Object Bitmaps没有完全加载所有控件,有的控件冲突导致中断使PB报错,找出冲突的控件是关键。进行对出问题的控件反注册,如果提示反注册成功,打开PB的OLE控件看是否报错,如果还报错返回步骤(三)循环查找。,再进入注册表看到的是最正确的。来注册会莫名的冲突,但是用。

悠闲饭团的博客 710

Java利用Socket进行数据读写

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

theVicTory的博客 8388

设备接入与APP(应用程序)接入华为云iotDA平台的详细操作步骤及获取方式

设备及APP(应用程序) 登录 IoTDA 平台所需的 入场与操作清单内容,提供逐项详细操作步骤和获取方式,并穿插贴士便于理解与实际操作落地。内容基于 华为云 IoTDA 控制台 和 IAM 控制台 的实际使用路径整理。

hy行者勇哥的博客 3059

ArcGIS Pro 3.4 二次开发 - 知识图谱

本文介绍了在ArcGIS Pro SDK 3.4和.NET 8环境下操作知识图谱的方法。主要包含两大功能模块:1) 知识图谱数据存储操作,包括建立连接、获取图层、检索要素类与表定义、ID转换等;2) 知识图谱数据模型管理,如获取数据模型属性、标识符信息等。代码示例详细展示了如何使用C#通过QueuedTask异步任务与知识图谱服务交互,包括异常处理、数据转换等常见操作场景。所有示例均采用using语句确保资源释放,并遵循ArcGIS Pro SDK的最佳实践。

Winemonk 763

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

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

weixin_29039773的博客 1406

C++经典面试题100例及答案

答:把一些具有共性的对象归类后形成一个集合,也就是所谓的类。答:这样可以提高编译效率,因为分开的话只需要编译一次生成对应的.obj文件后,再次应用该类的地方,这个类就不会被再次编译,从而大大的提高了编译效率。答:这种函数会自动为内联函数,这种函数在函数调用的地方在编译阶段都会进行代码替换。答:常对象是指在任何场合都不能对其成员的值进行修改的对象。答:所谓的回调函数,就是预先在系统的对函数进行注册,让系统知道这个函数的存在,以后,当某个事件发生时,再调用这个函数对事件进行响应。

usstmiracle的博客 3万+

52.TcpSocket

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

qq_55640460的博客 73

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

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

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

dumuweiyang的博客 1421

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

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

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

m0_73537205的博客 2485

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

单机轻松支持百万并发的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、付费专栏及课程。

余额充值