我有一个与第三方控制器通信的软件驱动程序;我有一个用于使用后者的API,但看不到它的源代码,而供应商也不合作,试图改进事情!我的情况如下。
情况是这样的。
为了向控制器发送请求,我发送一个XML包作为HTTP POST的内容给一个servlet,然后servlet给我发送响应。原来的代码,由之前的开发人员实现,使用java.net.Socket可以稳定工作。然而,我们的驱动是这样实现的,每发送一个请求都会创建一个新的套接字,如果驱动忙起来,第三方控制器在套接字处理方面就很难跟上。事实上,他们的支持人员对我说:"你真的需要留出5秒钟的时间。"你真的需要在每个请求之间留出5秒的时间..." 这在商业上根本无法接受。
为了提高性能,我想尝试让我们的套接字端打开,并无限期地重复使用套接字(当然考虑到连接可能会意外掉落,但这是我最不关心的问题,是可以控制的)。然而,无论我看起来做什么,效果都是,如果我使用Comms.getSocket(false),每一个请求都会创建一个新的套接字,一切都能正常工作,但忙的时候会出现瓶颈。如果我使用Comms.getSocket(true),会发生以下情况。
控制器允许HTTP 1.0和1.1,但他们的文档中没有提到keep-alive。我尝试了这两种模式,下面的代码显示我也添加了Keep-Alive头信息,但是作为服务器的控制器,我猜测是忽略了它们 -- 我想我没有办法知道,是吗?在HTTP 1.0模式下,控制器肯定会返回 "Connection: close",但在HTTP 1.1模式下不会这样做。
那么很可能是服务器端坚持 "每个请求一个socket "的方法。
然而,我想知道,在下面的代码中,我是否可能做错了什么(或者遗漏了什么)来实现我想要的东西。
private String postRequest() throws IOException {
String resp = null;
String logMsg;
StringBuilder sb = new StringBuilder();
StringBuilder sbWrite = new StringBuilder();
Comms comms = getComms();
Socket socket = comms.getSocket(true);
BufferedReader br = comms.getReader();
BufferedWriter bw = comms.getWriter();
if (null != socket) {
System.out.println("Socket closed ? " + socket.isClosed());
System.out.println("Socket bound ? " + socket.isBound());
System.out.println("Socket connected ? " + socket.isConnected());
// Write the request
sbWrite
.append("POST /servlet/receiverServlet HTTP/1.1\r\n")
.append("Host: 192.168.200.100\r\n")
.append("Connection: Keep-Alive\r\n")
.append("Keep-Alive: timeout=10\r\n")
.append("Content-Type: text/xml\r\n")
.append("Content-Length: " + requestString.length() + "\r\n\r\n")
.append(requestString);
System.out.println("Writing:\n" + sbWrite.toString());
bw.write(sbWrite.toString());
bw.flush();
// Read the response
System.out.println("Input shut down ? " + socket.isInputShutdown());
String line;
boolean flag = false;
while ((line = br.readLine()) != null) {
System.out.println("Line: <" + line + ">");
if (flag) sb.append(line);
if (line.isEmpty()) flag = true;
}
resp = sb.toString();
}
else {
System.out.println("Socket not available");
}
return resp; // Another method will parse the response
}
为了方便测试,我用一个额外的Comms助手类和一个叫做getSocket(boolean reuse)的方法来提供套接字,我可以选择总是创建一个新的套接字或者重用Comms为我创建的套接字,如下所示。
public Comms(String ip, int port) {
this.ip = ip;
this.port = port;
initSocket();
}
private void initSocket() {
try {
socket = new Socket(ip, port);
socket.setKeepAlive(true);
socket.setPerformancePreferences(1, 0, 0);
socket.setReuseAddress(true);
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
br = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
System.out.println("@@@ CREATED NEW SOCKET");
}
catch (UnknownHostException uhe) {
System.out.println("@@@ UNKNOWN HOST FOR SOCKET");
}
catch (IOException ioe) {
System.out.println("@@@ SOCKET I/O EXCEPTION");
}
}
public BufferedReader getReader() { return br; }
public BufferedWriter getWriter() { return bw; }
public Socket getSocket(boolean reuse) {
if (! reuse) initSocket();
return socket;
}
谁能帮帮我?
如果我们假设keep-alive的工作符合预期,我认为这一行是这样的 while ((line = br.readLine()) != null)
是一个错误的,因为这是一种无限循环。
readline()
返回 null
当没有更多的数据可读时,例如a EOF
,或者当serverclient关闭连接时,这将破坏你的重用套接字解决方案,因为一个开放的流永远不会导致一个 null
到 readLine()
调用,但会阻塞。
你需要修正关于读取响应的算法(为什么不使用已实现的http客户端?content-length
当从body中读取所需的数据量时,通过保持socket的活力进行下一个循环。flag
到 true
读取数据时,必须知道要读取的是什么数据(考虑到mimecontent-type),除此之外,还要知道数据的长度,所以读取数据时要使用 readLine()
在这里可能不是一个好的做法。
同时确保服务器允许持久化连接,通过检查它是否尊重它,通过响应相同的 connection:keep-alive
头部。