完整版见https://jadyer.github.io/2013/07/12/mina-tcp-http-server/
这是一个JavaProject
首先是服务启动类MainApp.java
package com.jadyer.core;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
/**
* 服务启动类
* @see 服务启动后(即执行这里的main方法),可用浏览器访问下面两个地址
* @see http://127.0.0.1:8000/ (浏览器会显示这几个字:欢迎访问由Mina2.0.7编写的Web服务器)
* @see http://127.0.0.1:8000/login(浏览器会显示这几个字:登录成功)
* @see 另外这里并未配置backlog,那么它会采用操作系统默认的连接请求队列长度50
* @see 详见org.apache.mina.core.polling.AbstractPollingIoAcceptor类源码的96行
* @create Jul 7, 2013 1:28:04 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class MainApp {
public static void main(String[] args) throws IOException {
NioSocketAcceptor acceptor = new NioSocketAcceptor();
acceptor.setBacklog(0);
acceptor.setReuseAddress(true);
acceptor.getSessionConfig().setWriteTimeout(10000);
acceptor.getSessionConfig().setBothIdleTime(90);
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ServerProtocolCodecFactory()));
acceptor.getFilterChain().addLast("executor", new ExecutorFilter());
acceptor.setHandler(new ServerHandler());
//服务端绑定两个端口,8000用于接收并处理HTTP请求,9901用于接收并处理TCP请求
List<SocketAddress> socketAddresses = new ArrayList<SocketAddress>();
socketAddresses.add(new InetSocketAddress(8000));
socketAddresses.add(new InetSocketAddress(9901));
acceptor.bind(socketAddresses);
//判断服务端启动与否
if(acceptor.isActive()){
System.out.println("写 超 时: 10000ms");
System.out.println("发呆配置: Both Idle 90s");
System.out.println("端口重用: true");
System.out.println("服务端初始化完成......");
System.out.println("服务已启动....开始监听...." + acceptor.getLocalAddresses());
}else{
System.out.println("服务端初始化失败......");
}
}
}
下面是业务分发类ServerHandler.java(也是Mina中的Handler)
package com.jadyer.core;
import java.net.HttpURLConnection;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import com.jadyer.util.JadyerUtil;
/**
* 业务分发类
* @see 本例中目前只接收两种请求
* @see TCP请求的固定业务编码为10005,HTTP请求的固定业务编码为/login(http://127.0.0.1:8000/login)
* @see TCP报文格式为前6个字节表示报文整体长度(长度不足6位时左补零),第7位开始代表业务编码(固定长度为5),第12位开始是业务数据
* @create Jul 7, 2013 2:24:45 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class ServerHandler extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
String respData = null;
Token token = (Token)message;
/*
* 打印收到的原始报文
*/
System.out.println("渠道:" + token.getBusiType() + " 交易码:" + token.getBusiCode() +" 完整报文(HEX):"
+ JadyerUtil.buildHexStringWithASCII(JadyerUtil.getBytes(token.getFullMessage(), "UTF-8")));
StringBuilder sb = new StringBuilder();
sb.append("\r\n------------------------------------------------------------------------------------------");
sb.append("\r\n【通信双方】").append(session);
sb.append("\r\n【收发标识】Receive");
sb.append("\r\n【报文内容】").append(token.getFullMessage());
sb.append("\r\n------------------------------------------------------------------------------------------");
System.out.println(sb.toString());
/*
* 根据请求的业务编码做不同的处理
*/
if(token.getBusiCode().equals("/")){
respData = this.buildHTTPResponseMessage("<h2>欢迎访问由Mina2.0.7编写的Web服务器</h2>");
}else if(token.getBusiCode().equals("/favicon.ico")){
respData = this.buildHTTPResponseMessage("<link rel=\"icon\" href=\"https://epay.10010.com/per/favicon.ico\""
+ "type=\"image/x-icon\"/>\n<link rel=\"shortcut icon\" href=\"http"
+ "s://epay.10010.com/per/favicon.ico\" type=\"image/x-icon\"/>");
}else if(token.getBusiCode().equals("/login")){
System.out.println("收到请求参数=[" + token.getBusiMessage() + "]");
respData = this.buildHTTPResponseMessage("登录成功");
}else if(token.getBusiCode().equals("10005")){
System.out.println("收到请求参数=[" + token.getBusiMessage() + "]");
respData = "00003099999999`20130707144028`";
}else{
if(token.getBusiType().equals(Token.BUSI_TYPE_TCP)){
respData = "ILLEGAL_REQUEST";
}else if(token.getBusiType().equals(Token.BUSI_TYPE_HTTP)){
respData = this.buildHTTPResponseMessage(501, null);
}
}
/*
* 打印应答报文
*/
sb.setLength(0);
sb.append("\r\n------------------------------------------------------------------------------------------");
sb.append("\r\n【通信双方】").append(session);
sb.append("\r\n【收发标识】Response");
sb.append("\r\n【报文内容】").append(respData);
sb.append("\r\n------------------------------------------------------------------------------------------");
System.out.println(sb.toString());
session.write(respData);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
System.out.println("已回应给Client");
if(session != null){
session.close(true);
}
}
@Override
public void sessionIdle(IoSession session, IdleStatus status){
System.out.println("请求进入闲置状态....回路即将关闭....");
session.close(true);
}
@Override
public void exceptionCaught(IoSession session, Throwable cause){
System.out.println("请求处理遇到异常....回路即将关闭....");
cause.printStackTrace();
session.close(true);
}
/**
* 构建HTTP响应报文
* @see 该方法默认构建的是HTTP响应码为200的响应报文
* @param httpResponseMessageBody HTTP响应报文体
* @return 包含了HTTP响应报文头和报文体的完整报文
*/
private String buildHTTPResponseMessage(String httpResponseMessageBody){
return buildHTTPResponseMessage(HttpURLConnection.HTTP_OK, httpResponseMessageBody);
}
/**
* 构建HTTP响应报文
* @see 200--请求已成功,请求所希望的响应头或数据体将随此响应返回..即服务器已成功处理了请求
* @see 400--由于包含语法错误,当前请求无法被服务器理解..除非进行修改,否则客户端不应该重复提交这个请求..即错误请求
* @see 500--服务器遇到了一个未曾预料的状况,导致其无法完成对请求的处理..一般来说,该问题都会在服务器的程序码出错时出现..即服务器内部错误
* @see 501--服务器不支持当前请求所需要的某个功能..当服务器无法识别请求的方法,且无法支持其对任何资源的请求时,可能返回此代码..即尚未实施
* @param httpResponseCode HTTP响应码
* @param httpResponseMessageBody HTTP响应报文体
* @return 包含了HTTP响应报文头和报文体的完整报文
*/
private String buildHTTPResponseMessage(int httpResponseCode, String httpResponseMessageBody){
if(httpResponseCode == HttpURLConnection.HTTP_OK){
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: ");
sb.append(JadyerUtil.getBytes(httpResponseMessageBody, "UTF-8").length);
sb.append("\r\n\r\n");
sb.append(httpResponseMessageBody);
return sb.toString();
}
if(httpResponseCode == HttpURLConnection.HTTP_BAD_REQUEST){
return "HTTP/1.1 400 Bad Request";
}
if(httpResponseCode == HttpURLConnection.HTTP_INTERNAL_ERROR){
return "HTTP/1.1 500 Internal Server Error";
}
return "HTTP/1.1 501 Not Implemented";
}
}
下面是用来封装客户端请求报文的实体类Token.java
package com.jadyer.core;
/**
* 封装客户端请求报文
* @create Jul 7, 2013 1:42:57 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class Token {
public static final String BUSI_TYPE_TCP = "TCP";
public static final String BUSI_TYPE_HTTP = "HTTP";
public String busiCode; //业务码
public String busiType; //业务类型:TCP or HTTP
public String busiMessage; //业务报文:TCP请求时为TCP完整报文,HTTP_POST请求时为报文体部分,HTTP_GET时为报文头第一行参数部分
public String busiCharset; //报文字符集
public String fullMessage; //原始完整报文(用于在日志中打印最初接收到的原始完整报文)
/*-- 五个属性的setter和getter略 --*/
}
下面是用于组装服务端的编解码器的工厂类ServerProtocolCodecFactory.java
package com.jadyer.core;
import org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory;
/**
* 组装服务端的编解码器的工厂
* @see 暂不提供客户端编解码器(其实它与服务端的编解码器差不多差不多)
* @see ====================================================================================
* @see 其内部维护了一个MessageDecoder数组,用于保存添加的所有解码器
* @see 每次decode()的时候就调用每个MessageDecoder的decodable()逐个检查
* @see 只要发现一个MessageDecoder不是对应的解码器,就从数组中移除,知道找到合适的MessageDecoder
* @see 如果最后发现数组为空,就表示没有找到对应的MessageDecoder,最后抛出异常
* @see ====================================================================================
* @create Jul 7, 2013 1:41:10 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class ServerProtocolCodecFactory extends DemuxingProtocolCodecFactory {
public ServerProtocolCodecFactory(){
super.addMessageEncoder(String.class, ServerProtocolEncoder.class);
super.addMessageDecoder(ServerProtocolTCPDecoder.class);
super.addMessageDecoder(ServerProtocolHTTPDecoder.class);
}
}
下面是服务端用到的协议编码器ServerProtocolEncoder.java
package com.jadyer.core;
import java.nio.charset.Charset;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
/**
* Server端协议编码器
* @see 用于编码响应给Client的报文(报文编码一律采用UTF-8)
* @create Jul 7, 2013 1:43:12 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class ServerProtocolEncoder implements MessageEncoder<String> {
@Override
public void encode(IoSession session, String message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
buffer.putString(message, Charset.forName("UTF-8").newEncoder());
buffer.flip();
out.write(buffer);
}
}
重点来了:下面是服务端的TCP协议解码器ServerProtocolTCPDecoder.java
package com.jadyer.core;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import com.jadyer.util.JadyerUtil;
/**
* Server端TCP协议解码器
* @see 用于解码接收到的TCP请求报文(报文编码一律采用UTF-8)
* @see 当收到数据包时,程序首先会执行decodable()方法,通过读取数据判断当前数据包是否可进行decode
* @see 当decodable()方法返回MessageDecoderResult.OK时,接着会调用decode()方法,正式decode数据包
* @see 在decode()方法进行读取操作会影响数据包的大小,decode需要判断协议中哪些已经decode完,哪些还没decode
* @see decode完成后,通过ProtocolDecoderOutput.write()输出,并返回MessageDecoderResult.OK表示decode完毕
* @create Jul 7, 2013 1:44:53 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class ServerProtocolTCPDecoder implements MessageDecoder {
/**
* 该方法相当于预读取,用于判断是否是可用的解码器,这里对IoBuffer读取不会影响数据包的大小
* 该方法结束后IoBuffer会复原,所以不必担心调用该方法时,position已经不在缓冲区起始位置
*/
@Override
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
//TCP报文格式约定为前6个字节表示报文整体长度,长度不足6位时左补零,第7位开始代表业务编码,业务编码固定长度为5,第12位开始是业务数据
if(in.remaining() < 6){
return MessageDecoderResult.NEED_DATA;
}
//服务端启动时已绑定9901端口,专门用来处理TCP请求的
if(session.getLocalAddress().toString().contains(":9901")){
byte[] messageLength = new byte[6];
in.get(messageLength);
if(in.limit() >= Integer.parseInt(JadyerUtil.getString(messageLength, "UTF-8"))){
return MessageDecoderResult.OK;
}else{
return MessageDecoderResult.NEED_DATA;
}
}else{
return MessageDecoderResult.NOT_OK;
}
}
@Override
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
byte[] message = new byte[in.limit()];
in.get(message);
String fullMessage = JadyerUtil.getString(message, "UTF-8");
Token token = new Token();
token.setBusiCharset("UTF-8");
token.setBusiType(Token.BUSI_TYPE_TCP);
token.setBusiCode(fullMessage.substring(6, 11));
token.setBusiMessage(fullMessage);
token.setFullMessage(fullMessage);
out.write(token);
return MessageDecoderResult.OK;
}
@Override
public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
//暂时什么都不做
}
}
下面是服务端的HTTP协议解码器ServerProtocolHTTPDecoder.java
package com.jadyer.core;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import com.jadyer.util.JadyerUtil;
/**
* Server端HTTP协议解码器
* @see 用于解码接收到的HTTP请求报文(报文编码一律采用UTF-8)
* @create Jul 7, 2013 1:44:20 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class ServerProtocolHTTPDecoder implements MessageDecoder {
@Override
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
if(in.remaining() < 5){
return MessageDecoderResult.NEED_DATA;
}
//服务端启动时已绑定8000端口,专门用来处理HTTP请求的
if(session.getLocalAddress().toString().contains(":8000")){
return this.isComplete(in) ? MessageDecoderResult.OK : MessageDecoderResult.NEED_DATA;
}else{
return MessageDecoderResult.NOT_OK;
}
}
@Override
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
byte[] message = new byte[in.limit()];
in.get(message);
String fullMessage = JadyerUtil.getString(message, "UTF-8");
Token token = new Token();
token.setBusiCharset("UTF-8");
token.setBusiType(Token.BUSI_TYPE_HTTP);
token.setFullMessage(fullMessage);
if(fullMessage.startsWith("GET")){
if(fullMessage.startsWith("GET / HTTP/1.1")){
token.setBusiCode("/");
}else if(fullMessage.startsWith("GET /favicon.ico HTTP/1.1")){
token.setBusiCode("/favicon.ico");
}else{
//GET /login?aa=bb&cc=dd&ee=ff HTTP/1.1
if(fullMessage.substring(4, fullMessage.indexOf("\r\n")).contains("?")){
token.setBusiCode(fullMessage.substring(4, fullMessage.indexOf("?")));
token.setBusiMessage(fullMessage.substring(fullMessage.indexOf("?")+1, fullMessage.indexOf("HTTP/1.1")-1));
//GET /login HTTP/1.1
}else{
token.setBusiCode(fullMessage.substring(4, fullMessage.indexOf("HTTP")-1));
}
}
}else if(fullMessage.startsWith("POST")){
//先获取到请求报文头中的Content-Length
int contentLength = 0;
if(fullMessage.contains("Content-Length:")){
String msgLenFlag = fullMessage.substring(fullMessage.indexOf("Content-Length:") + 15);
if(msgLenFlag.contains("\r\n")){
contentLength = Integer.parseInt(msgLenFlag.substring(0, msgLenFlag.indexOf("\r\n")).trim());
if(contentLength > 0){
token.setBusiMessage(fullMessage.split("\r\n\r\n")[1]);
}
}
}
//POST /login?aa=bb&cc=dd&ee=ff HTTP/1.1
//特别说明一下:此时报文体本应该是空的,即Content-Length=0,但不能排除对方偏偏在报文体中也传了参数
//特别说明一下:所以这里的处理手段是busiMessage=请求URL中的参数串 + "`" + 报文体中的参数串(如果存在报文体的话)
if(fullMessage.substring(5, fullMessage.indexOf("\r\n")).contains("?")){
token.setBusiCode(fullMessage.substring(5, fullMessage.indexOf("?")));
String urlParam = fullMessage.substring(fullMessage.indexOf("?")+1, fullMessage.indexOf("HTTP/1.1")-1);
if(contentLength > 0){
token.setBusiMessage(urlParam + "`" + fullMessage.split("\r\n\r\n")[1]);
}else{
token.setBusiMessage(urlParam);
}
//POST /login HTTP/1.1
}else{
token.setBusiCode(fullMessage.substring(5, fullMessage.indexOf("HTTP/1.1")-1));
}
}
out.write(token);
return MessageDecoderResult.OK;
}
@Override
public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
//暂时什么都不做
}
/**
* 校验HTTP请求报文是否已完整接收
* @see 目前仅授理GET和POST请求
* @see ====================================================================================
* @see GET /notify_yeepay?p1_MerId=11&r0_Cmd=Buy&r1_Code=1&r2_TrxId=22 HTTP/1.1^M
* @see Content-Type: application/x-www-form-urlencoded; charset=GBK^M
* @see Cache-Control: no-cache^M
* @see Pragma: no-cache^M
* @see User-Agent: Java/1.5.0_14^M
* @see Host: 123.125.97.248^M
* @see Accept: text/html, image/gif, image/jpeg, *; q=.2, 星号/*; q=.2^M
* @see Connection: keep-alive^M
* @see ^M
* @see ====================================================================================
* @see POST /tra/trade/noCardNoPassword.htm HTTP/1.1^M
* @see Content-Type: application/x-www-form-urlencoded;charset=GB18030^M
* @see Cache-Control: no-cache^M
* @see Pragma: no-cache^M
* @see User-Agent: Java/1.6.0_24^M
* @see Host: 192.168.20.1^M
* @see Accept: text/html, image/gif, image/jpeg, *; q=.2, 星号/*; q=.2^M
* @see Connection: keep-alive^M
* @see Content-Length: 541^M
* @see ^M
* @see cooBankNo=CMBC_CREDIT&signType=MD5&amount=499900&orderValidityNum=15&CVVNo=255
* @see ====================================================================================
* @see 至于上面所列的GET和POST请求原始报文中为何会出现^M
* @see 我的博客上有详细说明http://blog.csdn.net/jadyer/article/details/8212067
* @see ====================================================================================
* @param in 装载HTTP请求报文的IoBuffer
*/
private boolean isComplete(IoBuffer in){
/*
* 先获取HTTP请求的原始报文
*/
byte[] messages = new byte[in.limit()];
in.get(messages);
String message = JadyerUtil.getString(messages, "UTF-8");
/*
* 授理GET请求
*/
if(message.startsWith("GET")){
return message.endsWith("\r\n\r\n");
}
/*
* 授理POST请求
*/
if(message.startsWith("POST")){
if(message.contains("Content-Length:")){
//取Content-Length后的字符串
String msgLenFlag = message.substring(message.indexOf("Content-Length:") + 15);
if(msgLenFlag.contains("\r\n")){
//取Content-Length值
int contentLength = Integer.parseInt(msgLenFlag.substring(0, msgLenFlag.indexOf("\r\n")).trim());
if(contentLength == 0){
return true;
}else if(contentLength > 0){
//取HTTP_POST请求报文体
String messageBody = message.split("\r\n\r\n")[1];
if(contentLength == JadyerUtil.getBytes(messageBody, "UTF-8").length){
return true;
}
}
}
}
}
/*
* 仅授理GET和POST请求
*/
return false;
}
}
最后再贴一下这里用到的工具类JadyerUtil.java
package com.jadyer.util;
import java.io.UnsupportedEncodingException;
public class JadyerUtil {
private JadyerUtil(){}
/**
* 判断输入的字符串参数是否为空
* @return boolean 空则返回true,非空则flase
*/
public static boolean isEmpty(String input) {
return null==input || 0==input.length() || 0==input.replaceAll("\\s", "").length();
}
/**
* 判断输入的字节数组是否为空
* @return boolean 空则返回true,非空则flase
*/
public static boolean isEmpty(byte[] bytes){
return null==bytes || 0==bytes.length;
}
/**
* 字节数组转为字符串
* @see 如果系统不支持所传入的<code>charset</code>字符集,则按照系统默认字符集进行转换
*/
public static String getString(byte[] data, String charset){
if(isEmpty(data)){
return "";
}
if(isEmpty(charset)){
return new String(data);
}
try {
return new String(data, charset);
} catch (UnsupportedEncodingException e) {
System.out.println("将byte数组[" + data + "]转为String时发生异常:系统不支持该字符集[" + charset + "]");
return new String(data);
}
}
/**
* 字符串转为字节数组
* @see 如果系统不支持所传入的<code>charset</code>字符集,则按照系统默认字符集进行转换
*/
public static byte[] getBytes(String data, String charset){
data = (data==null ? "" : data);
if(isEmpty(charset)){
return data.getBytes();
}
try {
return data.getBytes(charset);
} catch (UnsupportedEncodingException e) {
System.out.println("将字符串[" + data + "]转为byte[]时发生异常:系统不支持该字符集[" + charset + "]");
return data.getBytes();
}
}
/**
* 通过ASCII码将十进制的字节数组格式化为十六进制字符串
* @see 该方法会将字节数组中的所有字节均格式化为字符串
* @see 使用说明详见<code>formatToHexStringWithASCII(byte[], int, int)</code>方法
*/
public static String buildHexStringWithASCII(byte[] data){
return buildHexStringWithASCII(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 格式化后的十六进制字符串
*/
public static String buildHexStringWithASCII(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 ASCII
sb.append(String.format("%02X ", b));
if(b<32 || b>126){ //不可见字符
sb2.append(" ");
}else{
sb2.append((char)b);
}
}else{ //CHA ASCII
if(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();
}
}
最后的最后贴一下使用JUnit4.x编写的测试用例TestServer.java
package com.jadyer.test;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
/**
* 这里用到的MinaUtil和HttpClientUtil均取自我的博文,地址如下
* http://blog.csdn.net/jadyer/article/details/8088068
* http://blog.csdn.net/jadyer/article/details/8087960
* @create Jul 9, 2013 7:59:11 PM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class TestServer {
@Test
public void testTcp(){
String message = "00004710005101101992012092222400000201307071605";
String respData = MinaUtil.sendTCPMessage(message, "127.0.0.1", 9901, "UTF-8");
Assert.assertEquals("00003099999999`20130707144028`", respData);
}
/**
* 也可直接浏览器访问http://127.0.0.1:8000/login以及http://127.0.0.1:8000/login?a=b&c=d&e=f
* 只要浏览器页面显示"登录成功",即表示HTTP_GET测试通过
*/
@Test
public void testHttpGet(){
//先测试带参数的GET请求
String respData11 = HttpClientUtil.sendGetRequest("http://127.0.0.1:8000/login?a=b&c=d&e=f");
Assert.assertEquals("登录成功", respData11);
//再测试不带参数的GET请求
String respData22 = HttpClientUtil.sendGetRequest("http://127.0.0.1:8000/login");
Assert.assertEquals("登录成功", respData22);
}
@Test
public void testHttpPost(){
//先测试带报文体的POST请求(即带参数,模拟表单提交)
String reqURL = "http://127.0.0.1:8000/login";
Map<String, String> params = new HashMap<String, String>();
params.put("username", "Jadyer");
params.put("password", "hongyu");
String respData11 = HttpClientUtil.sendPostSSLRequest(reqURL, params, "UTF-8");
Assert.assertEquals("登录成功", respData11);
//再测试不带报文体的POST请求(不带参数)
String respData22 = HttpClientUtil.sendPostSSLRequest(reqURL, new HashMap<String, String>(), "UTF-8");
Assert.assertEquals("登录成功", respData22);
//最后测试一下特殊情况,即不带报文体,但在请求地址上带有参数的POST请求(建行外联平台就这么干的)
reqURL = "http://127.0.0.1:8000/login?username=Jadyer&password=hongyu&aa=bb&cc=dd";
String respData33 = HttpClientUtil.sendPostSSLRequest(reqURL, new HashMap<String, String>(), "UTF-8");
Assert.assertEquals("登录成功", respData33);
}
}