package com.jadyer.util;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.InetSocketAddress;import java.net.Socket;import java.util.HashMap;import java.util.Map;/** * 使用JavaSocket編寫發送TCP請求的工具類 * @see 類似的有一個Mina工具類:http://blog.csdn.net/jadyer/article/details/8088068 * @see 欲查詢Scoket的有關屬性:http://blog.csdn.net/jadyer/article/details/8788272 * @create Apr 5, 2013 9:25:34 PM * @author 玄玉<http://blog.csdn.net/jadyer> */public class TCPUtil {private TCPUtil(){}/** * 發送TCP請求 * @see 本方法預設的連線逾時和讀取逾時均為30秒 * @see 編碼與解碼請求響應位元組時,均採用雙方約定的字元集,即本方法的第四個參數reqCharset * @param IP 遠程主機地址 * @param port 遠程主機連接埠 * @param reqData 待發送報文的中文字串形式 * @param reqCharset 該方法與遠程主機間通訊報文的編碼字元集(編碼為byte[]發送到Server) * @return localPort--本地綁定的連接埠,reqData--請求報文,respData--響應報文,respDataHex--遠程主機響應的原始位元組的十六進位表示 */public static Map<String, String> sendTCPRequest(String IP, String port, String reqData, String reqCharset){Map<String, String> respMap = new HashMap<String, String>();OutputStream out = null; //寫InputStream in = null; //讀String localPort = null; //本地綁定的連接埠(java socket, client, /127.0.0.1:50804 => /127.0.0.1:9901)String respData = null; //響應報文String respDataHex = null; //遠程主機響應的原始位元組的十六進位表示Socket socket = new Socket(); //客戶機try {socket.setTcpNoDelay(true);socket.setReuseAddress(true);socket.setSoTimeout(30000);socket.setSoLinger(true, 5);socket.setSendBufferSize(1024);socket.setReceiveBufferSize(1024);socket.setKeepAlive(true);socket.connect(new InetSocketAddress(IP, Integer.parseInt(port)), 30000);localPort = String.valueOf(socket.getLocalPort());/** * 發送TCP請求 */out = socket.getOutputStream();out.write(reqData.getBytes(reqCharset));/** * 接收TCP響應 */in = socket.getInputStream();ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();byte[] buffer = new byte[512];int len = -1;while((len=in.read(buffer)) != -1){bytesOut.write(buffer, 0, len);}/** * 解碼TCP響應的完整報文 */respData = bytesOut.toString(reqCharset);respDataHex = formatToHexStringWithASCII(bytesOut.toByteArray());} catch (Exception e) {System.out.println("與[" + IP + ":" + port + "]通訊遇到異常,堆棧資訊如下");e.printStackTrace();} finally {if (null!=socket && socket.isConnected() && !socket.isClosed()) {try {socket.close();} catch (IOException e) {System.out.println("關閉客戶機Socket時發生異常,堆棧資訊如下");e.printStackTrace();}}}respMap.put("localPort", localPort);respMap.put("reqData", reqData);respMap.put("respData", respData);respMap.put("respDataHex", respDataHex);return respMap;}/** * 通過ASCII碼將十進位的位元組數組格式化為十六進位字串 * @see 該方法會將位元組數組中的所有位元組均格式化為字串 * @see 使用說明詳見<code>formatToHexStringWithASCII(byte[], int, int)</code>方法 */private static String formatToHexStringWithASCII(byte[] data){return formatToHexStringWithASCII(data, 0, data.length);}/** * 通過ASCII碼將十進位的位元組數組格式化為十六進位字串 * @see 該方法常用於字串的十六進位列印,列印時左側為十六進位數值,右側為對應的字串原文 * @see 在構造右側的字串原文時,該方法內部使用的是平台的預設字元集,來解碼byte[]數組 * @see 該方法在將位元組轉為十六進位時,預設使用的是<code>java.util.Locale.getDefault()</code> * @see 詳見String.format(String, Object...)方法和new String(byte[], int, int)構造方法 * @param data 十進位的位元組數組 * @param offset 數組下標,標記從數組的第幾個位元組開始格式化輸出 * @param length 格式長度,其不得大於數組長度,否則拋出java.lang.ArrayIndexOutOfBoundsException * @return 格式化後的十六進位字串 */private static String formatToHexStringWithASCII(byte[] data, int offset, int length){int end = offset + length;StringBuilder sb = new StringBuilder();StringBuilder sb2 = new StringBuilder();sb.append("\r\n------------------------------------------------------------------------");boolean chineseCutFlag = false;for(int i=offset; i<end; i+=16){sb.append(String.format("\r\n%04X: ", i-offset)); //X或x表示將結果格式化為十六進位整數sb2.setLength(0);for(int j=i; j<i+16; j++){if(j < end){byte b = data[j];if(b >= 0){ //ENG ASCIIsb.append(String.format("%02X ", b));if(b<32 || b>126){ //不可見字元sb2.append(" ");}else{sb2.append((char)b);}}else{ //CHA ASCIIif(j == i+15){ //漢字前半個位元組sb.append(String.format("%02X ", data[j]));chineseCutFlag = true;String s = new String(data, j, 2);sb2.append(s);}else if(j == i&&chineseCutFlag){ //後半個位元組sb.append(String.format("%02X ", data[j]));chineseCutFlag = false;String s = new String(data, j, 1);sb2.append(s);}else{sb.append(String.format("%02X %02X ", data[j], data[j + 1]));String s = new String(data, j, 2);sb2.append(s);j++;}}}else{sb.append(" ");}}sb.append("| ");sb.append(sb2.toString());}sb.append("\r\n------------------------------------------------------------------------");return sb.toString();}}
下面是測試代碼
public static void main(String[] args) {String reqData = "0003721000510110199201209222240000020120922000069347814303000700000813``中國聯通交費儲值`為號碼18655228826交費儲值100.00元`UDP1209222238312219411`10000```20120922`chinaunicom-payFeeOnline`UTF-8`20120922223831`MD5`20120922020103806276`1`02`10000`20120922223954`20120922`BOCO_B2C```http://192.168.20.2:5545/ecpay/pay/elecChnlFrontPayRspBackAction.action`1`立即支付,交易成功`";String IP = "127.0.0.1";String port = "9901";String reqCharset = "GB18030";Map<String, String> respMap = sendTCPRequest(IP, port, reqData, reqCharset);System.out.println("=============================================================================");System.out.println("請求報文如下");System.out.println(respMap.get("reqData"));System.out.println("=============================================================================");System.out.println("響應報文如下");System.out.println(respMap.get("respData"));System.out.println("=============================================================================");System.out.println("響應十六進位如下");System.out.println(respMap.get("respDataHex"));System.out.println("=============================================================================");}
下面是控制台輸出
//控制台輸出如下//=============================================================================//請求報文如下//0003721000510110199201209222240000020120922000069347814303000700000813``中國聯通交費儲值`為號碼18655228826交費儲值100.00元`UDP1209222238312219411`10000```20120922`chinaunicom-payFeeOnline`UTF-8`20120922223831`MD5`20120922020103806276`1`02`10000`20120922223954`20120922`BOCO_B2C```http://192.168.20.2:5545/ecpay/pay/elecChnlFrontPayRspBackAction.action`1`立即支付,交易成功`//=============================================================================//響應報文如下//00015800000000訂單結果通知:商戶系統未成功接收到通知,需要繼續通知 6717356649614827173120130405201628201304051`//=============================================================================//響應十六進位如下////------------------------------------------------------------------------//0000: 30 30 30 31 35 38 30 30 30 30 30 30 30 30 B6 A9 | 00015800000000訂//0010: B5 A5 BD E1 B9 FB CD A8 D6 AA 3A C9 CC BB A7 CF | 單結果通知:商戶系//0020: B5 CD B3 CE B4 B3 C9 B9 A6 BD D3 CA D5 B5 BD CD | ?統未成功接收到通//0030: A8 D6 AA 2C D0 E8 D2 AA BC CC D0 F8 CD A8 D6 AA | ?知,需要繼續通知//0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | //0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | //0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | //0070: 00 00 36 37 31 37 33 35 36 36 34 39 36 31 34 38 | 67173566496148//0080: 32 37 31 37 33 31 32 30 31 33 30 34 30 35 32 30 | 2717312013040520//0090: 31 36 32 38 32 30 31 33 30 34 30 35 31 60 | 1628201304051`//------------------------------------------------------------------------//=============================================================================//