问题描述
报文抓包结果显示,TCP三次握手后(序号53),TLS延时10秒后才发送client hello报文(序号54)
问题分析
所有请求报文都产生了如下问题,初步分析问题原因是服务端发布的https服务,客户端使用的httpclient工具不兼容导致。
TCP Out_of_Order的原因分析:
一般来说是网络拥塞,导致顺序包抵达时间不同,延时太长,或者包丢失,需要重新组合数据单元,因为他们可能是由不同的路径到达你的电脑上面。
TCP Retransmission原因分析:
很明显是上面的超时引发的数据重传。
TCP dup ack XXX#X原因分析:
就是重复应答#前的表示报文到哪个序号丢失,#后面的是表示第几次丢失。
tcp previous segment not captured原因分析
意思就是报文没有捕捉到,出现报文的丢失。
pom依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.1.2</version>
</dependency>
客户端代码
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.security.KeyStore;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import com.ailk.aiip.enma.services.HttpClientService;
@Lazy(false)
public class HttpClientServiceImpl implements HttpClientService {
final private Logger LOGGER = LoggerFactory.getLogger(getClass());
private String keyStoreFilePath;
private String keyStorePassword;
private int serverPort;
private String serverIp;
private String url;
private boolean sslEnable;
private int connectionTimeout;
private int soTimeout;
private HttpClient httpClient;
private int retryTimes;
private String charset;
private String keyStoreClientPath;
private String keyStoreClientPassword;
@PostConstruct
public void init() {
httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager());
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout * 1000);
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, soTimeout * 1000);
httpClient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, charset);
if (sslEnable) {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream = new FileInputStream(new File(keyStoreFilePath));
keyStore.load(instream, keyStorePassword.toCharArray());
KeyStore clientkeyStore = KeyStore.getInstance("PKCS12");
InputStream ksIn = new FileInputStream(keyStoreClientPath);
clientkeyStore.load(ksIn, keyStoreClientPassword.toCharArray());
SSLSocketFactory socketFactory = new SSLSocketFactory(clientkeyStore,keyStoreClientPassword,keyStore);
socketFactory
.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Scheme sch = new Scheme("https", serverPort, socketFactory);
httpClient.getConnectionManager().getSchemeRegistry().register(sch);
} catch (Exception e) {
LOGGER.error("init HttpClient error", e);
}
}
}
@PreDestroy
public void destory() {
if(httpClient!=null){
httpClient.getConnectionManager().shutdown();
}
}
@Override
public String post(final String requestXml) {
String responseXml = null;
//post发送数据
if(httpClient != null){
try {
String requestUrl = "http://";
if (sslEnable) {
requestUrl = "https://";
}
requestUrl = requestUrl + serverIp + ":" + serverPort + url;
for (int i = 0; i <= retryTimes; i++) {
HttpPost post = null;
try {
post = new HttpPost(requestUrl);
LOGGER.debug("post request to {}", post.getURI());
ContentProducer contentProducer = new ContentProducer() {
@Override
public void writeTo(OutputStream out) throws IOException {
Writer writer = new OutputStreamWriter(out, charset);
writer.write(requestXml);
writer.flush();
}
};
post.setEntity(new EntityTemplate(contentProducer));
HttpResponse response = httpClient.execute(post);
responseXml = EntityUtils.toString(response.getEntity(), charset);
LOGGER.debug("post response is {}", responseXml);
break;
} catch (Exception e) {
LOGGER.error("post error,retry count {}", i + 1,e);
}
finally{
if(post!=null){
post.abort();
}
}
}
} catch (Exception e) {
LOGGER.error("post error", e);
}
}
return responseXml;
}
public String getKeyStoreFilePath() {
return keyStoreFilePath;
}
public void setKeyStoreFilePath(String keyStoreFilePath) {
this.keyStoreFilePath = keyStoreFilePath;
}
public String getKeyStorePassword() {
return keyStorePassword;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public int getServerPort() {
return serverPort;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public String getServerIp() {
return serverIp;
}
public void setServerIp(String serverIp) {
this.serverIp = serverIp;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isSslEnable() {
return sslEnable;
}
public void setSslEnable(boolean sslEnable) {
this.sslEnable = sslEnable;
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public int getSoTimeout() {
return soTimeout;
}
public void setSoTimeout(int soTimeout) {
this.soTimeout = soTimeout;
}
public int getRetryTimes() {
return retryTimes;
}
public void setRetryTimes(int retryTimes) {
this.retryTimes = retryTimes;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public void setKeyStoreClientPath(String keyStoreClientPath) {
this.keyStoreClientPath = keyStoreClientPath;
}
public void setKeyStoreClientPassword(String keyStoreClientPassword) {
this.keyStoreClientPassword = keyStoreClientPassword;
}
}
尝试解决方案
修改过各项参数,如:100-continue,未成功
HttpProtocolParams.setUseExpectContinue(params, true);
短连接修改为长连接,未成功
post.addHeader("Connection", "Keep-Alive");
升级过httpclient版本,连接池由ThreadSafeClientConnManager改为PoolingClientConnectionManager,未成功
引入commons-httpclient,替换org.apache.httpcomponents:httpclient,问题解决了
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
public String post(final String requestXml) {
String responseXml = null;
try {
String requestUrl = "http://";
if (sslEnable) {
requestUrl = "https://";
}
requestUrl = requestUrl + serverIp + ":" + serverPort + url;
System.setProperty("javax.net.ssl.trustStore", getKeyStoreFilePath());
System.setProperty("javax.net.ssl.trustStorePassword", keyStorePassword);
HttpClient client = new HttpClient();
PostMethod method = new PostMethod(requestUrl);
method.setRequestBody(requestXml);
LOGGER.debug("post requestXml is {}", requestXml);
client.executeMethod(method);
responseXml = method.getResponseBodyAsString();
LOGGER.debug("post response is {}", responseXml);
method.releaseConnection();
} catch (Exception e) {
LOGGER.error("post error", e);
}
return responseXml;
}