`

在网络中使用IO流进行数据收发

 
阅读更多
在Java当中,所有对外设的操作都通过IO流来实现,不管是从磁盘中读取或写入文件,或者是从网络环境中接收或发送数据。IO流的基类有两个InputSstream和OutputStream,它们实现IO最基本的、无数据缓冲的、按节节流进行读写的操作功能。但是在实际的处理当中,为了数据读写的方便或提高读写的效率,往往会用到它们的子类,比如带缓冲区的类BufferedInputStream、BufferedOutputStream,DataInputStream、DataOutputStream等等。
现在,我要来模拟一个常见的网络模型:客户端/服务器模式,来了解网络IO流的具体操作。首先我定义数据包的格式分别为消息头和消息体,其中消息头由三部分组成:消息包的总长度、命令字、序列号,各为一个整形,消息体为一个Byte数组,由若干字节组成,长度由消息包的总长度减去12个消息头的长度得来,消息体当中有一部分是消息内容,这个具体的组成不再详述,消息包的定义不是我们关注的重点,我们着重来看一下数据发送、接收的实现及效率如何。
首先来写一个服务端的程序来接收数据,写一个客户端程序来发送数据,总共发送1万条数据进行测试,服务器端代码如下:
importjava.io.IOException;
importjava.net.ServerSocket;
importjava.net.Socket;

importcom.gftech.cmpp.bean.CmppBody;
importcom.gftech.cmpp.bean.CmppDeliver;
importcom.gftech.cmpp.bean.CmppHead;
importcom.gftech.cmpp.bean.CmppPack;
importcom.gftech.cmpp.bean.ICmppCmdID;
importcom.gftech.smp.SmpDeliverPack;

/***//**
*测试读取速度:使用Buffer和不使用Buffer的情况
*
*
@authorsinboy
*
@since2007.3.30
*
*/

publicclassServerDemo...{

publicstaticvoidmain(String[]args)...{
intlistenPort=2000;
try...{
Socketclient
=null;
ServerSocketss
=newServerSocket(listenPort);
System.out.println(
"侦听"+listenPort+"端口,等待客户端的连接...");
while(true)...{
client
=ss.accept();
System.out.println(
"接收到通信平台或客服系统的连接"+client.toString());

read(client);
}

}
catch(IOExceptione)...{
e.printStackTrace();
}

}


publicstaticvoidread(Socketsock)...{
if(sock!=null)
try...{
while(true)...{
CmppPackcp
=CmppCommu.receive(sock);
if(cp!=null)...{
CmppHeadhead
=cp.getHead();
CmppBodybody
=cp.getBody();
if(head!=null)...{
switch(head.getCmdID())...{
caseICmppCmdID.CMPP_DELIVER:
CmppDelivercd
=newCmppDeliver(body.getBody());
if(cd!=null)...{
SmpDeliverPackpack
=newSmpDeliverPack(cd);
Stringcontent
="cmppdeliver:"+pack.getSrcAddr()+""+pack.getDestAddr()+""+pack.getContent()+""
+pack.getLinkID();
System.out.println(content);

}

}

}

}

}

}
catch(IOExceptione)...{
e.printStackTrace();
}

}


}


publicvoidsend(byte[]b)throwsIOException...{
if(sock==null)
thrownewIOException();
if(b!=null)...{
try...{
BufferedOutputStreamos
=newBufferedOutputStream(sock.getOutputStream());
if(b!=null)...{
os.write(b);
os.flush();
}

}
catch(IOExceptione)...{
throwe;
}

}

}

客户端代码如下:

publicclassClientDemo...{

publicstaticvoidmain(String[]args)...{
try...{
Socketclient
=newSocket("192.168.10.8",2000);
write(client);
}
catch(UnknownHostExceptione)...{
e.printStackTrace();
}
catch(IOExceptione)...{
e.printStackTrace();
}


}


publicstaticvoidwrite(Socketclient)...{
try...{
ArrayList
<CmppPack>cpList=newArrayList<CmppPack>();
for(inti=0;i<10000;i++)...{
SmpDeliverPackpack
=newSmpDeliverPack("13612345678","01234",""+(i+1));
CmppDelivercd
=pack.toCmppDeliver();
if(cd!=null)...{
CmppPackcp
=newCmppPack(newCmppHead(12+cd.getBody().length,ICmppCmdID.CMPP_DELIVER,100),cd);
cpList.add(cp);
}

}


for(inti=0;i<cpList.size();i++)...{
CmppCommu.send(client,cpList.get(i));
System.out.println(i);
}

Thread.sleep(
10000);
}
catch(IOExceptione)...{
e.printStackTrace();
}

catch(InterruptedExceptione)...{

}

}

}

用到的发送和接入方法如下:

publicclassCmppCommu...{

publicstaticvoidsend(Socketsock,CmppPackpack)throwsIOException...{
if(sock!=null&&pack!=null)...{
try...{
byte[]b=pack.getBytes();
BufferedOutputStreamos
=newBufferedOutputStream(sock.getOutputStream());
if(b!=null)...{
os.write(b);
os.flush();
}

}
catch(IOExceptione)...{
throwe;
}

}


}

publicstaticCmppPackreceive(Socketsock)throwsIOException...{
finalintMAX_LEN=10000;
CmppPackpack
=null;

if(sock==null)
thrownewIOException();
try...{
BufferedInputStreambis
=newBufferedInputStream(sock.getInputStream());
DataInputStreamin
=newDataInputStream(bis);

intlen=in.readInt();//读取消息头
if(len>=12&&len<MAX_LEN)...{
intcmd=in.readInt();
intseq=in.readInt();

intbodyLen=len-12;
byte[]msg=newbyte[bodyLen];
in.read(msg);
//读取消息体

CmppHeadhead
=newCmppHead(len,cmd,seq);
CmppBodybody
=newCmppBody(msg);
pack
=newCmppPack(head,body);
}

}
catch(SocketTimeoutExceptione)...{
//logger.warn("timeout");
}
catch(IOExceptione)...{
throwe;
}


returnpack;
}

}

使用Eclipse TPTP对程序的执行进行监控,结果如下:

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><a href="http://p.blog.csdn.net/images/p_blog_csdn_net/sinboy/293422/o_1.JPG"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/sinboy/293422/o_1.JPG"></a></shapetype>

<shapetype stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-family: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-bidi-font-size: 12.0pt; mso-font-kerning: 1.0pt">如上图所示,服务器端程序在接收数据时总共花了</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-fareast-font-family: 宋体; mso-bidi-font-size: 12.0pt; mso-font-kerning: 1.0pt">21</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-family: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-bidi-font-size: 12.0pt; mso-font-kerning: 1.0pt">秒多。因为从理论上讲把输入流用</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-fareast-font-family: 宋体; mso-bidi-font-size: 12.0pt; mso-font-kerning: 1.0pt">Buffer</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-family: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-bidi-font-size: 12.0pt; mso-font-kerning: 1.0pt">包装一下接收的速度会更快一些,下面我们对它进行验证,把接收程序略做改动,增加一句</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-fareast-font-family: 宋体; mso-bidi-font-size: 12.0pt; mso-font-kerning: 1.0pt">Buffer</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-family: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-bidi-font-size: 12.0pt; mso-font-kerning: 1.0pt">包装:</span></shapetype>

BufferedInputStreambis=newBufferedInputStream(sock.getInputStream());
DataInputStreamin
=newDataInputStream(bis);

再次运行,发现一个很奇怪的问题,接收时有数据丢失的情况,正常情况下应该是从110000

cmppdeliver:1361234567801234116240905710000010001
cmppdeliver:
1361234567801234216241005710000010001
cmppdeliver:
136123456780123426116241005710000010001
cmppdeliver:
136123456780123426216241005710000010001
cmppdeliver:
136123456780123426316241005710000010001
cmppdeliver:
136123456780123426416241005710000010001
......

为何用Buffer进行包装之后会有数据丢失而没有包装时接收正常呢?我们仔细研究一下接收的源代码发现,在每次对数据进行接收时,都会在一个Socket上重新建一个输入流,因为BufferedInputStream在创建时会自动建立一个缓冲区用于整块数据的读取,系统默认为8192,也就是说当你读第一个包的时候,系统本身其实已经读取的8192个字节的数据,而你的包大小可能是只有100个字节,在你循环过来再次读第二个数据包时,等于又重新创建了一个输入流,把前面那个丢掉了,而它原先已经预先读取的数据也随之丢失。而不用Buffer包装的输入流因为每次都是从实际的IO中读取数据所以不存在数据丢失的情况。

明白了这一点,那我们可以把创建输入流放到每次读取的循环之外,修改读取方法如下:

publicstaticCmppPackreceive(DataInputStreamin)throwsIOException...{
finalintMAX_LEN=10000;
CmppPackpack
=null;
if(in==null)
thrownewIOException();
try...{
intlen=in.readInt();//读取消息头
if(len>=12&&len<MAX_LEN)...{
intcmd=in.readInt();
intseq=in.readInt();

intbodyLen=len-12;
byte[]msg=newbyte[bodyLen];
in.read(msg);
//读取消息体

CmppHeadhead
=newCmppHead(len,cmd,seq);
CmppBodybody
=newCmppBody(msg);
pack
=newCmppPack(head,body);
}

}
catch(SocketTimeoutExceptione)...{
//logger.warn("timeout");
}
catch(IOExceptione)...{
throwe;
}


returnpack;
}

服务器端的代码也作相应调整,把创建输入流的过程放在循环之外,保证每次读取数据包都是使用同一个输入流:

BufferedInputStreambis=newBufferedInputStream(sock.getInputStream());
DataInputStreamin
=newDataInputStream(bis);
while(true)...{
CmppPackcp
=CmppCommu.receive(in);
...

再次进行测试,结果如下:

从上图可以看出来,效率提高了很多,这里提高的原因有两点:一个使用了Buffer进行输入流的包装,二是每次读取数据包时创建新的输入流的过程放到了循环之外,减少的资源的开销。但纯粹的使用Buffer进行包装,效果究竟能提高多少呢?我们再次使用非Buffer的读取方式,但把创建输入流的过程放在循环之外,接收方法不变,服务端代码如下:

DataInputStreamin=newDataInputStream(sock.getInputStream());
while(true)...{
CmppPackcp
=CmppCommu.receive(in);
...

从上图可以看出,非Buffer包装的输入流,时间增加了约3秒钟,效率下降了50%左右。

再发客户端的程序做一个改进,把创建输出流这一步也提到循环之外:

BufferedOutputStreamout=newBufferedOutputStream(client.getOutputStream());
for(inti=0;i<cpList.size();i++)...{
CmppCommu.send(out,cpList.get(i));
System.out.println(i);
}

提高虽然不明显,但也有提高,结果如下:

从上述的测试过程中可以明白两点:
1、使用Buffer进行输入输出流的包装可以减少程序对IO的实际操作次数,提高效率
2、尽量把不变的东西提到循环之外,象创建输入流这一步,极大地影响了程序的执行效率

分享到:
评论

相关推荐

    IO流的操作

    在Java编程语言中,输入输出流(IO流)是处理数据传输的核心机制,无论是读取文件、接收网络数据还是在程序之间传递信息,都离不开IO流。本篇将深入探讨IO流的操作,包括其概念、分类、使用方法以及在实际开发中的...

    Android笔记:Socket客户端收发数据

    以上就是Android中使用TCP Socket客户端进行数据收发的基本流程。在实际项目中,你可能需要根据业务需求对代码进行优化,比如添加超时设置、数据编码解码等。同时,为了提高应用的健壮性,还要考虑网络状态变化、...

    Java 中的 IO 流是如何实现的?

    IO,即Input(输入)和Output(输出),在Java中表现为数据流的形式,用于读取或写入各种数据源,如文件、网络连接等。Java的IO流体系结构复杂而强大,包括字节流和字符流两大类,以及许多子类,如缓冲流、转换流等...

    Java输入输出流(IO)教程

    使用场景及目标:适用于开发中有需要处理数据存储、网络数据收发等问题的人群,在项目实战中解决文件读写和数据交互问题。 其他补充说明:该教程不仅包含了理论概念,还辅之大量示例帮助理解和掌握知识点,并提供了...

    高速IO接口技术-serdes

    其中,RocketIO技术是Xilinx公司的高端FPGA产品中集成了高速串行收发器技术,通过先进的信号处理和优化技术,实现了高速、高效的数据传输。RocketIO技术可以支持多种高速串行标准,如PCI Express、RapidIO、ATA等,...

    c#基于串口数据的接收和网络数据的发送

    在IT行业中,串口通信和网络数据传输是两个至关重要的技术领域,特别是在设备间的数据交互和控制系统设计上。本文将详细探讨如何使用C#进行串口数据接收,并通过网络发送这些数据,以此实现如展厅入口控制媒体播放的...

    node.js实现后台连接socket套接字获取数据

    在IT行业中,Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它允许开发者使用JavaScript进行服务器端编程。Socket.IO是一个实时应用框架,它为实时、双向通信提供了强大的工具,特别适用于构建实时交互的Web...

    Xilinx Rocketio资料整理

    这些资源可能涵盖了Aurora的协议规范、配置指南、硬件接口定义以及如何在实际设计中使用Aurora进行数据传输的实例。通过深入学习和理解这些材料,工程师能够更好地掌握如何在Xilinx GTX RocketIO上实现Aurora协议,...

    c# 收发文件客户端

    在C#编程环境中,开发一个文件收发客户端是一项常见的任务,尤其在网络通信领域。这个项目主要涉及了C#中的网络编程、文件操作以及用户界面设计等核心知识点。下面将详细阐述这些关键点。 1. **网络编程基础**: ...

    数据流图Net-mai笔记

    在“数据流图Net-mai笔记”中,我们很可能是探讨如何使用数据流图来分析和设计网络邮件系统。在这个场景下,我们可以深入理解数据流图的基本元素、作用以及在C语言编程环境下的应用。 1. 数据流图基本元素: - **...

    Java数据输入输出流.pdf

    Java数据输入输出流是Java IO库中的重要组成部分,主要用于处理不同数据类型的输入和输出操作,尤其是在文件和网络通信中。本文将深入探讨DataInputStream和DataOutputStream这两个类以及它们所关联的DataInput和...

    IO:java基础io流

    7. 网络流:在进行网络通信时,`Socket` 和 `ServerSocket` 提供了基于TCP的字节流,`DatagramSocket` 和 `DatagramPacket` 提供了基于UDP的字节流。这些流类使得Java程序能够收发网络数据。 8. 多路复用流:`...

    java实现基于netty 的udp字节数据接收服务

    本示例关注的是如何利用Netty实现一个基于UDP(User Datagram Protocol)的数据接收服务,这在需要进行快速、无连接的数据传输场景中非常常见。以下是对这个主题的详细讲解: 首先,我们需要理解UDP的基础知识。UDP...

    C# Socket Server Client 收发图片

    在IT行业中,网络编程是不可或缺的一部分,特别是在分布式系统和实时通信的应用中。C#作为.NET框架的主要编程语言,提供了强大的网络编程支持。本教程将详细讲解如何使用C#的Socket库来实现一个简单的图片收发功能,...

    多人聊天室 JAVA技术开发

    在聊天室中,IO流用于在网络连接中传输数据。Java的IO流分为字节流和字符流两大类,且有输入流和输出流之分。例如,`java.io.InputStream`和`java.io.OutputStream`用于处理字节流,`java.io.Reader`和`java.io....

    11.Linux开发-网络编程、网络通信介绍.pdf

    2. **数据收发原理**:在TCP/IP协议栈中,数据被分割成数据包(或称报文段),通过网络层传输,然后在接收端重新组装。数据的可靠性由协议本身处理,如TCP确保数据的顺序和无丢失传输,而UDP则不提供这些保证。 3. ...

    使用电脑UART模拟LIN总线

    在进行实际的LIN诊断时,LindiagnosisTool这样的软件可以提供便利。它可以用来发送LIN命令,读取从节点的响应,检查通信的正确性,以及进行故障注入以测试系统的容错能力。 总结来说,使用电脑UART模拟LIN总线是一...

    基于RocketIO的高速光纤红外图像串行传输的实现.pdf

    在高帧率多通道数字图像传输和远程高速反馈控制的应用中,需要实时地采集大量数据并进行处理,FPGA结合RocketIO技术为这类系统提供了可能。 3. **高性能计算**: 在高性能计算领域,数据传输速度是影响整个系统...

    c# socket通信,序列化后的收发源代码,绝对值5分 TcpListener

    本篇文章将深入探讨C#中的`TcpListener`类以及如何结合序列化进行数据的收发。 首先,让我们了解TCP(传输控制协议)的基础知识。TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,它确保了数据的正确性...

    JAVA程序与PLC通信——学习使用

    本篇将详细探讨"JAVA程序与PLC通信——学习使用"这一主题,包括如何使用Java实现与西门子PLC的串口通信,以及如何收发数据。 首先,我们要理解Java语言的优势。Java作为一种跨平台的编程语言,其强大的网络支持和...

Global site tag (gtag.js) - Google Analytics