`
linliangyi2007
  • 浏览: 1012575 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

Graceful Java Programming 优雅Java编程 之Socket Client

阅读更多
   老久没有动手写Socket程序了,今天应同事的要求上了一段程序。

这是一段很简单与C++编写的服务端通讯的java客户端,咋一看上去,没有任何问题。

貌似没有问题的程序

	public static String sendSynMsg(String ipAddr, byte[] datas) throws Exception{
		//解析服务器地址和端口号
		int dotPos = ipAddr.indexOf(':');
		String ip = ipAddr.substring(0, dotPos).trim();
		int port = Integer.parseInt(ipAddr.substring(dotPos+1).trim());
		InetSocketAddress endpoint = new InetSocketAddress(ip , port);
		
		Socket socket = null;
		OutputStream out = null;
		InputStream in = null;
		try	{		
			socket = new Socket();
			//设置发送逗留时间2秒
			socket.setSoLinger(true, 2); 
			//设置InputStream上调用 read()阻塞超时时间2秒
			socket.setSoTimeout(2000);
			//设置socket发包缓冲为32k;
			socket.setSendBufferSize(32*1024);
			//设置socket底层接收缓冲为32k
			socket.setReceiveBufferSize(32*1024);
			//关闭Nagle算法.立即发包
			socket.setTcpNoDelay(true);
			//连接服务器
			socket.connect(endpoint);
			//获取输出输入流
			out = socket.getOutputStream();
			in = socket.getInputStream();
			//输出请求			
			out.write(datas);
			out.flush();
			//接收应答
			BufferedReader br = new BufferedReader( new InputStreamReader(in) , 4096);
			StringWriter received = new StringWriter(4096);
			char[] charBuf = new char[4096];
			int size = 0;
			while ((size = br.read(charBuf)) > 0){
				received.write(charBuf, 0, size);
			}
			return received.toString();
			
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch(Exception ex) {
					ex.printStackTrace();
				}
			}
			if (in != null) {
				try {
					in.close();
				} catch(Exception ex) {
					ex.printStackTrace();
				}
			}		
			if (socket != null) {
				try {
					socket.close();
				} catch(Exception ex) {
					ex.printStackTrace();
				}
			}
		}				
	}


但实际的调试中,总是报Read TimeOut异常!!排查原因后发现,数据是接收到了,只是size = br.read(charBuf)不会返回-1(java doc中的说明是读取到结束时size会返回-1)。
对于编写服务器端程序的C++程序员而言,他们通常会在通讯结束的时候,在数据的尾部加上一个\0的结束字符。因此,我们针对此做了修正

出问题的程序段
    while ((size = br.read(charBuf)) > 0){
        received.write(charBuf, 0, size);
    }


修改后的正确写法
  char lastChar = 0;
  do {
      size = br.read(charBuf , 0 , 4096);
      lastChar = charBuf[size-1];
      if(lastChar == 0){
          //去除尾部的\0字符
          received.write(charBuf, 0, size - 1);
      }
  }while(lastChar != 0);


最终的完整程序段:
	private static String sendSynMsg(String ipAddr, byte[] datas) throws Exception{
		//解析服务器地址和端口号
		int dotPos = ipAddr.indexOf(':');
		String ip = ipAddr.substring(0, dotPos).trim();
		int port = Integer.parseInt(ipAddr.substring(dotPos+1).trim());
		InetSocketAddress endpoint = new InetSocketAddress(ip , port);
		
		Socket socket = null;
		OutputStream out = null;
		InputStream in = null;
		try	{		
			socket = new Socket();
			//设置发送逗留时间2秒
			socket.setSoLinger(true, 2); 
			//设置InputStream上调用 read()阻塞超时时间2秒
			socket.setSoTimeout(2000);
			//设置socket发包缓冲为32k;
			socket.setSendBufferSize(32*1024);
			//设置socket底层接收缓冲为32k
			socket.setReceiveBufferSize(32*1024);
			//关闭Nagle算法.立即发包
			socket.setTcpNoDelay(true);
			//连接服务器
			socket.connect(endpoint);
			//获取输出输入流
			out = socket.getOutputStream();
			in = socket.getInputStream();
			//输出请求			
			out.write(datas);
			out.flush();
			//接收应答
			BufferedReader br = new BufferedReader( new InputStreamReader(in) , 4096);
			StringWriter received = new StringWriter(4096);
			char[] charBuf = new char[4096];
			int size = 0;
			char lastChar = 0;
			do {
				size = br.read(charBuf , 0 , 4096);
				lastChar = charBuf[size-1];
				if(lastChar == 0){
					received.write(charBuf, 0, size - 1);
				}
				//System.out.println(received.toString());
			}while(lastChar != 0);
			
			return received.toString();
			
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch(Exception ex) {
					ex.printStackTrace();
				}
			}
			if (in != null) {
				try {
					in.close();
				} catch(Exception ex) {
					ex.printStackTrace();
				}
			}		
			if (socket != null) {
				try {
					socket.close();
				} catch(Exception ex) {
					ex.printStackTrace();
				}
			}
		}				
	}



程序中的关键点:
1. 不直接使用new Socket(String ip , int port)的构造函数,而是设置了socket的环境参数后再连接

2.设置发送逗留时间 socket.setSoLinger(true, 2); 这个参数是socket发送数据时的超时,如果对方在固定时间内不接受,则关闭socket。与socket.setSoTimeout(2000)不同,这个是设置InputStream上调用 read()阻塞超时时间。

3.socket.setTcpNoDelay(true);关闭Nagle算法。这使得在调用out.flush();时总能第一时间的发送数据包(这个适用于你的数据包是完整的一次性发送的前提)。

4.根据应用协议的实际大小,优化你的接收和发送缓冲,这两个参数可以有效提高网络通信的效率。

5.使用char的数组配合StringWriter作为接收数据的写入,这个比使用readLine方法实现更优雅。

一点小经验,和大家分享

分享到:
评论
27 楼 linliangyi2007 2009-02-12  
swift 写道
不错的分享,不过有一些细节可能楼主忽略了

1.一般来说发送的http request正文并没有\0这么一个ending flag,而且大多数情况client的request在网络上并没有ending flag,这时候你的程序就没法判断了。
而在http协议中是通过Content-length这个属性来设置长度的,这个是正文byte数组的长度(并非char数组的长度,例如中文若以UTF-8编码则每个汉字占3个字节),只有按照这个读取才能保证准确无误。

2.网络字节流是基于byte的,如果你一上来就
linliangyi2007 写道

BufferedReader br = new BufferedReader( new InputStreamReader(in) , 4096);


会把inputstream的字节流强制转换成你的JVM charset,可能是GBK,GB18030或是UTF-8。
那么两边编码解码不一致,你的读也就不会正确了。


这位兄弟说的是。不过你回的急了些,我在回帖中已经说了使用byte的两种情况,其中就包括了协议过程中包含length标记的
26 楼 swift 2009-02-12  
unsid 写道

(3) 之前和别人讨论过关于网络通讯如何判断字节流是否接收完毕的话题,有人说"-1"是判断从文件读取的流,不能用来判断网络流,还有有人用in.avaliable()来判断该读取多少,你认为是否合理?我用in.avaliable()判断过,结果是不对的...

in.available()是说无阻塞情况下当前所能读的长度,不能用这个来判断。
我试过程序里如果流很大这个值有可能是8k(流是超过8k的),但有时候可能会是0(网络阻塞)。
所以有时候你会现程序在run模式这个值是0,而debug模式这个值并不是0,因为程序debug模式下一般是不会阻塞的。
25 楼 swift 2009-02-12  
不错的分享,不过有一些细节可能楼主忽略了

1.一般来说发送的http request正文并没有\0这么一个ending flag,而且大多数情况client的request在网络上并没有ending flag,这时候你的程序就没法判断了。
而在http协议中是通过Content-length这个属性来设置长度的,这个是正文byte数组的长度(并非char数组的长度,例如中文若以UTF-8编码则每个汉字占3个字节),只有按照这个读取才能保证准确无误。

2.网络字节流是基于byte的,如果你一上来就
linliangyi2007 写道

BufferedReader br = new BufferedReader( new InputStreamReader(in) , 4096);


会把inputstream的字节流强制转换成你的JVM charset,可能是GBK,GB18030或是UTF-8。
那么两边编码解码不一致,你的读也就不会正确了。
24 楼 unsid 2009-02-09  
如果要写一个网络通讯程序,你会像hassian一样,先写一组自己的IO包么,基于原始outputStream再次封装,考虑一下几种情况

1、请求-》数据
例如“我要用户名为admin的用户的详细信息”,则把"admin"和服务器端处理这个请求的服务类“serachuser”作为参数写入socket的outputStream。
2、请求+数据-》状态
例如“我要把file1文件存到服务器的c盘下”,则把命令“save”,路径“c:”和文件流写到socket的outputStream中,返回存储成功。

那么该如何组织这些通讯,把admin和serachuser拼装在一起“serachuser_Admin”到服务器上拆开解析还是分为两次通讯第一次发送serachuser返回ready状态再发送admin??
如果更复杂的第二种情况,将命令save和文件字节流拼在一起再解析显然比较困难,我以前的解决方法是用xml文件,<command>save</command><content>文件的2进制码的base64编码</content>,这样写会让文件变得非常的大,增加了不必要的传输量。如果将文件和“save”分为2此通讯,那这两次通讯应该是属于同一个事务,又怎么保证事务的完整性。
23 楼 bachmozart 2009-02-09  
TCP网络流的控制在于协议
22 楼 unsid 2009-02-09  
嘿嘿,准备翻翻rmi,hassian,jdbc driver的源码,看看里面对于网络流是怎么控制的,这个帖子应该继续讨论下去
21 楼 bachmozart 2009-02-09  
哈哈,是呀,dennis_zane 老大也太不讲究啦,光告诉我说我理解不对,就是不告诉我哪里不对,我很想知道的呀,o(∩_∩)o...

我只知道我少说了一个信号驱动模型,呵呵

决定了以后每次看到dennis_zane老大的帖子,都问他一遍,他不告诉我答案,我誓不罢休.o(∩_∩)o...
20 楼 pipilu 2009-02-09  
bachmozart 写道


另外,dennis_zane 老大,诚心向你请教我对IO模型理解哪里有问题呀?


哈哈,追到这个帖子里来了。
19 楼 linliangyi2007 2009-02-09  
swen00 写道
受教了,一般我都是用byte[]接收,没用过char[],没有需要size-1啊
我做法是字节流判断关闭socket
1)接收到-1
2)异常抛出
字符流
1)接收到Null
2)异常抛出


byte接收时,如果碰到char编码不同,则字节长度就不一样了,UTF8的汉字是3byte,英文字母1byte,不知道有没有记错。
utf-16的统统2byte

使用byte接收数据,我习惯两种情况:
1.通讯包有明确协议,能给出每段报文的字节数的;或者报文头部就带有描述字节数的信息的。
2.信息本身就是2进制的,如音频,图像等
18 楼 linliangyi2007 2009-02-09  
bachmozart 写道
SoLinger 套接字选项 取值

{0,0} 代表socket调用 close 后会等待发送缓冲区所有数据报送完毕,才会真正close socket ,这个是默认

{1,0} 夭折连接 ,即调用close ,不管发送缓冲区有没有待发送数据,都直接关闭连接,丢弃发送缓冲区的所有数据

{1,非0值n},调用close后,等待n长时间后(具体单位忘了),此时间中仍处理发送缓冲区的数据,数据发送完或者时间到,关闭socket

另外,dennis_zane 老大,诚心向你请教我对IO模型理解哪里有问题呀?

还想对楼主说一句,我们做java的也可以多去了解底层细节呀,虽然大多都是业余时间.



SoLinger时间单位是秒,而SoTimeOut是毫秒,呵呵,java这么设计估计是历史遗留问题吧,否则应该统一单位的

楼上说的对,还是俺不够勤奋啊,一直没有找到java方面对socket的描述(java网络通讯的书买了不少,但对socket下面的实现谈得少),也就懒得看与C、C++相关的文章了。

17 楼 bachmozart 2009-02-09  
SoLinger 套接字选项 取值

{0,0} 代表socket调用 close 后会等待发送缓冲区所有数据报送完毕,才会真正close socket ,这个是默认

{1,0} 夭折连接 ,即调用close ,不管发送缓冲区有没有待发送数据,都直接关闭连接,丢弃发送缓冲区的所有数据

{1,非0值n},调用close后,等待n长时间后(具体单位忘了),此时间中仍处理发送缓冲区的数据,数据发送完或者时间到,关闭socket

另外,dennis_zane 老大,诚心向你请教我对IO模型理解哪里有问题呀?

还想对楼主说一句,我们做java的也可以多去了解底层细节呀,虽然大多都是业余时间.



16 楼 baobao181 2009-02-09  
这段代码很好。。我来比较比较我写的。
15 楼 unsid 2009-02-09  
之前和别人讨论一个问题,socket.connect创建了一个网络连接,那么这个连接到底是个什么东西,我指示它以什么形势存在,比如是不是服务器端为客户端保存某个状态位,或者什么其他形势,为什么会有长连接和瞬时连接之分,在socket.close()之前和之后,服务器和客户端各发生了哪些变化
14 楼 whaosoft 2009-02-09  
精华帖 (0) :: 良好帖 (2) :: 新手帖 (17) :: 隐藏帖 (0)
为什么我打不了?
13 楼 swen00 2009-02-08  
另长连接的socket一般都会加上心跳包来判断连接

12 楼 linliangyi2007 2009-02-08  
unsid 写道
我纳闷一个问题:为什么进来一些人打了17个“新手贴”却始终是咱们3个人再说这个事,是:太简单不屑于回答?还是浮躁坏风气?我看答案未见得,这事真那么简单,这问题真那么新手,我看也不见得。


哈哈,楼上的兄弟,不用太气愤。目前中国的IT界浮躁风太甚。很多的所谓公司的高手,是只停留在应用层面,而缺乏专研和创新精神的。大多喜欢看很多开源项目的API,会用很多很多IDE,能搭各种应用服务器,当却很少注重算法,数据结构,也很少认真研究协议的。也许有很多人看了要生气,要不服气的,不过我觉得我说的是事情,相信javaeye上有真正的大牛,会有同样的感受。
11 楼 swen00 2009-02-08  
受教了,一般我都是用byte[]接收,没用过char[],没有需要size-1啊
我做法是字节流判断关闭socket
1)接收到-1
2)异常抛出
字符流
1)接收到Null
2)异常抛出
10 楼 unsid 2009-02-08  
我纳闷一个问题:为什么进来一些人打了17个“新手贴”却始终是咱们3个人再说这个事,是:太简单不屑于回答?还是浮躁坏风气?我看答案未见得,这事真那么简单,这问题真那么新手,我看也不见得。
9 楼 linliangyi2007 2009-02-08  
unsid 写道
这个主题好,虽然现在不用自己控制底层通讯细节了,但是不了解就会导致不能正确使用socket的api,尤其是socket通讯这样的程序即难以测试,又很难保证某一时刻不出意想不到的问题。。。。


百分之一千的赞同!!期待socket高人讲解,或者提供相关的资料!!
8 楼 unsid 2009-02-08  
这个主题好,虽然现在不用自己控制底层通讯细节了,但是不了解就会导致不能正确使用socket的api,尤其是socket通讯这样的程序即难以测试,又很难保证某一时刻不出意想不到的问题。。。。

相关推荐

    Go-graceful优雅的重载http服务器零宕机时间兼容systemdsupervisor

    本文将深入探讨如何使用Go-graceful库来实现优雅的HTTP服务器重载,确保在更新服务时实现零宕机时间,并同时兼容systemd和supervisor这两种常见的系统管理工具。 `Go-graceful`是一个专门设计用于处理HTTP服务器的...

    graceful:写优雅服务的库

    控制优雅的应用程序 优雅的应用程序可以用信号来控制。 主进程支持以下信号 期限/INT 快速关机 放弃正常关机 合肥 重启工人 启动新的工作进程 优雅关闭旧的工作进程 USR2 升级可执行文件(启动新的主进程和工作进程...

    Laravel开发-graceful-cache

    `graceful-cache`是一个针对Laravel的缓存存储库的包装器,其目标是提供一种更加优雅、灵活的方式来管理你的应用缓存。在这个项目中,开发者可以更好地控制缓存的生命周期,提高应用的效率。 `graceful-cache`的...

    前端开源库-graceful-readlink

    优雅的前端开源库——`graceful-readlink`是一个针对`fs.readlink`进行优化的模块,主要用于解决Node.js在处理符号链接时可能出现的问题。在Node.js原生的`fs`模块中,`fs.readlink`函数用于读取符号链接(symbolic ...

    前端开源库-graceful-ncp

    `graceful-ncp`是一个专门为前端开发者设计的开源库,它实现了优雅的、异步的文件和目录的递归复制功能。这个库的核心目标是提供一种更稳定、更易用的方式来处理文件系统操作,特别是在处理大量文件或复杂目录结构时...

    Graceful Graph

    优雅图(Graceful Graph)是图论中的一个概念,它源于数学和计算机科学,特别是网络流和图遍历的问题。这个概念是由美国数学家Edward G. Kolaitis在1970年代提出的,目的是研究图的标号特性。在英特尔线程挑战赛3月...

    前端开源库-graceful-kill

    本文将深入探讨“graceful-kill”的概念、作用以及它如何改善前端应用程序的性能。 1. 优雅地杀死进程 在计算机系统中,进程的正常退出通常涉及清理资源、保存状态和关闭连接等操作。"优雅地杀死"一个进程意味着...

    java程序发布成window后服台

    Java程序在Windows环境下发布为后台服务是常见的需求,这使得应用程序能够在系统启动时自动运行,无需用户交互。本文将详细讲解如何将一个Java程序打包并发布为Windows后台服务,同时提供一个具体的示例。 首先,...

    graceful-fs-fs模块的一个替代拥有各种改进

    优雅的fs(graceful-fs)是Node.js生态系统中一个重要的模块,它作为原生fs模块的一个增强版本,提供了一系列的改进和优化,旨在提高文件系统的操作性能和稳定性。在Node.js开发过程中,文件系统操作是不可或缺的一...

    Laravel开发-graceful-laravel-workers

    3. **优雅关闭(Graceful Shutdown)**:在“graceful-laravel-workers”中,主要目标是实现优雅关闭,即在接收到停止命令时,工作进程会等待当前处理的任务完成后再退出,确保数据的一致性和完整性。 4. **...

    express-graceful-exit, 为了得到零停机,优雅地退出 Express.zip

    express-graceful-exit, 为了得到零停机,优雅地退出 Express express-graceful-exit具有零停机时间的组件为 node.js 使用 Express 部署。 它是为 3. X, 开发的,因此它可能需要与 Express 2.x 兼容。这个模块最初是...

    socket常见错误代码解析.pdf

    Socket Error 10094是指优雅关闭正在进行中,这种错误通常是由于Socket关闭正在进行中所致。解决方法是等待Socket关闭完成。 Socket Error 11001 - Host not found Socket Error 11001是指主机不存在,这种错误...

    Functional Programming in C++

    Mastering the functional style of programming can help you tackle the demands of modern apps and will lead to simpler expression of complex program logic, graceful error handling, and elegant ...

    Scalable IO in Java.pdf

    在讨论可伸缩性目标时,文档强调了在增加负载时的优雅退化(Graceful degradation),即在用户数量增加时,系统能够合理地使用增加的资源(CPU、内存、磁盘、带宽)来提升性能。同时,系统应当满足可用性与性能目标...

    RFC8538 Notification Message Support for BGP Graceful Restart

    **BGP Graceful Restart**(优雅重启)是一种使边界网关协议(BGP)能够在路由器重启时保持部分连接状态的方法,从而减少对网络的影响。在**RFC4724**中定义了这一机制,但它限制了BGP优雅重启的应用范围,即不包括...

    javaAJAX无刷技术

    在实际开发中,为了兼容各种浏览器和提升用户体验,开发者通常会结合使用AJAX和其他技术,例如使用渐进增强(Progressive Enhancement)或优雅降级(Graceful Degradation)策略,确保即使在JavaScript禁用或不支持...

    前端开源库-graceful-fs-extra

    `graceful-fs-extra`是一个针对Node.js环境的`fs`模块扩展库,它在原生`fs`模块的基础上增加了一些实用功能,并提供了更优雅的错误处理机制,使得文件操作更加高效和稳定。本文将深入探讨`graceful-fs-extra`库的...

    Scalable IO in Java -Doug Lea

    在Scalable IO in Java中,Doug Lea首先介绍了网络服务的可扩展性目标,包括在增加负载(即更多客户端)的情况下优雅地降级(graceful degradation),以及在资源(如CPU、内存、磁盘和带宽)增加时持续改进性能。...

    BGP Graceful Restart

    【BGP Graceful Restart】是BGP(Border Gateway Protocol)的一种特性,旨在确保在路由器或协议处理组件(如Route Processor,RP)重启时,路由信息的连续性和稳定性,避免网络中断。这一特性允许BGP Speaker...

    go-graceful-restart-example, 具有优雅重启功能的服务器示例.zip

    go-graceful-restart-example, 具有优雅重启功能的服务器示例 服务器使用 Go graceful重新启动安装并运行服务器$ go get github.com/Scalingo/go-graceful-restart-example$ go-graceful-restart-examp

Global site tag (gtag.js) - Google Analytics