一直以来很迷惑TOMCAT的连接数,网上也是众说纷纭,所以今天自己专门来测试一下,配置参数请参考: TOMCAT配置参数说明 ,本人比较懒,所以只测试了BIO和NIO,没有APR模式(主要是看说的那么多,不想配置),下面正式开始:
1、测试环境
TOMCAT:TOMCAT8
JAVA:JDK1.8.0_121
ServerSocket的启动方式:继承至HttpServlet随容器启动
2、服务端统一测试代码
ServerSocket
package test.server;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
public class StartSocketServlet extends HttpServlet {
ControlScoketServer ass = null;
@Override
public void init() throws ServletException {
// TODO Auto-generated method stub
super.init();
// MiniBasic.isDebug=true;
ass = new ControlScoketServer();
ass.setDaemon(true);
ass.start();
System.out.println("服务已经开启");
}
@Override
public void destroy() {
// TODO Auto-generated method stub
super.destroy();
try {
ass.stopScoketServer();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
ass.interrupt();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package test.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import zhw.basic.CheckUtils;
import zhw.server.basic.ssh.MiniBasic;
public class ControlScoketServer extends Thread {
public boolean isStop = false;
protected ServerSocket serverSocket;
// private ExecutorService cachedThreadPool ;
private String ip;
private int port;
public final static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static void startThread(Runnable run){
cachedThreadPool.execute(run);
}
public ControlScoketServer() {
this.ip = "127.0.0.1";
this.port = 10083;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
startServer();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void stopScoketServer() {
isStop = true;
try {
serverSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void startServer() throws IOException {
isStop = false;
serverSocket = new ServerSocket();
try {
serverSocket.setSoTimeout(0);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if (!CheckUtils.String_IsNullOrEmpty(ip)) {
serverSocket.bind(new InetSocketAddress(ip, port));
} else {
serverSocket.bind(new InetSocketAddress(port));
}
MiniBasic.getLogger().info("Socket服务器开启,IP:" + ip + ",端口:" + port);
int i = 0;
while (!isStop) {
try {
Socket client = serverSocket.accept();
i++;
ClientSocketHandler csh = new ClientSocketHandler(client, i);
startThread(csh);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package test.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import zhw.server.basic.ssh.MiniBasic;
public class ClientSocketHandler implements Runnable {
private int threadId = 0;
Socket client;
private InputStream is;
private OutputStream os;
public ClientSocketHandler(Socket client, int threadId) {
this.threadId = threadId;
this.client = client;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
is = this.client.getInputStream();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
os = this.client.getOutputStream();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
byte[] bt = new byte[512];
while (true) {
MiniBasic.showInfo("线程: " + threadId + " 开始执行,等待客户端输入..");
try {
while (is.read(bt) != 0) {
MiniBasic.showInfo("**************线程: " + threadId + " 客户端输入为:" + new String(bt));
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
break;
}
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
HttpServer
package test.server;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HttpServerServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println("有客户端连接,参数:"+req.getParameter("TESTPARAM"));
try {
//保持连接
Thread.sleep(30*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
resp.getWriter().print("SERVER RESPONSE:"+req.getParameter("TESTPARAM"));
resp.getWriter().close();
}
}
WebsocketServer
package test.server;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import zhw.server.basic.ssh.MiniBasic;
import zhw.server.basic.ssh.websocket.ZhwWebSocketServer;
@ServerEndpoint(value = "/testwebsocket")
public class WebSocketServer {
public static CopyOnWriteArrayList<WebSocketServer> wbSockets = new CopyOnWriteArrayList<WebSocketServer>();
private Session session;
@OnOpen
public void start(Session session, EndpointConfig config) {
this.session = session;
System.out.println("新的客户端:" + this.session.getId());
wbSockets.add(this);
}
@OnClose
public void end() {
MiniBasic.getLogger().error("websocket关闭:" + this.session.getId());
socketClosed();
}
private void socketClosed() {
if (wbSockets != null && wbSockets.contains(this)) {
wbSockets.remove(this);
}
}
@OnMessage
public void incoming(String message) {
// Never trust the client
MiniBasic.showInfo("websocket 收到信息: " + message);
}
@OnError
public void onError(Throwable t) throws Throwable {
MiniBasic.getLogger().error("websocket出错:" + this.session.getId() + "--" + t.getMessage());
}
public void sendData(String data) {
try {
MiniBasic.showInfo("------------发送socket数据。。。。。。。。。。。" + data);
this.session.getBasicRemote().sendText(data);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for additional
information regarding copyright ownership. The ASF licenses this file to
You under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of
the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License. -->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="true" version="3.1">
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>
<servlet>
<servlet-name>testserver</servlet-name>
<servlet-class>test.server.HttpServerServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>socketserver</servlet-name>
<servlet-class>test.server.StartSocketServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testserver</servlet-name>
<url-pattern>*.test</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>0</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
3、客户端代码,根据测试不同需要更改
HttpClient
package test.client;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import zhw.basic.http.HttpHelper;
public class HttpClient {
public final static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception {
HttpHelper helper = new HttpHelper();
for (int i = 0; i < 260; i++) { //根据测试不同更改数量
ExcuteHttpRequest ehr = new ExcuteHttpRequest(i + 1, helper);
cachedThreadPool.execute(ehr);
Thread.sleep(10);
}
Thread.sleep(3 * 1000);
}
}
class ExcuteHttpRequest implements Runnable {
private int clid;
private HttpHelper helper;
public ExcuteHttpRequest(int clid, HttpHelper helper) {
this.clid = clid;
this.helper = helper;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(this.clid + "开始连接..");
try {
String responseText = this.helper.sendRequest("http://127.0.0.1/test/testhttp.test",
"TESTPARAM=HTTPCLIENT-" + this.clid, "UTF-8");
System.out.println(this.clid + "接受到的数据:" + responseText);
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println(this.clid +"连接出错:"+e.getMessage());
}
}
}
SocketClient
package test.client;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class SocketClient {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
List<Socket> ls = new ArrayList<Socket>();
for (int i = 0; i < 5000; i++) { //
Socket s = new Socket("127.0.0.1", 10083);
// s.setKeepAlive(true);
s.setOOBInline(false);
s.setSoTimeout(0);
ls.add(s);
Thread.sleep(10);
System.out.println("已连接:" + i);
}
Thread.sleep(3 * 1000);
int i = 0;
while (true) {
for (Socket ws : ls) {
i++;
try {
ws.getOutputStream().write(("SOCKET CLIENT:" + i).getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.sleep(10);
}
i = 0;
Thread.sleep(20 * 1000);
}
}
}
WebSocketClient
package test.client;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
@ClientEndpoint
public class WebSocketClient {
private String deviceId;
private Session session;
public WebSocketClient() {
}
public WebSocketClient(String deviceId) {
this.deviceId = deviceId;
}
protected boolean start() {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
String uri = "ws://127.0.0.1/test/testwebsocket";
System.out.println("Connecting to " + uri);
try {
session = container.connectToServer(WebSocketClient.class, URI.create(uri));
System.out.println("count: " + deviceId);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
public static void main(String[] args) throws InterruptedException {
List<WebSocketClient> ar = new ArrayList<WebSocketClient>();
for (int i = 1; i < 260; i++) {// 根据测试不同更改数量
WebSocketClient wSocketTest = new WebSocketClient(String.valueOf(i));
if (!wSocketTest.start()) {
System.out.println("测试结束!");
break;
} else {
ar.add(wSocketTest);
Thread.sleep(10);
}
}
Thread.sleep(3 * 1000);
while (true) {
for (WebSocketClient ws : ar) {
try {
ws.session.getBasicRemote().sendText("CLIENT:" + ws.deviceId);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread.sleep(10);
}
Thread.sleep(20 * 1000);
}
}
}
ComblineTest
package test.client;
import java.net.Socket;
import zhw.basic.http.HttpHelper;
public class ComblineTest {
public static void main(String[] args) {
HttpHelper helper = new HttpHelper();
for (int i = 0; i < 100; i++) {
ExcuteHttpRequest ehr = new ExcuteHttpRequest(i + 1, helper);
HttpClient.cachedThreadPool.execute(ehr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 100; i < 250; i++) {// 根据测试不同更改数量
WebSocketClient wSocketTest = new WebSocketClient(String.valueOf(i));
if (!wSocketTest.start()) {
System.out.println("测试结束!");
break;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
for (int i = 250; i < 400; i++) {
ExcuteHttpRequest ehr = new ExcuteHttpRequest(i + 1, helper);
HttpClient.cachedThreadPool.execute(ehr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 400; i < 1400; i++) { //
try {
Socket s = new Socket("127.0.0.1", 10083);
// s.setKeepAlive(true);
s.setOOBInline(false);
s.setSoTimeout(0);
Thread.sleep(10);
System.out.println("已连接:" + i);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
BIO模式测试
catalina.bat 启动项配置:
set JAVA_OPTS=-server -Xms128m -Xmx512m -Xss1m -XX:PermSize=128m -XX:MaxNewSize=256m -XX:MaxPermSize=256m
server.xml配置:
<Executor name="tomcatThreadPool"
namePrefix="tomcatThreadPool-"
maxThreads="200"
maxSpareThreads="300"
minSpareThreads="100"/>
<Connector executor="tomcatThreadPool"
port="80"
protocol="org.apache.coyote.http11.Http11Protocol"
maxHttpHeaderSize="8192"
acceptCount="50"
connectionTimeout="20000"
keepAliveTimeout="36000000"
maxKeepAliveRequests="-1"
redirectPort="443" />
HttpClient
创建260个连接 连接数测试结果:
260开始连接..
251连接出错:请求失败:Connection refused: connect
252连接出错:请求失败:Connection refused: connect
253连接出错:请求失败:Connection refused: connect
254连接出错:请求失败:Connection refused: connect
255连接出错:请求失败:Connection refused: connect
256连接出错:请求失败:Connection refused: connect
257连接出错:请求失败:Connection refused: connect
258连接出错:请求失败:Connection refused: connect
259连接出错:请求失败:Connection refused: connect
260连接出错:请求失败:Connection refused: connect
可以看出第251个开始就不能连接了,符合 200个最大线程+50个 队列 数
197接受到的数据:SERVER RESPONSE:HTTPCLIENT-197
198接受到的数据:SERVER RESPONSE:HTTPCLIENT-198
199接受到的数据:SERVER RESPONSE:HTTPCLIENT-199
200接受到的数据:SERVER RESPONSE:HTTPCLIENT-200
202接受到的数据:SERVER RESPONSE:HTTPCLIENT-202
201接受到的数据:SERVER RESPONSE:HTTPCLIENT-201
....
248接受到的数据:SERVER RESPONSE:HTTPCLIENT-248
249接受到的数据:SERVER RESPONSE:HTTPCLIENT-249
250接受到的数据:SERVER RESPONSE:HTTPCLIENT-250
250个结果都正确返回
SocketClient
创建1000个连接 连接数测试结果:
已连接:996
已连接:997
已连接:998
已连接:999
服务端接受结果
ServerSocket 不受tomcat配置的连接池限制
WebSocket
创建260个连接,连接数测试结果:
count: 200
Connecting to ws://127.0.0.1/test/testwebsocket
测试结束!
javax.websocket.DeploymentException: The HTTP request to initiate the WebSocket connection failed
200就已经不能创建了,受到200个最大线程数限制
WebSocket+HttpClient+SocketServer 联合测试
- 创建100个http连接,0-99(显示+1了)
全部连接成功98开始连接.. 99开始连接.. 100开始连接.. Connecting to ws://127.0.0.1/test/testwebsocket count: 100 Connecting to ws://127.0.0.1/test/testwebsocket count: 101
- 然后创建150个websocket连接,100-249
第200个时连接失败,因为已经到了200MaxThreadcount: 199 Connecting to ws://127.0.0.1/test/testwebsocket 测试结束! javax.websocket.DeploymentException: The HTTP request to initiate the WebSocket connection failed
- 创建200个http连接,250-399(显示+1了)
第300个失败,acceptCount 起作用397开始连接.. 398开始连接.. 399开始连接.. 400开始连接.. 301连接出错:请求失败:Connection refused: connect 300连接出错:请求失败:Connection refused: connect
- 创建1000个socket连接 400-1399
已连接:1398 已连接:1399 8接受到的数据:SERVER RESPONSE:HTTPCLIENT-8
全部连接成功
BIO模式测试完成
NIO模式测试
把server中的connector修改为如下,其他不变:
<Connector executor="tomcatThreadPool"
port="80"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxHttpHeaderSize="8192"
acceptCount="50"
connectionTimeout="20000"
keepAliveTimeout="36000000"
maxKeepAliveRequests="-1"
redirectPort="443" />
HttpClient
创建500个连接 连接数测试结果:
499开始连接..
500开始连接..
13接受到的数据:SERVER RESPONSE:HTTPCLIENT-13
17接受到的数据:SERVER RESPONSE:HTTPCLIENT-17
客户端不会报错,服务每次只处理200个,这个跟maxthread设置有关系,处理完成后再依次处理后续请求,acceptcount无效。为了确认是不是因为请求数少了而没有报错,我使用了创建5000个连接但是在1200多的时候,eclise 显示 oom,不过我估计确实NIO不会受accpet限制 1228开始连接..
1229开始连接..
1230开始连接..
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
SocketClient
创建3000个连接 连接数测试结果:
已连接:2997
已连接:2998
已连接:2999
同时连接且能同时正常通信
Websocket
创建500个连接,均连接成功并都能正常通信
WebSocket+HttpClient+SocketServer 联合测试
这个测试的有些多,主要修改下联合测试的代码,测试结果:
SokcetServer连接不受限制
单独HTTP和单独websocket连接不受acceptcount限制,可以无限等待队列
先HTTP连接超过maxthreads后websocket将不能连接,直接报错,也就是说一般情况下http和websocket混合连接时,超过连接数限制websocket会报错,http会等待
先websocket连接超过maxthreads后HTTP能连接,但是进入等待队列,如果HTTP没有处理完成,WEBSOCKET会进入等待状态,可能是用例中http是线程等待,而websocket是IO等待的原因(才疏学浅,弄不明白)
总结一下:
两种模式下ScoektServer虽然是从tomcat中启动,但是不受tomcat连接数限制
HTTP和WEBSOCKET 单独不混合连接时 BIO 模式下都要受Maxthreads 限制,NIO模式下连接不受限制,但是要进入等待,要受等待时间限制
HTTP和WEBSOCKET混合连接时 BIO 模式下都要受Maxthreads 限制,NIO模式下超过Maxthread, WEBSOCKET 要报错,HTTP还是可以连接但是受等待时间限制
以上如果要配置TOMCAT 并发量 需要考虑 JVM的内存大小 和 Maxthreads 的数量