okhttp框架之通信过滤Socket域名
HttpClient: 在android 5.0就被从源码中移除了
HttpUrlConnection: 偏底层,不适合直接使用,封装起来也比较麻烦。
Volley:适合数据量小但是频繁的网络操作,对大文件下载表现糟糕。
Okhttp:目前主推的网络库,全面支持各种网络请求、文件上传下载;性能高效,底层线程池提高请求的复用性;优秀的代码设计。但是也需要进行二次封装。
Retrofit:对Okhttp的二次封装。
这些网络是是如何调用到TCP/IP socket的呢, 下面就简单分析okhttp源码,解析TCP/IP socket 流程。
在某些工程的设计中,由于历史原因,可能需要兼容不同的网络库,主要思路就是做个中间层,能够切换不同的网络库。
1- okhttp 框架
框架图:
上图是OkHttp的总体架构,大致可以分为以下几层:
Interface——接口层:接受网络访问请求
Protocol——协议层:处理协议逻辑
Connection——连接层:管理网络连接,发送新的请求,接收服务器访问
Cache——缓存层:管理本地缓存
I/O——I/O层:实际数据读写实现
Inteceptor——拦截器层:拦截网络访问,插入拦截逻辑
调用流程图:
主要核心就是拦截器
参考:https://square.github.io/okhttp/interceptors/
http封装:
2- okhttp 调用socket
BridgeInterceptor:
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//添加请求头信息
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
下面我们看Socket是如何连接的,这就要看ConnectInterceptor类
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//这个newStream开始进行连接的
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
newStream:
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//这个方法是找到一个合适的连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//创建一个HttpCodec,用于Encodes HTTP requests and decodes HTTP responses请求编码和响应解码
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
findHealthyConnection:
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
findConnection:
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
...省略代码、通过判断去查找或者创建一个RealConnection
// Do TCP + TLS handshakes. This is a blocking operation.
//这个地方开始连接
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
connect:
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
EventListener eventListener) {
...省略代码
while (true) {
try {
//如果是通过Https代理的时候返回true
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
// 如果不使用代理,走的是这个逻辑
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
...省略代码关闭连接、创建异常
}
if (route.requiresTunnel() && rawSocket == null) {
ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
+ MAX_TUNNEL_ATTEMPTS);
throw new RouteException(exception);
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
connectSocket:
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
rawSocket.setSoTimeout(readTimeout);
try {
//根据平台进行socket连接
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
//为输入输出流赋值
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
我们是在Android下所以就是AndroidPlatform,代码如下
@Override public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
try {
//进行网络连接
socket.connect(address, connectTimeout);
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} catch (SecurityException e) {
// Before android 4.3, socket.connect could throw a SecurityException
// if opening a socket resulted in an EACCES error.
IOException ioException = new IOException("Exception in connect");
ioException.initCause(e);
throw ioException;
} catch (ClassCastException e) {
// On android 8.0, socket.connect throws a ClassCastException due to a bug
// see https://issuetracker.google.com/issues/63649622
if (Build.VERSION.SDK_INT == 26) {
IOException ioException = new IOException("Exception in connect");
ioException.initCause(e);
throw ioException;
} else {
throw e;
}
}
}
2- AndroidPlatform 继续分析
connect:
libcore/ojluni/src/main/java/java/net/Socket.java
public void connect(SocketAddress endpoint, int timeout){
InetSocketAddress epoint = (InetSocketAddress) endpoint;
InetAddress addr = epoint.getAddress ();
int port = epoint.getPort();
checkAddress(addr, "connect");
//这里可以侦测到app 访问了那些域名,端口是多少,pid
//根据pid ,系统/proc/pid/cmdline 含有了包名
System.out.println(addr); //打印server 地址
System.out.println(epoint.getHostName()); //打印的hostname
System.out.println(port); //打印端口
System.out.println("pid="+Libcore.os.getpid()); //打印PID
impl.connect(addr, port);
}
3-继续分析socket.getOutputStream())
其他使用方式
String path = "/getDemo.php";
SocketAddress dest = new InetSocketAddress(this.host, this.port);
socket.connect(dest);
OutputStreamWriter streamWriter = new OutputStreamWriter(socket.getOutputStream());
bufferedWriter = new BufferedWriter(streamWriter);
bufferedWriter.write("GET " + path + " HTTP/1.1\r\n");
bufferedWriter.write("Host: " + this.host + "\r\n");
bufferedWriter.write("\r\n");
bufferedWriter.flush();
getOutputStream如何调用的呢?
libcore/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
socket.getOutputStream() 重点,要看这里是如何实现的,能否在这里进行字段的过滤:
-->impl.getOutputStream() [impl: SocketImpl ], getOutputStream就是SocketImpl子类实现的
-->class AbstractPlainSocketImpl extends SocketImpl
-->getOutputStream [AbstractPlainSocketImpl.java]
-->SocketOutputStream extends FileOutputStream
-->FileOutputStream
--->write [对应上面的bufferedWriter.write("Host: " + this.host + "\r\n"); ]
---> IoBridge.write(fd, b, off, len);
IoBridge: 不在继续追查
okhttp3.1.2-org-source-code
链接:https://pan.baidu.com/s/1zMKVtP43nL37uZFDn5kj4Q
提取码:e4pn
REF:
https://www.jianshu.com/p/5ea4b7108168
https://blog.csdn.net/qqqq245425070/article/details/100162964
https://www.jianshu.com/p/7491e9d4c236