`
lei_cbd
  • 浏览: 1769 次
  • 性别: Icon_minigender_1
  • 来自: 南京
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

做一个通讯项目的一点心得

阅读更多
最近做了一个小型的通讯项目,有一点体会,在这里拿来和大家分享一下:

一:关于Socket的长连接
这个项目中,客户提出了“一次连接,10次交互”的需求,就是说创建一个Socket后,在客户端与服务端完成10次交互前(客户端请求,服务端响应算一次交互),不会被关掉。起初按照这种思路来做Socket的长连接,发现在使用阻塞IO的情况下,如果,在一次交互后不关闭Socket,那么下次客户端虽然可以正常发送请求,但是怎么也读不出服务端的响应信息,因为服务端的输入流被阻塞在上次读取后。如果,每次交互后都关掉Socket,就不符合客户的要求了。于是查资料,说是用异步输入输出流java.nio,于是将其引入项目中,结果发现,异步IO虽然为多个Socket提供了不同的通道,但是对一个Socket而言,依然存在上述问题。于是和客户沟通,客户问一技术牛人,结果得到“Socket长连接指的是服务端将消息往客户端推, 就是在一次连接下,客户端向服务端发多条消息, 服务端一次响应完毕,其间,客户端如果在一定的时间内没有消息发往服务端,服务端会主动断开连接”。

二:关于Socket使用中读取响应消息的方法
使用Socket进行通信会涉及到读取服务端的响应消息。读取的方法可分为2类三种。

类1:一次性全部读取。

代码:
方法一:public String getResultStr(Socket sourceSocket)
{

String resultStr = null;

InputStream in;
try {
in = sourceSocket.getInputStream();
int readIndex = 5 * 1024 * 1024;
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(in), readIndex);
char[] charArray = new char[readIndex];
int read_rst = bufferedReader.read(charArray);
resultStr = new String(charArray, 0, read_rst);
bufferedReader.close();

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

return resultStr;
}
类2:非一次性读取

方法二:一行行读
public String getResultStr(Socket sourceSocket)
{
String resultStr = null;

InputStream in;

try {
in = sourceSocket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(in));
StringBuffer responseBuffer = new StringBuffer();
String line = bufferedReader.readLine();
while (null != line)
{
responseBuffer.append(line);
line = bufferedReader.readLine();
}

bufferedReader.close();

resultStr = responseBuffer.toString();

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

return resultStr;
}

方法三:一字节一字节读
public String getResultStr(Socket sourceSocket)
{
String resultStr = null;

InputStream in = null;

try {
in = sourceSocket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(in));

int read_rst = bufferedReader.read();

StringBuffer readBuffer = new StringBuffer();
while (-1 != read_rst)
{
char singleChar = (char) read_rst;

readBuffer.append(singleChar);
}

bufferedReader.close();

resultStr = readBuffer.toString();
} catch (IOException e) {
e.printStackTrace();
}

return resultStr;
}

其中方法一的优点是读取速度快,且不用被超时所扰。缺陷是,只能读取一定量的字节,虽然BufferedReader的构造方法提供了设置缓冲区大小的功能,但是不管设多大,都只能读取一定量的字节,据项目中的情况来看,是65536个。如果响应消息有几MB的话,这种方法是肯定不行的。但是如果读取的消息很小,在65536个字节以内,则可以使用。

方法二的优点是便于做读取后的算法操作,速度嘛也挺快。缺陷是不能精确读取。因为readLine的方法读不出’\n’,’\r\n’,所以读出来的内容其长度与实际长度有出入。

方法三是最优解。(以上只是三种方法的原型,具体业务还要具体实现)


三:关于解决Socket读取响应消息超时的分析及解决方法:

分析
在使用Socket的过程中会遇到读取响应消息超时的问题,这是为什么呢?就我现在的理解,一句话:在服务端还没有关闭连接前,客户端读取响应消息就会一直等待,直到超时。

解决方法:
1.Socket提供的setSoTimeout(int timeout) 方法
在获得Socket的实例后,设置下超时时间,然后当read或是readLine完最后一个字节或是字符串后,会抛一个InterruptedIOException,在catch里做你想做的事情,或break,或关掉连接。

2.如果服务端也是你设计的话(就是响应消息也是你拼接的),请看。
可以在服务端里拼接响应消息的方法里为响应消息加上一个‘\r\n‘,注意一定要加在消息末尾。然后用“二:关于Socket使用中读取响应消息的方法”里第三种方法来读取响应消息,注意,通信消息头中肯定会有“Content-Length“一项,先取出其值(Content-Length表示消息主体的字节长度),接着找出主体消息的起始位置(主体消息中的最前方一定要是个固定的内容),开始计算响应消息的实际字节长度,最后将取出的Content-Length的值与实际计算出的长度进行比较,如果相等就break掉,这样,就不会读到会引起超时消息末尾。

其方法1的优点时易于实现,缺陷是不安定因素太大。setSoTimeout里总要设个值,设多少呢?假设在网络正常情况下读一个1MB的响应消息需要1s,那么如果网络阻塞呢?如果你设一个很大的值,就会影响使用。
方法2的优点是不受网络因素的影响。缺陷是,如果服务端的中文字符编码与流中的中文字符编码不一致,会导致乱码,进而会影响计算的准确度。一旦计算的不准,就会超时。
不过,方法2仍是最优解。

以上就是自己对这个项目的一点体会,欢迎大家指正。
分享到:
评论
17 楼 coofucoo 2009-04-11  
fjlyxx 写道

  ,网络读取绝对不能根据这种读取方式..这样的读取方式根本不能保证数据的完整性. 建议你先固定读取12个字节(包头定义) 这12个字节就表示你要接收的数据大小.如果读满了这12个字节的数据那么就读完了. 不要按行去读取,你可以定义一个缓冲区byte[size] 你每次都读取<=size的数据  这种效率比按行快多了.



不用mina的时候项目中就这么做。
16 楼 coofucoo 2009-04-11  
hbb 写道
为什么不用mina呢?



对啊,怎么不用mina。。。
15 楼 fjlyxx 2009-04-02  
shansun123 写道
unsid 写道
两个问题:
1 为什么网络流不建议用字节读取行 line = bufferreader.readLine()
2 起初我也为如何判断读取完成而恼火,读取文件流可以用(line = br.readLine()) != null(按字节读取用-1判断),但是在网络流里面这个怎么也读不对,后来别人告诉我在服务器发送完信息后用socket.shutdownOutput(),之后!= null可以判断了

吸收了~

  ,网络读取绝对不能根据这种读取方式..这样的读取方式根本不能保证数据的完整性. 建议你先固定读取12个字节(包头定义) 这12个字节就表示你要接收的数据大小.如果读满了这12个字节的数据那么就读完了. 不要按行去读取,你可以定义一个缓冲区byte[size] 你每次都读取<=size的数据  这种效率比按行快多了.
14 楼 beckrabbit 2009-03-20  
做和C++的长连接交互时 他们喜欢先写一个报文头,在报文头中写清报文体的实际大小,我们java在读取的时候用public int read(byte[] buf,int offset, int length)
方法读取这个大小,如果这个数值比较大的话只调用一次read是肯定读不完整的,所以要while循环直到读取完毕整个流的内容,在多线程操作时要注意加锁或者放在队列里面以免读乱了,至于line = bufferreader.readLine() 这种一行读取的方式只适用于java和java的通讯,和C++什么的你永远得不到结束表示,至于nio倒不是很关键的东西。
13 楼 shansun123 2009-03-20  
unsid 写道
两个问题:
1 为什么网络流不建议用字节读取行 line = bufferreader.readLine()
2 起初我也为如何判断读取完成而恼火,读取文件流可以用(line = br.readLine()) != null(按字节读取用-1判断),但是在网络流里面这个怎么也读不对,后来别人告诉我在服务器发送完信息后用socket.shutdownOutput(),之后!= null可以判断了

吸收了~
12 楼 keep____moving 2009-03-19  
都是高手啊!!!让我这个新手有点自卑了,学习学习……
11 楼 fjlyxx 2009-03-01  
先定义好自己的数据包,建议不要按行读取.你可以用开头4个字节表示要读取的字节大小.然后用两个交替的缓冲区去读取数据库再设置一个已经读取的字节长度去判断是否读完.

关于长连接十次交互,理论上是不允许这样设计的,其实你可以做出逻辑上的长连接不一定要做成物理上的长连接. 你可以靠令牌去实现这个. 大概可以分为以下几个步骤 第一 请求连接 服务端返回任务令牌  第二次 发送请求 这个和你具体业务有关  要分包的分包 要编码的编码等等 这个步骤是不确定 你可以进行多次的交互 第三 就是断开连接 注明该次任务情况等等   以上连接你可以定义出 你的发送包数据格式和回复包数据格式 包头可以用固定字节  比如 发送长度用4个字节  令牌用12个字节  命令用4个字节 等等
10 楼 bachmozart 2009-03-01  
为啥要用字符流去操作捏???

BufferReader以及XXXReader 这种东西还是别在Socket中用的为好
9 楼 unsid 2009-02-20  
我一只不理解读多了指的是什么,是不是可以考虑有这么3种情况
1 缓存中有多于一行(\r\n)的纪录,那么readLine()读取一行,buff里还剩下一些
2 缓存中有不到一行的记录,那么readLine()
  (1)是阻塞,直到缓存中出现\r\n为止 ?
  (2)现在缓存中有多少就认为是一行?

我觉得很可能是第2个情况,因为一个网络流很可能没有换行,那么不可能直到超时才继续执行。

我觉得可能是这样:

情况一:你只是希望你文件所有的内容都传到服务器上而已,那么一行读取是否为实际真的一行就不重要了,因为最终拼成的文件是完整的就够了。

情况二:你的客户端基于服务器段传输信息格式,比如说第一行是命令,第二行是版本号,那么你需要明确知道读多少是一行,那可能buff就有你说的问题了,总之仅仅需要传输完整的文件,无所谓读多读少问题。
8 楼 andot 2009-02-20  
BufferReader 读文件没问题是因为文件长度是已知的,而 BufferReader 实际上是把文件内容在读取时一次不是读入一个字节,而是一次读入一段(这一段的大小跟Buffer大小设置有关),所以,你在 BufferReader 里用 readLine 读入一行,实际上可能不止一行被读入到内存,只是返回一行的内容给你,当你下一次读取时,如果内存中的数据是你想要的,它就不再读取磁盘上的文件,这就是 Buffer 的作用。

你用它读网络流它仍然是按照它的 Buffer 大小来读入字节数的,所以读多了就会有麻烦了。
7 楼 unsid 2009-02-20  
两个问题:
1 为什么网络流不建议用字节读取行 line = bufferreader.readLine()
2 起初我也为如何判断读取完成而恼火,读取文件流可以用(line = br.readLine()) != null(按字节读取用-1判断),但是在网络流里面这个怎么也读不对,后来别人告诉我在服务器发送完信息后用socket.shutdownOutput(),之后!= null可以判断了
6 楼 hbb 2009-02-20  
为什么不用mina呢?
5 楼 dennis_zane 2009-02-20  
似乎大家都不知道有个东西叫ByteBuffer,有个东西叫DataInputStream。

楼主我记的在这里发帖提过那个所谓长连接的问题,客户端阻塞肯定是因为读不到什么东西或者读不够你设定的缓冲区字节数,阻塞怎么会是关闭socket,读到-1或者抛出EOFException也才是对端关闭了输出流,但是也不代表socket关闭了,所以很大可能是你们客户的服务端的实现问题。


4 楼 iXh 2009-02-20  
越过。。。。
3 楼 qingsong 2009-02-20  
学习中......
2 楼 lei_cbd 2009-02-20  
多谢,这个方法我接收了。
1 楼 andot 2009-02-20  
lei_cbd 写道
说创建一个Socket后,在客户端与服务端完成10次交互前(客户端请求,服务端响应算一次交互),不会被关掉。


这个可以做到,首先setKeepAlive(true),然后写入流和读取流在使用完之后都不要 close,写入流可以用 BufferedOutputStream 包装,写入数据后,用 flush 方法确认把数据全部发送出去,但千万别把 BufferedOutputStream close 掉,否则连原始的写入流也会 close 掉。读取流不要用 BufferedReader 包装,否则它可能会自作主张的多读入数据,以至于影响你的后续通讯。读取流的时候直接用返回的原始流就可以,然后按照字节读取,读取的数据可以先读入 byte[] 数组,然后写入 ByteArrayOutputStream 中,最后转成完整的数据 byte[],或者根据需要再将其转换为字符串。也可以一个字节一个字节的读,然后用 StringBuffer 组成完整字符串。后一种方法更适合于读取的都是 ASCII 码的数据(比如 HTTP 的头)。最关键的就是不要多读,一般协议中会包含有消息的长度,然后根据那个长度就可以判断是否读取完成,也有根据标志来标识是否结束的(例如 HTTP 的头是两次连续的 \r\n 表示头结束),当然按字节读,读到 -1 也是流结束的标志。通过这种方式,你就可以在一个 Socket 上反复的写入读取数据,不要说交互10次,就是100次也没问题。另外,你还要保证不要多线程的在一个 Socket 上同时发出多个这样的读写请求,否则通讯就乱掉抛出异常了。

我在写 PHPRPC 客户端时,就是用这种方法来跟服务器通讯的,实践证明是很有效的。

相关推荐

    软件项目管理学习心得(精选5篇)参考.doc

    软件项目管理学习心得(精选5篇)参考.doc是软件项目管理的学习心得,涵盖了项目管理的基本概念、项目集成管理、项目范围管理、项目进度管理、项目成本管理、项目质量管理、项目人力资源管理、沟通管理、风险管理和...

    java项目开发心得

    在Java项目开发中,开发心得主要集中在以下几个方面: 1. **知识技术储备**:开发者需要具备广泛的知识技术,包括但不限于Java基础知识、JSP、数据库管理、Servlet、EJB(Enterprise JavaBeans)、开发环境配置和...

    Winform项目开发心得

    在 Winform 项目中,登录界面验证成功后需要进入主界面,这可以通过在登录窗体中增加一个静态变量来实现。当登录成功时,将静态变量设置为 true,并关闭登录窗体,然后在程序的 Main 入口中使用 Application.Run() ...

    推荐一个毕业项目及写作心得

    【标题】:“推荐一个毕业项目及写作心得” 在信息技术领域,毕业项目是学生们展示自己学习成果和实践能力的重要环节。这个标题所暗示的知识点主要涵盖了毕业设计的选择、实施过程以及项目报告的撰写技巧。 首先,...

    项目经理心得体会参考.doc

    项目经理心得体会参考.doc

    项目管理心得体会与学习合集

    在IT行业中,项目管理是一项至关重要的技能,它涵盖了多个领域,包括团队协调、计划制定、风险管理以及执行与监督。以下是对标题、描述和标签中所提及的文件内容的详细解析: 1. **产品部、项目组、实施组的关系**...

    IT项目管理心得体会与学习合集

    在IT行业中,项目管理是一项至关重要的技能,它涵盖了项目的规划、执行、监控和收尾等多个阶段,确保项目能够按时、按预算、按质量地完成。本资料合集围绕"IT项目管理心得体会与学习合集"的主题,提供了丰富的学习...

    SSH项目源码及心得体会

    这个"SSH项目源码及心得体会"的资源对于初学者来说尤其宝贵,因为它不仅包含了实际项目的源代码,还记录了开发者在整合和使用SSH框架过程中的心得与经验。 Struts2是MVC(Model-View-Controller)架构模式的一种...

    项目管理心得:一个项目经理的个人体会、经验总结

    本人做项目经理工作多年,感到做这个工作最要紧的就是要明白什么是因地制宜、因势利导,只有最合适的,没有什么叫对的,什么叫错的,项目经理最忌讳的就是完美主义倾向,尤其是做技术人员出身的,喜欢寻找标准答案,...

    ARM的一点心得 ARM的一点心得

    看门狗是一个重要的安全机制,它可以作为定时器触发中断或者在程序出现故障时强制系统复位,防止程序失控。在设置看门狗时,需要根据实际需求配置其计时周期。 系统时钟初始化涉及多个寄存器,例如LOCKTIME、PLLCON...

    项目总结+项目心得体会

    在IT行业中,项目总结与项目心得体会是至关重要的环节,它们能帮助我们提炼经验,提升技能,为未来的项目提供宝贵借鉴。以下将详细讨论这些文件所涵盖的知识点。 首先,我们看到"Ext3.2中文API(最终完成版2010-12-...

    java开发三年项目经验心得

    ### Java开发三年项目经验心得分析 #### 一、项目经验概览 在这篇文章中,作者回顾了自己在Java开发领域三年的工作经历,期间参与了三个不同的项目,从中获得了丰富的实战经验和技术积累。这三个项目分别是涉密...

    项目心得体会.rar

    "项目心得体会.rar"这个压缩包文件,显然包含了某个人或团队在完成一个项目后总结的心得体会,这通常是一份珍贵的文档,记录了他们在项目实施过程中的学习、挑战以及解决策略。下面,我们将深入探讨项目管理的一些...

    互联网项目管理心得.pdf

    项目经理应该认识到,计划发布日期只是一个目标,而实际合理发布日期是项目组可以实际实现的日期。 2. 随着项目的进行,实际合理发布日期会逐渐明朗。项目经理应该调整项目计划,使之与实际合理发布日期相符。 3. ...

    JAVA做完项目心得

    1. **面向对象编程**:Java是一种纯面向对象的语言,我在项目中深入了解了类、对象、继承、封装和多态等概念。通过创建实体类来模拟现实世界中的对象,我学会了如何设计合理的类结构,以及如何利用多态性提高代码的...

    junit测试的一点心得

    junit测试的一点心得,在spring中加载外部配置文件是的一点小技巧junit测试

Global site tag (gtag.js) - Google Analytics