当前位置 : 首页 » 文章分类 :  开发  »  Java-Net

Java-Net

Java 网络相关笔记


Java 常见网络异常


BindException: Address already in use

java.net.BindException: Address already in use: JVM_Bind
该异常发生在服务器端进行 new ServerSocket(port) (port 是一个 0 到 65536 间的整型值)操作时。异常的原因是与 port 一样的一个端口已经被启动,并进行监听。此时用 netstat –anp 命令,可以看到一个 Listening 状态的端口。只需要找一个没有被占用的端口就能解决这个问题。


ConnectException: Connection refused

java.net.ConnectException: Connection refused: connect
该异常发生在客户端进行 new Socket(ip, port) 操作时,该异常发生的原因是或者具有 ip 地址的机器不能找到(也就是说从当前机器不存在到指定 ip 路由),或者是该 ip 存在,但找不到指定的端口进行监听。
出现该问题,首先检查客户端的 ip 和 port 是否写错了,如果正确则从客户端 ping 一下服务器看是否能 ping 通,如果能 ping 通(服务服务器端把 ping 禁掉则需要另外的办法),则看在服务器端的监听指定端口的程序是否启动,这个肯定能解决这个问题。


SocketException: Socket is closed

java.net.SocketException: Socket is closed
该异常在客户端和服务器均可能发生。
异常的原因是己方主动关闭了连接后(调用了 Socket 的 close 方法)再对网络连接进行读写操作。


SocketException: Connection reset

SocketException: Connection reset by peer

java.net.SocketException: Connection reset
java.net.SocketException: Connection reset by peer: socket write error

该异常在客户端和服务器端均有可能发生。

引起该异常的原因有两个:
1、第一个就是如果一端的 Socket 被关闭(主动关闭,或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connection reset by peer: socket write error)。
2、另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。
简单的说就是在连接断开后的读和写操作引起的。

导致 Connection reset 的原因是服务器端因为某种原因关闭了 Connection, 而客户端依然在读写数据,此时服务器会返回复位标志 RST, 然后此时客户端就会提示 “java.net.SocketException: Connection reset”

服务器返回了 RST 时,如果此时客户端正在从 Socket 套接字的输出流中读数据则会提示 “Connection reset”
服务器返回了 RST 时,如果此时客户端正在往 Socket 套接字的输入流中写数据则会提示 “Connection reset by peer: socket write error”

java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(SocketInputStream.java:210)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)


org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
        at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:380)
        at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:420)
        at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:345)

常见的场景举例:
1、比如 nginx 网关设有超时时间,超时时间内后端服务没有返回,网关主动断开了连接,等后端服务处理完请求发送响应时,就会报这个错。

2、比如如果发起一个 http 请求,但是这个响应很慢,然后客户端关闭了连接,服务端发送消息体的时候,发现连接被关闭了,会抛出这个异常


SocketException: Broken pipe

java.net.SocketException: Broken pipe
该异常在客户端和服务器均有可能发生。

在抛出 SocketExcepton:Connect reset by peer:Socket write error 后,如果再继续写数据则抛出该异常。前两个异常的解决方法是首先确保程序退出前关闭所有的网络连接,其次是要检测对方的关闭连接操作,发现对方关闭连接后自己也要关闭该连接。

常见的场景举例:
1、客户端由于某种原因断开了连接(比如读取超时,比如网关设置了60秒超时,超过60秒后网关直接返回超时并断开连接),而这时候服务器还在处理请求,它并不知道客户端已经断开了连接,处理完请求后再将处理结果发给客户端,就 broken pipe 了;
2、客户端读取超时关闭了连接,这时候服务器端再向客户端已经断开的连接写数据时就发生了broken pipe异常!

注意,并不是只有超时才会导致这个问题,只要是连接断开,再往这个断开的连接上去执行写操作,都会出现这个异常,客户端超时断开只是其中的一种情况

java.io.IOException: Broken pipe
https://stackoverflow.com/questions/15785175/java-io-ioexception-broken-pipe/15785439

一次SocketException:Connection reset 异常排查
https://www.cnblogs.com/shoren/p/httpclient-connectionreset.html

org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:380)
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:420)
at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:345)


java.io.IOException: UT010029: Stream is closed

因为一个流关闭了但是你又试着使用它就会报这个异常

https://stackoverflow.com/questions/38692035/java-io-ioexception-ut010029-stream-is-closed

Failed to invoke @ExceptionHandler method: public com.nio.common.web.HttpResponse com.nio.common.web.ErrorHandler.handleAllException(java.lang.Exception,javax.servlet.http.HttpServletRequest)
java.io.IOException: UT010029: Stream is closed
at io.undertow.servlet.spec.ServletOutputStreamImpl.write(ServletOutputStreamImpl.java:137) ~[undertow-servlet-1.4.26.Final.jar!/:1.4.26.Final]
at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2039) ~[jackson-core-2.8.11.jar!/:2.8.11]
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.flush(UTF8JsonGenerator.java:1051) ~[jackson-core-2.8.11.jar!/:2.8.11]
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:953) ~[jackson-databind-2.8.11.3.jar!/:2.8.11.3]


SocketTimeoutException

问题:
springboot 开发的下载接口遇到较大的文件,比如 1GB,2GB,3GB 下载过程会中断报错:

Caused by: org.apache.catalina.connector.ClientAbortException: java.net.SocketTimeoutException
    at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:351)

Caused by: java.net.SocketTimeoutException: null
    at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:134)

Spring MVC 中并没有控制请求处理超时时间的配置,默认都是不限制处理时间。
server.tomcat.connection-timeout 是 Tomcat 的一个参数只不过是通过 spring 来设置,它并不是请求的处理超时时间,而是指创建连接后 server 等待 client 发送请求数据的超时时间。

Amount of time the connector will wait, after accepting a connection, for the request URI line to be presented.
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html

解决:
虽然网上解释这个参数与接口处理超时无关,但是设置 server.tomcat.connection-timeout=1800000 后,确实解决了大文件(3GB)下载中断报错的问题。


Orderly Versus Abortive Connection Release in Java

Oracle 官方文档中关于正常 TCP 连接释放和异常 TCP 连接释放的说明

Orderly Versus Abortive Connection Release in Java
https://docs.oracle.com/javase/8/docs/technotes/guides/net/articles/connection_release.html


Socket

package java.net;
public class Socket implements java.io.Closeable {
}

close()

public synchronized void close() throws IOException {
    synchronized(closeLock) {
        if (isClosed())
            return;
        if (created)
            impl.close();
        closed = true;
    }
}

setSoLinger()

在 Java Socket 中,当我们调用 Socket 的 close() 方法时,默认的行为是当底层网卡所有数据都发送完毕后,关闭连接
通过 setSoLinger 方法,我们可以修改 close 方法的行为。

1、on = false
这是默认行为,当 on 为 false 时,linger 对应的设置就没有意义,当 socket 主动 close, 调用的线程会马上返回,不会阻塞,残留在缓冲区中的数据将继续发送给对端,并且与对端进行 FIN-ACK 协议交换,最后进入 TIME_WAIT 状态。

2、on = true, linger > 0
当网卡收到关闭连接请求后,等待 linger 时间
如果在 linger 过程中数据发送完毕,正常四次挥手关闭连接
如果在 delay_time 过程中数据没有发送完毕,发送 RST 包关闭连接

调用 close 的线程将阻塞,发生两种可能的情况:
1是剩余的数据继续接收,进行关闭协议交换;
2就是超时过期,剩余的数据将被删除,进行 FIN-ACK 交换。

3、on = true, linger = 0
这种方式就是所谓 hard-close, 这个方式是讨论或者争论最多的用法,当网卡收到关闭连接请求后,无论数据是否发送完毕,立即发送RST包关闭连接,任何剩余的数据都被立即丢弃,并且 FIN-ACK 交换也不会发生,替代产生 RST ,让对端抛出 Connection reset by peer 的 SocketException 。

public void setSoLinger(boolean on, int linger) throws SocketException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!on) {
        getImpl().setOption(SocketOptions.SO_LINGER, new Boolean(on));
    } else {
        if (linger < 0) {
            throw new IllegalArgumentException("invalid value for SO_LINGER");
        }
        if (linger > 65535)
            linger = 65535;
        getImpl().setOption(SocketOptions.SO_LINGER, new Integer(linger));
    }
}

tcp 参数 so_linger 说明及测试
http://www.4e00.com/blog/linux/2019/03/27/tcp-option-so-linger.html


InetAddress

Java InetAddress 获取本机IP和端口号

IP
String IP = InetAddress.getLocalHost().getHostAddress();

端口号一般指的是 spring 端口号,直接读取配置项即可。

public class MyService {
    @Value("${server.port}")
    private int port;
}

InetAddress.getLocalHost().getHostAddress()获取的ip为127.0.0.1

在 test 环境可以通过 InetAddress.getLocalHost().getHostAddress() 顺利获取本机 ip,但换了一台新的服务器后每次获取的都是 127.0.0.1 了。

原因:
InetAddress.getLocalHost().getHostAddress() 是通过本机名去获取本机ip的
默认情况下本机名是 localhost, 在host文件中对应的ip是127.0.0.1,所以通过这个函数获取到的ip就是127.0.0.1了

解决方法:
1、hostname newhostname 修改主机名(重启后失效)
2、在 /etc/hosts 里加一行
本机IP newhostname

获取本机IP列表

若本机有多个网卡,下面方法可以获取所有的 ip

public static Set<InetAddress> resolveLocalAddresses() {
    Set<InetAddress> addrs = new HashSet<InetAddress>();
    Enumeration<NetworkInterface> ns = null;
    try {
        ns = NetworkInterface.getNetworkInterfaces();
    } catch (SocketException e) {
        // ignored...
    }
    while (ns != null && ns.hasMoreElements()) {
        NetworkInterface n = ns.nextElement();
        Enumeration<InetAddress> is = n.getInetAddresses();
        while (is.hasMoreElements()) {
            InetAddress i = is.nextElement();
            if (!i.isLoopbackAddress() && !i.isLinkLocalAddress() && !i.isMulticastAddress()
                    && !isSpecialIp(i.getHostAddress())) addrs.add(i);
        }
    }
    return addrs;
}

URL

package java.net;
public final class URL implements java.io.Serializable {}

openConnection() 创建连接

返回一个 URLConnection 实例,代表到远程资源对象的一个连接。

每次调用此 URL 的 handler 的 URLStreamHandler.openConnection(URL) 方法都会创建一个新的 URLConnection 实例。

注意:创建 URLConnection 实例时并没有建立真正的网络连接,只有在 URLConnection.connect() 时才会建立网络连接

如果在 java.lang, java.io, java.util, java.net 包或这些包的子包中有 URLConnection 的具体子类,则 openConnection() 返回的就是这些具体子类,例如 HTTP 协议返回的是 HttpURLConnection 类,JAR 协议返回的是 JarURLConnection 类。

public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}

openStream()

url.openStream() 等价于 openConnection().getInputStream()
openConnection() 返回一个 java.net.URLConnection 如果连接的是 Http 的 url, 返回的就是一个 HttpURLConnection 对象,通过其 getInputStream() 方法直接就可以读取 http 资源。

HttpURLConnection 内部会初始化一个 TCP 连接到指定 http 地址,并发送一个 http get 请求,如果返回 200 则可以通过 InputStream 读取 response 内容。

URLConnection

package java.net;
public abstract class URLConnection {}

connect()

IllegalStateException: connect in progress

如果在 URLConnection.connect() 之后继续设置 URLConnection 的属性,就会提示 IllegalStateException: connect in progress, 因为此时已经连接了,无法再修改属性。

Java HttpURLConnection 发送 POST 请求

Java学习–HttpURLConnection发送post请求
https://blog.csdn.net/qq_41117947/article/details/79361094

java java.net.URLConnection 实现http get,post
https://www.cnblogs.com/ooo0/p/9846073.html

Java: how to use UrlConnection to post request with authorization?
https://stackoverflow.com/questions/2026260/java-how-to-use-urlconnection-to-post-request-with-authorization

Java HttpURLConnection 测试 POST 接口连通性

public void postTest(String url) {
    // URL 格式校验
    if (!new UrlValidator().isValid(url)) {
        log.error("URL malformed {}", url);
    }

    // 连通性校验
    try {
        URL url = new URL(url);
        //
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
        // 1秒连接超时
        httpURLConnection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(1));
        // POST 方法
        httpURLConnection.setRequestMethod(HttpMethod.POST.name());
        // "Accept", "*/*"
        httpURLConnection.setRequestProperty(HttpHeaders.ACCEPT, ContentType.WILDCARD.getMimeType());
        // 获取返回码
        HttpStatus httpStatus = HttpStatus.valueOf(httpURLConnection.getResponseCode());
        if (httpStatus.is2xxSuccessful()) {
            Object resp = httpURLConnection.getContent();
            log.info("target url resp code: {}, content: {}", httpStatus, JsonMappers.Normal.toJson(resp));
        } else {
            log.error("target url call fail {}", httpStatus);
        }
    } catch (MalformedURLException e) {
        log.error("URL malformed {}", request.getUrl());
    } catch (IOException e) {
        log.error("URL connection error", e);
    } catch (Exception e) {
        log.error("target url call fail", e);
    }
}

Java下载图片最简单的方式

下载图片为 byte[]

byte[] image = IOUtils.toByteArray(new URL(imageUrl));

使用Java URL下载图片

比如可以直接读取图片:

public static byte[] getImageBytesFromUrl(String imageUrl) {
    DataInputStream dataInputStream = null;
    ByteArrayOutputStream output = null;
    try {
        URL url = new URL(imageUrl);
        dataInputStream = new DataInputStream(url.openStream());

        output = new ByteArrayOutputStream();

        byte[] buffer = new byte[1024];
        int length;

        while ((length = dataInputStream.read(buffer)) > 0) {
            output.write(buffer, 0, length);
        }

        return output.toByteArray();
    } catch (IOException e) {
        logger.error("getImageBytesFromUrl error. imageUrl : " + imageUrl, e);
    } finally {
        IOUtils.closeQuietly(output);
        IOUtils.closeQuietly(dataInputStream);
    }
    return null;
}

HttpURLConnection用法详解
https://www.cnblogs.com/guodongli/archive/2011/04/05/2005930.html


使用SafeHttpURLConnection代替

使用 HttpURLConnection/URLConnection 请求网络资源时,必须使用安全 SDK 的 SafeHttpURLConnection 类替代其完成相关功能

规则说明:
禁止在代码中使用 HttpURLConnection 类或 URLConnection 类请求网络资源。

应使用 Java Web 安全基础库 中的 SafeHttpURLConnection 类完成相关功能。

详细说明:
HttpURLConnection 和 URLConnection 未对参数进行安全校验,极易导致 SSRF 漏洞。

攻击者可通过提交恶意构造的参数对服务器所在内网进行攻击,窃取内网敏感信息,甚至以此服务器为跳板对内网其他脆弱Web应用进行攻击,造成巨大的安全影响和损失。

Java Web 安全基础库 的 SafeHttpURLConnection 类针对 SSRF 漏洞在底层提供了较为完善的保护机制,可防御此类攻击。

正例01:使用安全基础库请求网络资源

//SafeHttpURLConnection类对URLConnection/HttpURLConnection类进行了全面封装,直接替换即可。
URL url = new URL(targetUrl);
SafeHttpURLConnection connection = new SafeHttpURLConnection(url);
//一些set*操作
connection.connect();

反例01:使用HttpURLConnection造成SSRF漏洞

URL url = new URL(targetUrl);
//由HttpURLConnection直接对可控地址targetUrl发起请求,未对内网资源进行拦截,造成SSRF漏洞
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//一些set*操作
connection.connect();

反例02:使用URLConnection造成SSRF漏洞

try {
    URL url = new URL(targetUrl);
    //URLConnection直接对可控地址targetUrl发起请求,未对内网资源进行拦截,造成SSRF漏洞
    URLConnection connection = url.openConnection();
    System.out.println("Content-Type: " + connection.getContentType());
} catch (IOException e) {
    e.printStackTrace();
}

HttpURLConnection

package java.net;
abstract public class HttpURLConnection extends URLConnection {}

getResponseCode()

获取 HTTP 状态码,无法获取 HTTP 状态码时返回 -1

public int getResponseCode() throws IOException {
    /*
     * We're got the response code already
     */
    if (responseCode != -1) {
        return responseCode;
    }

    /*
     * Ensure that we have connected to the server. Record
     * exception as we need to re-throw it if there isn't
     * a status line.
     */
    Exception exc = null;
    try {
        getInputStream();
    } catch (Exception e) {
        exc = e;
    }

    /*
     * If we can't a status-line then re-throw any exception
     * that getInputStream threw.
     */
    String statusLine = getHeaderField(0);
    if (statusLine == null) {
        if (exc != null) {
            if (exc instanceof RuntimeException)
                throw (RuntimeException)exc;
            else
                throw (IOException)exc;
        }
        return -1;
    }

    /*
     * Examine the status-line - should be formatted as per
     * section 6.1 of RFC 2616 :-
     *
     * Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase
     *
     * If status line can't be parsed return -1.
     */
    if (statusLine.startsWith("HTTP/1.")) {
        int codePos = statusLine.indexOf(' ');
        if (codePos > 0) {

            int phrasePos = statusLine.indexOf(' ', codePos+1);
            if (phrasePos > 0 && phrasePos < statusLine.length()) {
                responseMessage = statusLine.substring(phrasePos+1);
            }

            // deviation from RFC 2616 - don't reject status line
            // if SP Reason-Phrase is not included.
            if (phrasePos < 0)
                phrasePos = statusLine.length();

            try {
                responseCode = Integer.parseInt
                        (statusLine.substring(codePos+1, phrasePos));
                return responseCode;
            } catch (NumberFormatException e) { }
        }
    }
    return -1;
}

上一篇 Helm

下一篇 MyCat

阅读
评论
3.8k
阅读预计17分钟
创建日期 2020-09-08
修改日期 2021-12-15
类别
标签

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论