上一篇已經講過怎麼綁定端口發送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的本地端口限制了,就留在下一篇啦。