如何使用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地址可 ...
随机推荐
- [leetcode]560. Subarray Sum Equals K 和为K的子数组
Given an array of integers and an integer k, you need to find the total number of continuous subarra ...
- Bootstrap模态框使用WebUploader点击失效问题解决
解决 方法一 在上传按钮上监听一个点击事件,如create(),在该函数中重新生成上传按钮 function create(){ uploader.addButton({ id: '#filePick ...
- Spring框架的AOP技术(注解方式)
1. 步骤一:创建JavaWEB项目,引入具体的开发的jar包 * 先引入Spring框架开发的基本开发包 * 再引入Spring框架的AOP的开发包 * spring的传统AOP的开发的包 * sp ...
- SSL - 简介
一.密码技术 要了解SSL协议,首先要了解:加密算法.消息摘要算法(又称为哈希算法Hash),数字签名等概念.这些技术每个都可以写出一整本的书,它们结合在一起,提供了保密性.完整性和身份验证的功能. ...
- idea开发工具下报Set language level to 6-@Override in interfaces的解决方法
idea开发工具下报Set language level to 6-@Override in interfaces的解决方法 实现接口时报如下错误:Set language level to 6-@O ...
- 23. Man and His Natural Habitat 人类及其自然栖息地
. Man and His Natural Habitat 人类及其自然栖息地 ① Ecology is that branch of science which concerns itself wi ...
- Django模型层(2)
https://www.cnblogs.com/yuanchenqi/articles/8963244.html from django.db import models class Author(m ...
- Python网络编程总结
----learn from luffycity---- 1. 什么是C/S架构? C指的是client(客户端软件),S指的是Server(服务端软件),C/S架构就是基于网络实现客户端与服务端通信 ...
- (并查集 贪心思想)Supermarket -- POJ --1456
链接: http://poj.org/problem?id=1456 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=82830#probl ...
- poj 2591 Set Definition
题目 这道题是个水题,写博客上的人好像都不屑于写这这道题.但是我为什么还是觉得挺难的呢? 我也是看了别人的博客才过了的,感觉这种写法很新颖,这样就可以不用在排序了. 下面给出正解: #include& ...