上一篇已经讲过怎么绑定端口发送http请求和https请求,但是只能运行在单线程中不会出问题,然鹅,多线程发送请求时,就会出现一个socket不够用的情况,这就要求请求的端口需要设置一个范围,在范围内可以复用。
先写一个main方法模拟多线程请求的情况
public static void main(String[] args){
try {
final RestTemplateUtil2 restTemplateUtil2 = new RestTemplateUtil2();
String url = "http://www.baidu.com";
// String s = restTemplateUtil2.get(url,null,String.class);
int j = 0;
for (int i = 1; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while (j < 10){
String s = restTemplateUtil2.httpsGet(url, null, String.class);
// System.out.println("s=="+Thread.currentThread()+" = [" + s.length() + "]");
}
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("err = [" + e + "]");
}
}
修改一下上一篇自己写的BindPortConnectSocketFactory类中,创建socket的方法
public class BindPortConnectSocketFactory implements ConnectionSocketFactory {
public Map<Integer,Socket> map = new HashMap<>();
private int startPort = 10000;
private int endPort = 10005;
int currentPort = startPort;
public static final BindPortConnectSocketFactory INSTANCE = new BindPortConnectSocketFactory();
public static BindPortConnectSocketFactory getSocketFactory() {
return INSTANCE;
}
public BindPortConnectSocketFactory() {
super();
}
@Override
public synchronized Socket createSocket(HttpContext context) throws IOException {
for (Map.Entry<Integer,Socket> entry : map.entrySet()){
System.out.println("==socket="+entry.getValue().hashCode()+
"; port--"+entry.getValue().getLocalPort()+
"; close:"+entry.getValue().isClosed()+
"; connect-"+entry.getValue().isConnected()+
"; bind-"+entry.getValue().isBound()+
"; isInputShutdown:"+entry.getValue().isInputShutdown()+
"; isOutputShutdown:"+entry.getValue().isOutputShutdown());
}
FXSocket socket = new FXSocket();
socket.bind(new InetSocketAddress(currentPort++));
System.out.println("socket.getLocalPort() = ["+socket.getLocalPort()+
"]=socket="+socket.hashCode());
return socket;
}
@Override
public Socket connectSocket(
int connectTimeout,
Socket socket,
HttpHost host,
InetSocketAddress remoteAddress,
InetSocketAddress localAddress,
HttpContext context) throws IOException {
final Socket sock = socket != null ? socket : createSocket(context);
if (localAddress != null) {
sock.bind(localAddress);
}
try {
sock.connect(remoteAddress, connectTimeout);
} catch (final IOException ex) {
try {
sock.close();
} catch (final IOException ignore) {
}
throw ex;
}
System.out.println("connectSocket.getLocalPort = [" + socket.getLocalPort() + "]");
map.put(socket.getLocalPort(),sock);
return sock;
}
}
这里添加了一个map来记录当前所有添加的socket和port情况,还有端口范围以及端口绑定情况
看一下输出日志
socket.getLocalPort() = [10000]=socket=1704182765
socket.getLocalPort() = [10001]=socket=1119031276
connectSocket.getLocalPort = [10001]
connectSocket.getLocalPort = [10000]
关闭socket连接
==socket = [1704182765; port--10000; close:false; connect-true; bind-true; isInputShutdown:false; isOutputShutdown:false
==socket = [1119031276; port--10001; close:true; connect-true; bind-true; isInputShutdown:true; isOutputShutdown:true
socket.getLocalPort() = [10002]=socket=325716189
18:00:12.015 [Thread-41] INFO o.a.http.impl.execchain.RetryExec - I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://www.baidu.com:80: The target server failed to respond
18:00:12.022 [Thread-41] INFO o.a.http.impl.execchain.RetryExec - Retrying request to {}->http://www.baidu.com:80
connectSocket.getLocalPort = [10002]
关闭socket连接
18:00:13.226 [Thread-66] INFO o.a.http.impl.execchain.RetryExec - I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://www.baidu.com:80: The target server failed to respond
==socket = [1704182765; port--10000; close:true; connect-true; bind-true; isInputShutdown:true; isOutputShutdown:true
==socket = [1119031276; port--10001; close:true; connect-true; bind-true; isInputShutdown:true; isOutputShutdown:true
==socket = [325716189; port--10002; close:false; connect-true; bind-true; isInputShutdown:false; isOutputShutdown:false
18:00:13.227 [Thread-66] INFO o.a.http.impl.execchain.RetryExec - Retrying request to {}->http://www.baidu.com:80
socket.getLocalPort() = [10003]=socket=92872500
connectSocket.getLocalPort = [10003]
Process finished with exit code 0
大量请求实验,有兴趣的同学可以自己试试,我试过以后,发现socket永远再用的只会有2个,之前创建的会关闭。但是注意,关闭之后,不会解绑,也不会关闭连接,所以这也导致了已用过的端口无法再绑定,否则会报address already in use的错误。
看Socket的源码,也会发现这个问题
/**
* Closes this socket.
* <p>
* Any thread currently blocked in an I/O operation upon this socket
* will throw a {@link SocketException}.
* <p>
* Once a socket has been closed, it is not available for further networking
* use (i.e. can't be reconnected or rebound). A new socket needs to be
* created.
*
* <p> Closing this socket will also close the socket's
* {@link java.io.InputStream InputStream} and
* {@link java.io.OutputStream OutputStream}.
*
* <p> If this socket has an associated channel then the channel is closed
* as well.
*
* @exception IOException if an I/O error occurs when closing this socket.
* @revised 1.4
* @spec JSR-51
* @see #isClosed
*/
public synchronized void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
}
}
翻译一下,意思是:
当前在此套接字上的I/O操作中阻塞的任何线程都将抛出SocketException异常
一旦一个套接字被关闭,它就不能用于进一步的网络使用(即不能重新连接或重新绑定)。需要创建一个新的套接字。
下面的源码我就把解释直接写在下面了
/**
* Returns the connection state of the socket.
* <p>
* Note: Closing a socket doesn't clear its connection state, which means
* this method will return {@code true} for a closed socket
* (see {@link #isClosed()}) if it was successfuly connected prior
* to being closed.
*
*注意:关闭套接字并不会清除它的连接状态,这意味着该方法将为关闭的套接字返回{@code true}(参见
*{@link #isClosed()}),如果它在关闭之前已经成功连接。
*
* @return true if the socket was successfuly connected to a server
* @since 1.4
*/
public boolean isConnected() {
// Before 1.3 Sockets were always connected during creation
return connected || oldImpl;
}
/**
* Returns the binding state of the socket.
* <p>
* Note: Closing a socket doesn't clear its binding state, which means
* this method will return {@code true} for a closed socket
* (see {@link #isClosed()}) if it was successfuly bound prior
* to being closed.
*
* 注意:关闭套接字不清除其绑定状态,这意味着该方法将为关闭的套接字返回{@code true}(请参阅
* {@link #isClosed()}),如果它在关闭之前绑定成功的话。
*
* @return true if the socket was successfuly bound to an address
* @since 1.4
* @see #bind
*/
public boolean isBound() {
// Before 1.3 Sockets were always bound during creation
return bound || oldImpl;
}
也就是说,一旦端口绑定了,想要再去绑定这个端口,就会报错,在哪里报错呢,我故意让它绑定报错一下
修改一下代码
public synchronized Socket createSocket(HttpContext context) throws IOException {
for (Map.Entry<Integer,Socket> entry : map.entrySet()){
System.out.println("==socket = [" + entry.getValue().hashCode()+"; port--"+ entry.getValue().getLocalPort()+ "; close:"
+entry.getValue().isClosed()+"; connect-"+entry.getValue().isConnected()+"; bind-"+entry.getValue().isBound()
+"; isInputShutdown:"+entry.getValue().isInputShutdown()+"; isOutputShutdown:"+entry.getValue().isOutputShutdown());
}
FXSocket socket = new FXSocket();
if (currentPort == 10003){
socket.bind(new InetSocketAddress(10000));
}else{
socket.bind(new InetSocketAddress(currentPort++));
}
System.out.println("socket.getLocalPort() = [" + socket.getLocalPort() + "]"+"=socket="+socket.hashCode());
return socket;
}
为什么写10003呢,因为一直都只会用两个端口,当10003用到的时候10000肯定已经关闭了,日志表示也是真的关闭了
下面试报错的日志
==socket = [1368876963; port--10000; close:true; connect-true; bind-true; isInputShutdown:true; isOutputShutdown:true
==socket = [2040279492; port--10001; close:true; connect-true; bind-true; isInputShutdown:true; isOutputShutdown:true
==socket = [508958732; port--10002; close:false; connect-true; bind-true; isInputShutdown:false; isOutputShutdown:false
Exception in thread "Thread-18" org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://www.baidu.com": Address already in use: JVM_Bind; nested exception is java.net.BindException: Address already in use: JVM_Bind
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:666)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531)
at com.fxdigital.restcustom.RestTemplateUtil2.exchangeHttps(RestTemplateUtil2.java:150)
at com.fxdigital.restcustom.RestTemplateUtil2.httpsGet(RestTemplateUtil2.java:142)
at com.fxdigital.restcustom.RestTemplateUtil2$1.run(RestTemplateUtil2.java:166)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.BindException: Address already in use: JVM_Bind
at java.net.DualStackPlainSocketImpl.bind0(Native Method)
at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106)
at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387)
at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:190)
at java.net.Socket.bind(Socket.java:644)
at com.fxdigital.restcustom.http.BindPortConnectSocketFactory.createSocket(BindPortConnectSocketFactory.java:50)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:118)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at com.fxdigital.restcustom.http.BindPortCloseableHttpClient.doExecute(BindPortCloseableHttpClient.java:157)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:89)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:652)
... 6 more
at java.net.DualStackPlainSocketImpl.bind0(Native Method)
at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106)
这里我们看到,绑定最后走到了native方法,这我就想不开了,这要我怎么搞,总不至于还让公司的人去改底层吧
但是控制台的10000端口也确实没有进程使用
前面是在执行请求的时候,后面是绑定的端口socket已经关闭了,但是还是不能绑定
突发奇想一下,那要是系统自己自动随机的端口呢
去掉自己的代码,重新运行,还好可以去map中直接拿到已经关闭的socket曾经绑定的端口
int port = 0;
public synchronized Socket createSocket(HttpContext context) throws IOException {
for (Map.Entry<Integer,Socket> entry : map.entrySet()){
System.out.println("==socket = [" + entry.getValue().hashCode()+"; port--"+ entry.getValue().getLocalPort()+ "; close:"
+entry.getValue().isClosed()+"; connect-"+entry.getValue().isConnected()+"; bind-"+entry.getValue().isBound()
+"; isInputShutdown:"+entry.getValue().isInputShutdown()+"; isOutputShutdown:"+entry.getValue().isOutputShutdown());
if (entry.getValue().isClosed()){
port = entry.getKey();
}
}
FXSocket socket = new FXSocket();
if (port != 0){
socket.bind(new InetSocketAddress(port));
}
System.out.println("socket.getLocalPort() = [" + socket.getLocalPort() + "]"+"=socket="+socket.hashCode());
return socket;
}
运行一下,看一下日志
==socket = [1219446347; port--4082; close:true; connect-true; bind-true; isInputShutdown:true; isOutputShutdown:true
==socket = [696481194; port--4075; close:false; connect-true; bind-true; isInputShutdown:false; isOutputShutdown:false
Exception in thread "Thread-1" org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://www.baidu.com": Address already in use: JVM_Bind; nested exception is java.net.BindException: Address already in use: JVM_Bind
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:666)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531)
at com.fxdigital.restcustom.RestTemplateUtil2.exchangeHttps(RestTemplateUtil2.java:150)
at com.fxdigital.restcustom.RestTemplateUtil2.httpsGet(RestTemplateUtil2.java:142)
at com.fxdigital.restcustom.RestTemplateUtil2$1.run(RestTemplateUtil2.java:166)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.BindException: Address already in use: JVM_Bind
at java.net.DualStackPlainSocketImpl.bind0(Native Method)
at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106)
at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387)
at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:190)
at java.net.Socket.bind(Socket.java:644)
at com.fxdigital.restcustom.http.BindPortConnectSocketFactory.createSocket(BindPortConnectSocketFactory.java:55)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:118)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at com.fxdigital.restcustom.http.BindPortCloseableHttpClient.doExecute(BindPortCloseableHttpClient.java:157)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:89)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:652)
... 6 more
Exception in thread "Thread-49" org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://www.baidu.com": Address already in use: JVM_Bind; nested exception is java.net.BindException: Address already in use: JVM_Bind
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:666)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531)
at com.fxdigital.restcustom.RestTemplateUtil2.exchangeHttps(RestTemplateUtil2.java:150)
at com.fxdigital.restcustom.RestTemplateUtil2.httpsGet(RestTemplateUtil2.java:142)
at com.fxdigital.restcustom.RestTemplateUtil2$1.run(RestTemplateUtil2.java:166)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.BindException: Address already in use: JVM_Bind
at java.net.DualStackPlainSocketImpl.bind0(Native Method)
at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106)
at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387)
at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:190)
at java.net.Socket.bind(Socket.java:644)
at com.fxdigital.restcustom.http.BindPortConnectSocketFactory.createSocket(BindPortConnectSocketFactory.java:55)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:118)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
依旧还是报错,说明,就算是不修改,也是端口一旦绑定,也是一样的没有释放,说明系统自己管理的时候,也没有及时释放端口
难道要手动释放,百度有说要2分钟时长等待的,写了一个5分钟的等待,试了一下,依旧不行
这下我就懵逼了,这怎么搞,要是这样一直加下去,范围我根本控制不了啊
百度的时候,偶然发现了这样一个人写的博客
https://www.cnblogs.com/yunnotes/archive/2013/04/19/3032301.html
在linux系统中,直接修改/proc/sys/net/ipv4/ip_local_port_range文件中的值,就可以限制socket建立的tcp连接端口范围了。感觉自己辛苦了那么久,好像走错了方向,求内心的阴影面积
开心的我拿到了公司的linux服务器,在上面迅速跑了一把,其中限制端口为10000-10200
多次测试之后,端口一直都是在10000-10200之间,当改回原来的配置32768-61000之后,端口也成了40000多,it worked
但是proc目录下的文件,都是运行时生成的,所以,重启之后,就没有用了,我们要配置好,就得在另外一个地方去配置了
在/etc/system.config文件中这样写
# System default settings live in /usr/lib/sysctl.d/00-system.conf.
# To override those settings, enter new settings here, or in an /etc/sysctl.d/<name>.conf file
#
# For more information, see sysctl.conf(5) and sysctl.d(5).
net.ipv4.ip_local_port_range=20000 210000
接下来,就是windows的本地端口限制了,就留在下一篇啦。