Android中使用Retrofit2请求出现SocketTimeoutException的原因
问题描述
在Android中使用Retrofit2调用服务时,抛出**SocketTimeoutException: 连接超时**错误,请求的服务地址为 http://182.20.1.14:2020/api/MainAppExternal/UpdateFcmId。
相关代码实现:
private Retrofit retrofit(String url, boolean addLogInterceptor, int timeout) { return new Retrofit.Builder() .baseUrl(url).client(getOkHttpClient(addLogInterceptor, timeout)) .addConverterFactory(GsonConverterFactory.create(LocalGsonBuilder.getInstance().getGson())) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); } private OkHttpClient getOkHttpClient(boolean addLogInterceptor, int timeout) { OkHttpClient.Builder httpClient = new OkHttpClient.Builder() .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS); setupInterceptor(httpClient, addLogInterceptor); setupTimeout(httpClient, timeout); return httpClient.build(); } private void setupInterceptor(OkHttpClient.Builder httpClient, boolean addLogInterceptor) { if (addLogInterceptor) { httpClient.addInterceptor(new ConnectionInterceptor()); } }
完整异常堆栈信息:
exception : connect timed outjava.net.SocketTimeoutException: connect timed out at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:341)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:196)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:178)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:356)
at java.net.Socket.connect(Socket.java:650)
at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:63)
at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:223)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:149)
at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:192)
at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)
at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at com.dgpays.mpos.common.common.ConnectionInterceptor.intercept(ConnectionInterceptor.java:84)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
at okhttp3.RealCall.execute(RealCall.java:69)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:41)
at io.reactivex.Observable.subscribe(Observable.java:12267)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:12267)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
异常原因解析
SocketTimeoutException: connect timed out表示客户端在设定的时间内无法与目标服务器完成TCP连接建立,常见诱因分为以下几类:
1. 网络环境层面
- 设备当前网络信号弱(如偏远地区移动数据、Wi-Fi信号不稳定),导致TCP三次握手无法完成
- 设备处于代理网络环境,代理配置错误或代理服务器自身不可用
- 本地防火墙、路由器或运营商网络拦截了对目标IP和端口的连接请求
2. 服务器端层面
- 目标服务器
182.20.1.14未启动,或2020端口对应的服务已崩溃 - 服务器的防火墙/安全组未开放
2020端口,拒绝外部设备的连接请求 - 服务器负载过高,线程池已满,无法及时处理新的连接请求
3. 客户端配置层面
- 代码中
CONNECTION_TIMEOUT常量设置过短,TCP连接还未完成握手就触发超时 setupTimeout方法可能覆盖了初始设置的连接超时参数,导致实际超时时间不符合预期- 自定义的
ConnectionInterceptor可能修改了请求头、URL或拦截了连接流程,影响正常连接
排查建议
- 验证网络连通性:在设备或电脑上执行
ping 182.20.1.14测试能否连通服务器,用telnet 182.20.1.14 2020测试端口是否开放 - 确认服务器状态:联系后端人员确认服务是否正常运行,
2020端口是否在监听,安全组是否允许当前设备的IP访问 - 调整超时配置:临时将
CONNECTION_TIMEOUT调至30秒,观察是否还会出现超时 - 排查自定义拦截器:暂时移除
ConnectionInterceptor,测试请求是否正常,排除拦截器的影响 - 切换网络测试:将设备切换到其他网络(如从Wi-Fi切换到移动数据),确认是否是当前网络的问题
内容的提问来源于stack exchange,提问作者ridvan

