如何使用socket进行java网络编程(三)
本篇文章继续记录java网络通讯编程的学习。在本系列笔记的第一篇中曾经记录过一个项目中的程序,当时还处于项目早期,还未进入与第三方公司的联调阶段,笔者只是用java写了一个client程序模拟了一下第三方发送报文。 在client程序printer.println(datagram)后,server程序可以接收报文并能执行:解析->报文转换->转发前置机->接收前置机处理结果->报文转换 这样一个处理过程。但进入与第三方公司的联调后,server程序暴露了一些个问题。
(一)、
首先,上述 解析->报文转换->转发前置机->接收前置机处理结果->报文转换 实际上是不完整的,完整的应该是 解析->报文转换->转发前置机->接收前置机处理结果->报文转换->将结果写回原socket的输出流
原socket指的是第一步读取第三方报文时候的那个socket,这个socket在server.accept()的时候已经得到了,并且我们不需要知道他的地址和端口,只需把返回报文直接写进它的输出流就好了。 在第一篇笔记中结尾处的注释里笔者居然想新new一个带第三方地址和端口的socket发送返回报文,简直太土了。- -!
所以,补齐SocketHandler程序:
logger.info("开始向第三方返回结果报文...");
OutputStream os = this.socket.getOutputStream();
os.write(toThirdDatagram.getBytes());
os.close();
logger.info("向第三方返回结果报文结束");
或:
logger.info("开始向第三方返回结果报文...");
PrintWriter thirdPrinter = new PrintWriter (new OutputStreamWriter(this.socket.getOutputStream()));
thirdPrinter.println(toThirdDatagram);
thirdPrinter.flush();
thirdPrinter.close();
logger.info("向第三方返回结果报文结束");
由于我们处理的报文是文本形式,所以推荐使用reader/writer的第二种方法。
(二)、
另外,接下来才是本次联调测试发现的重要的问题:
对方通过其公司的通讯平台发送报文过来,我的server程序可以接收,但对方收不到我返回给他的报文。
在我的log4j日志上面没有发现有什么报错信息,但对方发送报文之后一直收不到我返回的报文,最后本地的通讯平台报了socket超时。而对方通过telnet发送同样的报文,又是可以正确接受到返回报文的。两种方法在我这边的日志上都是没有报错的。
两天的时间里,我们一直是觉得是我在发送返回报文的时候出了问题,或者是对方在接受我的返回报文时出了问题。但其实,问题出现在一开始我接收对方报文的时候。
很意外,因为我的日志里显示对方的报文我是正确的接收下来了的。
问题究竟出在哪呢?
BufferedReader reader = new BufferedReader(new InputStreamReader(
this.socket.getInputStream()));
datagram = reader.readLine();
logger.info("接收到第三方报文" + datagram);
问题就出在上面的红色代码处。readLine()方法是个阻塞方法,在收到换行或回车符或者socket超时之前,它会一直阻塞,而对方通过其通讯平台发给我的报文并不属于这样结尾,所以就发送了阻塞(对方通过telnet发送报文不会阻塞,因为敲了回车),等到socket超时之后,我的server程序继续向下执行,记录了“接收到第三方报文xxxxxx”这样的日志,并在最后向socket写入了返回报文,而此时socket对于对方来说早已经超时了,对方当然不会收到我的返回了!在超时之后记录到我的日志上的“成功”信息都是假象!
改正程序:
BufferedReader reader = new BufferedReader(new InputStreamReader(
this.socket.getInputStream()));
//datagram = reader.readLine();
char[] cbuf = new char[4000];
int l = reader.read(cbuf);
datagram = new String(cbuf,0,l);
logger.info("接收到第三方报文" + datagram);
read(cbuf)方法可以正确的识别一次数据包的结束并将接受到的报文存放在cbuf进行缓冲。不会造成阻塞。
至此,程序继续向下执行,并正确的返回报文给了第三方。
修正后的SocketHandler如下:
package com.zjjs.server; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.CharBuffer; import org.apache.log4j.Logger; import com.zjjs.trans.Trans;
import com.zjjs.trans.ZjjsTrans;
import com.zjjs.util.ConfigUtil;
/**
* create by linyang 2015-09-18
* 套接字请求处理线程
* 完成 接收第三方--->发送前置机--->接收前置机--->发送第三方 等步奏
* **/
public class SocketHandler implements Runnable {
/*前置机服务的地址和端口*/
private static final String _adress_ = ConfigUtil.getConfigValue("front_address");
private static final int _port_ = Integer.parseInt(ConfigUtil.getConfigValue("front_port")); private Socket socket = null;
private Socket frontSocket = null;
private static Logger logger = Logger.getLogger(SocketHandler.class); public SocketHandler(Socket socket) {
super();
this.socket = socket;
} @Override
public void run() {
String datagram = null;
String send2frontDatagram = null;
ZjjsTrans transProcesser = null;
String transCode = "";
Trans trans = null;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
this.socket.getInputStream()));
//datagram = reader.readLine();
char[] cbuf = new char[4000];
int l = reader.read(cbuf);
datagram = new String(cbuf,0,l); logger.info("接收到第三方报文" + datagram);
transCode = datagram.substring(4, 8);
try {
try {
trans = (Trans) Class.forName("com.zjjs.trans.Trans" + transCode)
.newInstance();
} catch (InstantiationException inse) {
inse.printStackTrace();
logger.error("实例化交易失败,交易类型" + "Trans" + transCode + ", "
+ inse);
} catch (IllegalAccessException ille) {
ille.printStackTrace();
logger.error("实例化交易失败IllegalAccessException,交易类型" + "Trans"
+ transCode + ", " + ille);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
logger.error("未找到交易类型" + "com.zjjs.trans.Trans" + transCode + ", " + e);
}
transProcesser = new ZjjsTrans(trans);
logger.info("解析收到第三方报文开始...");
transProcesser.parserDatagramReceive(datagram);
logger.info("解析收到第三方报文结束");
logger.info("开始生成转发到前置机报文...");
send2frontDatagram = transProcesser.toFrontServerDatagram();
logger.info("结束生成转发到前置机报文,报文内容[" + send2frontDatagram + "]");
frontSocket = new Socket(_adress_, _port_);
logger.info("开始向前置机发送报文...");
PrintWriter printer = new PrintWriter (new OutputStreamWriter(frontSocket.getOutputStream()));
/*
OutputStreamWriter writer = new OutputStreamWriter(
frontSocket.getOutputStream()); */
printer.println(send2frontDatagram);
printer.flush();
//printer.close(); //这里close()的话会把socket本身也close掉 logger.info("向前置机发送报文结束"); logger.info("开始接收前置机返回报文...");
BufferedReader frontreader = new BufferedReader(new InputStreamReader(frontSocket.getInputStream()));
String frontReturnDatagram = frontreader.readLine();
printer.close();
frontreader.close();
logger.info("前置机返回报文[" + frontReturnDatagram + "]");
logger.info("接收前置机返回报文结束"); logger.info("开始将前置机返回报文转换为返回给第三方报文...");
String toThirdDatagram = transProcesser.toThirdServerDatagram(frontReturnDatagram);
logger.info("转换后返回第三方报文[" + toThirdDatagram + "]");
logger.info("将前置机返回报文转换为返回给第三方报文结束"); logger.info("开始向第三方返回结果报文...");
PrintWriter thirdPrinter = new PrintWriter (new OutputStreamWriter(this.socket.getOutputStream()));
thirdPrinter.println(toThirdDatagram);
thirdPrinter.flush();
thirdPrinter.close();
reader.close();
logger.info("向第三方返回结果报文结束"); } catch (IOException ioe) {
logger.error("I/O error:" + ioe);
}finally{
try {
if(this.socket!=null)
this.socket.close();
} catch (IOException e) {}
}
} }
(三)、
那么改正后的程序就完备了吗?
没有。 通过跟同事交流,至少在以下几个地方,程序还有改进的空间。
1、对于每个第三方请求都启动一个线程进行处理这种方式,性能上不会太好,使用线程池可以改进。
2、在reader.read(cbuf)进行读取对方发送报文的地方,其实是只读取一个数据包。而tcp协议对于数据量比较大的报文是分包进行发送的。这就要求我们编写的应用层程序在应用层协议这个层面上自行判断报文的结束点。对多个分包进行循环读取。这才是上述程序的最大隐患。
2017-8-15补充: 1、线程池可以使用apache commons pool来实现,通用的对象池。可以用来封装数据库连接、笔者之前也用来封装过kafka的connect池化。
当然也能用来封装线程。
2、关于第2点,其实之前也提到过 “在应用层协议这个层面上自行判断报文的结束点”,这个其实只要设计好报文格式,
在报文头里说明本次通信所发送的报文体长度就可以了。
3、其实原程序严格说还有一个隐患,只不过在当时应该是预估好报文长度不会超过4000,所以不会出问题。 对于不知道长度范围的情况,应该是循环读取,
int c;
while( (c = reader.read(cbuf, 0, cbuf.length)) != -1 ){
}
BufferedReader reader = new BufferedReader(new InputStreamReader(
this.socket.getInputStream()));
//datagram = reader.readLine();
char[] cbuf = new char[4000];
int l = reader.read(cbuf);
datagram = new String(cbuf,0,l);
logger.info("接收到第三方报文" + datagram);
如何使用socket进行java网络编程(三)的更多相关文章
- 如何使用socket进行java网络编程(二)
通过在如何使用socket进行java网络编程(一)中程序的编写,可以总结出一些常用的java socket编程的范例来. ServerSocket server = new ServerSocket ...
- 如何使用socket进行java网络编程(四)
在上一篇的结尾,提到过用来处理每一个服务端accept到的socket,我们由原来最开始的单线程改成了多线程去处理,但是对每一个接收到的socket都new一个thread去处理,这样效率太低,我们需 ...
- Java网络编程三--基于TCP协议的网络编程
ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状体 Socket accept():如果接收到客户端的连接请求,该方法返回一个与客户端对应Socket ...
- 如何使用socket进行java网络编程(五)
本篇记录: 1.再谈readLine()方法 2.什么是真正的长连接 最近又参与了一个socket的项目,又遇到了老生常谈的readLine()问题:对方通过其vb程序向我方socketServer程 ...
- 如何使用socket进行java网络编程(一)
笔者进来遇到一个项目,一家公司的系统需要在完成自身业务逻辑的同时,接入到某银行的核心系统(这里准确说应该是前置机)进行一系列的账务处理,然后再将账务处理结果返回给该公司系统. 网络通信采用TCP协议. ...
- java网络编程三次握手四次挥手
第一次握手:client设置syn=1,随机产生一个序列号seq=x,将数据包发送到server.client进入syn_send状态, 等待server确认. 第二次握手:server查看clien ...
- java 网络编程复习(转)
好久没有看过Java网络编程了,现在刚好公司有机会接触,顺便的拾起以前的东西 参照原博客:http://www.cnblogs.com/linzheng/archive/2011/01/23/1942 ...
- Java 网络编程(转)
一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...
- 关于Java网络编程
一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...
随机推荐
- jQuery之双下拉框
双下拉框要实现的效果,实际上就是左边下拉选择框里的内容,可以添加到右边,而右边同理.写了个简单的例子,来说明一下. 代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...
- 匹配数字、字母和?%&=-_这几个符号的正则表达式
/^[\w\?%&=\-_]+$/ 说明:(1) \w 代表 0-9a-zA-Z 即数字.字母 (2) \?%&=\-_ 匹配?%&=-_,而正则中?代表0个或1个,因为是特殊 ...
- 优秀UX设计师的八条黄金法则
与用户保持亲密 成为成功的UX设计师最重要的先决条件之一就是与用户保持紧密的联系,以发现和了解他们的需求和爱好.理想情况下你应该让自己完全地成为产品用户,因为只有这样你才能理解背后的动机.“这样的 ...
- part1:1-embeded学习心态
遇到问题,要冷静分析问题,采用排除法,个个排除查找问题之所在!切记!在没分析完自己问题之前,别把问题所在指向他人!
- 《完全版线段树》——notonlysuccess
转载自:NotOnlySuccess的博客 [完全版]线段树 很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku打广告,但是现在我自己都不太好意思去看那篇文 ...
- mysql的UseAffectedRows问题 以及其他常见配置说明
遇到MySQL中on duplicate key update语句返回值不正确: 在server5.1.*的返回分别为insert=1,update=3,nochange=2 在server5.5.* ...
- hdu-1728(bfs+优化)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1728 注意:1.先输入起点(y1,x1)和终点(y2,x2): 2.如果一个一个遍历会超时. 思路:每 ...
- s111 stark组件
内容回顾: 1. 类当做key 2. django中的model所在app名称.以及小写类名. def index(request): # print(m1.UserInfo,m1.UserInfo. ...
- 201709012工作日记--Android消息机制
1. android的消息机制——Handler机制 参考:http://www.jianshu.com/p/9e4d1fab0f36. Android异步消息处理机制完全解析,带你从源码的角度理解: ...
- (转)C#静态方法使用经验浅谈
转自:http://developer.51cto.com/art/200908/147734.htm C#静态方法有什么弊端? 我们在实际的开发过程中会注意到C#静态方法对于我们程序的影响,那么有哪 ...