网络相关笔记

文章目录

    • 前言
    • 一、七层网络协议
    • 二、三次握手四次挥手
    • 三、TCP与UDP
    • 四、TCP与HTTP
    • 五、HTTP与Socket
    • 六、HTTP与HTTPS
    • 七、SSL与TLS
    • 八.Tomcat配置HTTPS
    • 八.SpringBoot配置https访问
    • 九、Android客户端访问Https

前言

上周自己玩的微信小程序打包发布体验版的时候发现微信平台需要配置的合法域名固定了以https开头,所以自己立即在阿里云服务器的购买一个0元的ssl证书。阿里云文档上配置https的方法很简洁,但由于自己忘记了在安全组配置443端口导致一直访问不了。在了解https很http的过程中发现大学时期的网络知识基本都忘光了,所以这里整理了一份笔记。

一、七层网络协议

网络相关笔记_第1张图片

  1. 物理层:硬件相关,光缆、路由器…
  2. 数据链路层:以太网协议Ethernet。定义一组电信号的含义(数据包/帧)。每帧=head(发送者mac/接收者mac/数据类型)+data(数据包的具体内容)。基于局域网,传输方式:广播。比如在办公室:嗨,UI,我是Android,给我个LOGO!UI接收到之后就把LOGO发过来了。
  3. 网络层:IP协议等。数据链路层是在局域网,在办公室喊的时候其他人能听到,但是外边的人就听不到了。比如Android跟UI在不同的办公室。IP地址就是用来标示每个办公室的。IP+mac就能准确的找到具体的人。比如Android找UI要LOGO的时候,大喊一声:嗨,UI,我是Android,给我个LOGO!(发广播)如果在办公室找不到,负责人就会根据IP去到对应的办公室,然后再根据mac地址找到UI。(任何寻找目标mac)
  4. 传输层:涉及到了上面说的TCP/UDP协议了。网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,端口号帮我们寻找到服务。找一台主机根据可以根据:IP+mac。寻找一台主机的服务根据:IP+mac+端口。比如访问启动的tomcat,默认是127.0.0.1:8080。
  5. 会话层:建立和管理应用程序之间的通信。自动收发包,自动寻址。
  6. 表示层:用Linux给window发包,两个系统语法不一致,就像安装包一样,exe是不能在linux下用的,shell在window下也是不能直接运行的。于是需要表示层(presentation),帮我们解决不同系统之间的通信语法问题。
  7. 应用层:如Android、IOS、Web等。

二、三次握手四次挥手

三次握手:
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

摘录百度百科的三次握手,好难记啊,自己理解一下:
第一次握手:客户端问服务器:服务器端,你在吗?
第二次握手:服务器端回答客户端:客户端,我在的。
第三次握手:客户端回答服务器:好的。(确立连接)
当服务器端接到客户端回应的“好的”的时候连接就建立了,这时候两者之间就可以互相传输数据了。

仅仅两次握手问题:
第一次握手:客户端问服务器:服务器端,你在吗?
第二次握手:服务器端回答客户端:客户端,我在的。(确立连接)

这种情况下客户端发送给服务器端可能会出现一种情况:客户端给服务器端传输的报文在传输的过程中,报文由于某种原因“偷懒了一刻钟”,这段时间内客户端与服务器端有可能已经断卡了连接,“一刻钟”之后“偷懒”的报文继续传输到到服务器端,这时候由于之前的连接已经断开,这个报文会被认为是新的连接请求:“服务器端,你在吗?‘,然后服务器依然回答"客户端,我在的。(确立连接)"。可是由于客户端已经断开了,所以不会回复服务器端,那么服务器端就一直在等待着与客户端进行数据传输,这样容易造成资源浪费。

四次挥手:
第一次挥手:TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
第二次挥手:服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
第三次挥手:服务器关闭客户端的连接,发送一个FIN给客户端。
第四次挥手:客户端发回ACK报文确认,并将确认序号设置为收到序号加1。

这玩意一样不好记,自己理解:
第一次挥手:客户端告诉服务器端:我要跟你分手,我这边已经没有数据要传给你了,分手之后我们互相就不要再传输数据了。
第二次挥手:服务器端回应客户端:太突然了,我有些数据可能还没传输完呢,让我再想想。(这个时候服务器依然还能向客户端传输数据)
第三次挥手:服务器端再次回复客户端:我这边的数据也传输完成了,我同意分手了。
第四次挥手:客户端接收到之后回复服务器端:好的。(服务器端接收到之后就断开连接了)
(客户端回复的时候等待2MSL时间,2MSL内没收到服务器端任何回复自己也断开连接。)

这里会产生两个问题(标准答案看这里):
1.为什么创建连接的时候是操作三次,断开连接的时候要操作四次:
答:分手需要时间,比如先把朋友圈、空间秀恩爱说说删除,然后再跟渣男说,是我pass的你。所以听到说分手的时候先回应一下,准备好了(比如数据传输完成)再回应一下。

2.最后一次挥手,为什么客户端需要等待2MSL时间?
答:2MSL(最大报文段生存时间),最后一次挥手的过程中,传送的报文有可能“偷懒”或者“迷路丢失”了,第三次挥手之后服务器端如果没接收到客户端任何回复的话会继续重新回复。等待的2MSL时间,就有足够的时间重发可能丢失的ACK报文。

三、TCP与UDP

UDP
全称:User Datagram Protocol,直接翻译:用户数据报协议。是一种传输层的协议。无连接。采用UDP进行通信时不用建立连接,可以直接向一个IP地址发送数据,但是不能保证对方是否能收到。个人理解就相当于广播一下,你只管发送广播,客户端接不接收不用管它,所以它传输相对快,但丢包率很高。
特点:

  1. 面向无连接的
  2. 程序结构较简单
  3. 基于数据报
  4. 可能丢包
  5. 不保证数据顺序

使用场景:
音频、视频、即时通讯、直播

TCP
全称:Transmission Control Protocol,直接翻译:传输控制协议。是一种面向连接的、可靠的、基于字节流的传输层通信协议。利用TCP进行通信时,首先要通过三步握手。
特点:

  1. 全双工
  2. 是面向连接
  3. 是面向字节流的
  4. 保证数据正确性
  5. 保证数据顺序

使用场景:网页浏览、文件下载(不是BT、电脑下载)、邮件的发送。

四、TCP与HTTP

HTTP:超文本传输协议。是一个简单的请求-响应协议,它通常运行在TCP之上,请求和响应消息的头以ASCII码形式给出。世界上几乎所有的HTTP通信都是由TCP/IP承载的。
HTTP1.0、HTTP2.0相关

五、HTTP与Socket

http:简单的对象访问协议,对应于应用层。Http协议是基于TCP链接的。
Socket:是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。

Http连接:短连接,客户端向服务器发送一次请求,服务器端相应后连接即会断掉。
socket连接:长链接,理论上客户端和服务端一旦建立连接,则不会主动断掉

六、HTTP与HTTPS

HTTP协议是一种使用明文数据传输的网络协议。
HTTPS协议可以理解为HTTP协议的升级,就是在HTTP的基础上增加了数据加密。HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

七、SSL与TLS

八.Tomcat配置HTTPS

上周根据阿里云服务器的文档配置:
申请证书->上传证书到服务器->tomcat配置证书
网络相关笔记_第2张图片
网络相关笔记_第3张图片

 <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="443" />

   <Connector port="443"
    protocol="HTTP/1.1"
    SSLEnabled="true"
    scheme="https"
    secure="true"
    keystoreFile="/usr/tomcat/apache-tomcat-7.0.85/conf/itnewers.com.pfx"   
    keystoreType="PKCS12"
    keystorePass="x0c4f6XT"   
    clientAuth="false"
    SSLProtocol="TLS"/>

按照这个流程配置,使用https访问的时候怎么也访问不了,最蛋疼的是网上的帖子基本也都是这么配置的。周末打了两天“农药”,周一打开电脑的时候突然想起来访问80端口的时候需要在阿里云服务器的安全组配置进入的端口号,那么我这里使用了redirectPort="443"是不是也需要在安全组配置一个443的端口号,如下图:
网络相关笔记_第4张图片
配置了443端口号之后意外地发现:

//访问不成功
https://域名:8080
//访问成功
https://域名:443
//访问成功
https://域名

结果如下图:
网络相关笔记_第5张图片

为什么呢?
查看很多帖子都说redirectPort是重定向到某个接口,比如说上面的

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="443" />

是访问8080端口会重定向到443端口,并且我也是这么认为的,但实际上https用法好像又不是这样,从结果上看是:
http访问的时候使用8080端口,https访问的时候使用443端口。(这里其实还是不太明白)

令人奇怪的是使用https访问不需要端口号也能正常访问?
答:http协议的默认端口是80,https的默认端口是443,这里将端口改为了443,访问https时可不用加端口号。

八.SpringBoot配置https访问

将下载好的证书复制到src/main/resoureces下,然后在application.properties内配置证书信息:

# 配置Https访问
server.ssl.key-store=classpath:itnewers.com.pfx
server.ssl.key-store-password=x0c4f6XT
server.ssl.key-store-type=PKCS12

如下图:
网络相关笔记_第6张图片运行结果如下:
网络相关笔记_第7张图片但是这时候是不支持使用http访问的,火狐使用http访问会报

Bad Request
This combination of host and port requires TLS.

想要同时支持https、http还需要进行如下配置:
1.application.properties内添加:

server.http.port=8082

由于8081端口已经被https使用,http就不能再使用那个端口,所以配置8082端口给给http使用。

2.添加TomcatConfig类:

package com.streamlet;

import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TomcatConfig {

	@Value("${server.http.port}")
	private int httpPort;

	@Bean
	public EmbeddedServletContainerCustomizer containerCustomizer() {
		return new EmbeddedServletContainerCustomizer() {
			@Override
			public void customize(ConfigurableEmbeddedServletContainer container) {
				if (container instanceof TomcatEmbeddedServletContainerFactory) {
	                TomcatEmbeddedServletContainerFactory containerFactory =
	                        (TomcatEmbeddedServletContainerFactory) container;
	                Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
	                connector.setPort(httpPort);
	                containerFactory.addAdditionalTomcatConnectors(connector);
	            }
				
			}};

	}
}

运行结果如下
网络相关笔记_第8张图片

九、Android客户端访问Https

1.无证书方式:

/**
 * @author Linxz
 * 创建日期:2019年02月20日 20:11
 * version:1.0.0
 * 描述:
 */
public class LinxzHttpCreator {
    /**
     * 构建Http
     * */
    private static final class OkHttpHolder {
        private static final int TIME_OUT = 60;
        private static final OkHttpClient.Builder BUILDER = new OkHttpClient.Builder();
        private static final ArrayList<Interceptor> INTERCEPTORS = Linxz.getConfiguration(ConfigKeys.INTERCEPTOR);
        private static OkHttpClient.Builder addInterceptor() {
            if (INTERCEPTORS != null && !INTERCEPTORS.isEmpty()) {
                for (Interceptor interceptor : INTERCEPTORS) {
                    BUILDER.addInterceptor(interceptor);
                }
            }
            return BUILDER;
        }

        /**
         * http请求
         * */
        private static final OkHttpClient OK_HTTP_CLIENT = addInterceptor()
                .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
                .build();


        /**
         * https请求
         * */
        private static final OkHttpClient OK_HTTPS_CLIENT = httpsBuilder()
                .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
                .build();


        private static OkHttpClient.Builder httpsBuilder(){
            try {
                TrustManager[] trustAllCerts = buildTrustManagers();
                final SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

                final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
                OkHttpClient.Builder builder =addInterceptor();

                builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
                builder.hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                return builder;
            } catch (NoSuchAlgorithmException | KeyManagementException e) {
                e.printStackTrace();
                return new OkHttpClient.Builder();
            }
        }

        private static TrustManager[] buildTrustManagers() {
            return new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };
        }
    }




    /**
     * 构建全局Retrofit客户端
     */
    private static final class RetrofitHolder {
        private static final Retrofit RETROFIT_CLIENT = new Retrofit.Builder()
                .baseUrl(HttpConfig.BASE_URL)
                .client(OkHttpHolder.OK_HTTP_CLIENT)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }

    private static final class HttpStoreHolder {
        private static final HttpStore REST_SERVICE = RetrofitHolder.RETROFIT_CLIENT.create(HttpStore.class);
    }

    public static HttpStore getHttpStore(){
        return HttpStoreHolder.REST_SERVICE;
    }

    public static OkHttpClient getHttpsClent(){
        return OkHttpHolder.OK_HTTPS_CLIENT;
    }

}

2.需要p12证书方式:

public class SSLHelper {
    private static final String KEY_STORE_TYPE_BKS = "bks";
    private static final String KEY_STORE_TYPE_P12 = "PKCS12";

    /**
     * 记得添加相应的证书到assets目录下面
     */
    private static final String KEY_STORE_CLIENT_PATH = "client.p12";//P12文件
    private static final String KEY_STORE_TRUST_PATH = "client.truststore";//truststore文件
    private static final String KEY_STORE_PASSWORD = "自己的P12文件密码";//P12文件密码
    private static final String KEY_STORE_TRUST_PASSWORD = "自己的truststore文件密码";//truststore文件密码

    private static final String KEY_CRT_CLIENT_PATH = "xxx.crt";//CRT文件

    private static boolean isServerTrusted = false;

    private static SSLContext sslContext = null;

    public static SSLContext getSslContext(Context context) {
        if (sslContext == null) {
            try {
                // 服务器端需要验证的客户端证书
                KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
                // 客户端信任的服务器端证书
                KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);

                InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
                InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
                try {
                    keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
                    trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        ksIn.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    try {
                        tsIn.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                sslContext = SSLContext.getInstance("TLS");
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManagerFactory.init(trustStore);
                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
                keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
                sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sslContext;
    }

    public static SSLContext getSslContextByDefaultTrustManager(Context context) {
        if (sslContext == null) {
            try {
                // 服务器端需要验证的客户端证书
                KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);

                InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
                try {
                    keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        ksIn.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                sslContext = SSLContext.getInstance("TLS");
                // Create a TrustManager that trusts the CAs in our KeyStore
                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
                tmf.init(keyStore);

                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
                keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
                sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sslContext;
    }

    public static SSLContext getSslContextByCustomTrustManager(Context context) {
        if (sslContext == null) {
            try {
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                InputStream caInput = new BufferedInputStream(context.getResources().getAssets().open(KEY_CRT_CLIENT_PATH));
                final Certificate ca;
                try {
                    ca = cf.generateCertificate(caInput);
                } finally {
                    caInput.close();
                }

                sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new X509TrustManager[]{new X509TrustManager() {
                    public void checkClientTrusted(X509Certificate[] chain,
                                                   String authType) throws CertificateException {
                        Log.d("SSLHelper", "checkClientTrusted --> authType = " + authType);
                        //校验客户端证书
                    }

                    public void checkServerTrusted(X509Certificate[] chain,
                                                   String authType) throws CertificateException {
                        Log.d("SSLHelper", "checkServerTrusted --> authType = " + authType);
                        //校验服务器证书
                        for (X509Certificate cert : chain) {
                            cert.checkValidity();
                            try {
                                cert.verify(ca.getPublicKey());
                                isServerTrusted = true;
                            } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
                                e.printStackTrace();
                                isServerTrusted = false;
                            }
                        }
                    }

                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }}, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sslContext;
    }
}
public class UnSafeHostnameVerifier implements HostnameVerifier {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
}

fun okHttpsClientBuilder(): OkHttpClient.Builder {
    val builder = okHttpClientBuilder()
    if (SSLHelper.getSslContext(JBT.getApplicationContext()) != null) {
        SSLHelper.getSslContext(JBT.getApplicationContext()).socketFactory.let {
            builder.sslSocketFactory(it)
                    .hostnameVerifier(UnSafeHostnameVerifier())
        }
    }
    return builder
}

3.Glide加载https图片:

/**
 * @author Linxz
 * 创建日期:2020年03月23日 16:07
 * version:v4.5.4
 * 描述:Glide加载Https失败问题
 */
@GlideModule
public final class HttpsGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        super.applyOptions(context, builder);
    }

    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        OkHttpClient okHttpClient= LinxzHttpCreator.getHttpsClent();
        registry.replace(GlideUrl.class, InputStream.class,new OkHttpUrlLoader.Factory(okHttpClient));
    }
}

你可能感兴趣的:(Android开发,java开发,Web)