网络基础(Socket通讯)

目录

网络分层

网络分层——来源于极客时间之趣谈网络协议

1,应用层:如常用的http协议属于应用层,定义了数据的包装与解析规则。
2,传输层:包含TCP与UDP协议,这一层通常使用java封装的Socket类进行数据的发送与接收。
3,网络层:包含常用的IP协议以及路由协议。这一层用于指定数据的IP传输规则,包含传输路线,路由选择等。
4,链路层:包含常见的ARP协议,负责将IP地址解析为Mac地址(硬件地址),从而找到对应的设备
5,物理层:这一层是在物理设备上进行数据的传输,包含有线与无线通信。

在地址栏输入url后的流程?
1,使用DNS(域名系统)解析域名,从而获取url中的IP地址
2,使用IP地址与本地默认的端口(80)建立TCP连接(3次握手)
3,建立TCP连接后发起http请求
4,服务器收到请求并响应HTTP请求,将对应的html文本发送给浏览器
5,浏览器解析htm代码,并请求htm代码中的资源(如js、css图片等)
6,断开TCP连接(4次挥手)
7,浏览器对页面进行渲染呈现给用户

HTTP

1,HTTP(超文本传输协议):是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,默认端口号为80。特点如下:

1,简单快速:客户向服务器请求数据时,只需传送请求方法和路径。由于传输内容少,故通信速度相对较快。
2,灵活:HTTP允许传输任意类型的数据对象。传输的类型由Content-Type加以标记
3,无连接:每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。由于使用TCP连接,这种方式比较耗时并且效率不高。
4,无状态:协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。可以使用Cookie来解决无状态的问题,Cookie就相当于一个通行证,第一次访问的时候给客户端发送一个Cookie,当客户端再次来的时候,拿着Cookie(通行证),那么服务器就知道这个是”老用户“。

2,http协议的组成

  • 请求报文:请求行/请求头/请求体

请求行:包含请求方法get/post、URI、HTTP版本信息(如:http1.0)
请求头:即header,里面包含很多字段。比如使用Accept-Language可以设置返回数据的语言类型。
请求体:发送的数据。比如get、post请求的参数。

  • 响应报文:状态行/响应头/响应体

状态行:包含HTTP版本,状态码,状态码原因短语。通常使用状态码判断结果。
响应头:返回的header
响应体:请求服务器返回的数据。通常是我们需要使用的数据

  • 常用的http头部字段
  • 通用首部字段(请求报文与响应报文都会使用的首部字段)
    Date:创建报文时间
    Connection:连接的管理
    Cache-Control:缓存的控制
    Transfer-Encoding:报文主体的传输编码方式
  • 请求首部字段(请求报文会使用的首部字段)
    Host:请求资源所在服务器
    Accept:可处理的媒体类型
    Accept-Charset:可接收的字符集
    Accept-Encoding:可接受的内容编码
    Accept-Language:可接受的自然语言
  • 响应首部字段(响应报文会使用的首部字段)
    Accept-Ranges:可接受的字节范围
    Location:令客户端重新定向到的URI
    Server:HTTP服务器的安装信息
  • 实体首部字段(请求报文与响应报文的的实体部分使用的首部字段)
    Allow:资源可支持的HTTP方法
    Content-Type:实体主类的类型
    Content-Encoding:实体主体适用的编码方式
    Content-Language:实体主体的自然语言
    Content-Length:实体主体的的字节数
    Content-Range:实体主体的位置范围,一般用于发出部分请求时使用

3,请求方式:

GET:用于请求访问已经被URI(统一资源标识符)识别的资源,可以通过URL传参给服务器
POST:用于传输信息给服务器,主要功能与GET方法类似,但一般推荐使用POST方式
PUT:传输文件,报文主体中包含文件内容,保存到对应URI位置
HEAD:获得报文首部,与GET方法类似,只是不返回报文主体,一般用于验证URI是否有效
DELETE:删除文件,与PUT方法相反,删除对应URI位置的文件
OPTIONS:查询响应URI支持的HTTP方法

  • get重点在从服务器上获取资源,post重点在向服务器发送数据;
  • get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用"?"连接,多个请求数据间用"&"连接,如:http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的;post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的;
  • get传输的数据量小,因为受URL长度限制,但效率较高;post可以传输大量数据,所以上传文件时只能用Post方式;
  • get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等;post较get安全性较高;
  • get方式只能支持ASCII字符,向服务器传的中文字符可能会乱码。post支持标准字符集,可以正确传递中文字符。

4,常用的响应码(可以查看HttpURLConnection类中定义的常量)

1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求

200:请求被正常处理
204:请求被受理但没有资源可以返回
206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
301:永久性重定向
302:临时重定向
303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
304:发送附带条件的请求时,条件不满足时返回,与重定向无关
307:临时重定向,与302类似,只是强制要求使用POST方法
400:请求报文语法有误,服务器无法识别
401:请求需要认证
403:请求的对应资源禁止被访问
404:服务器无法找到对应资源
500:服务器内部错误
503:服务器正忙

5,缓存机制:主要是使用header中的字段:Cache-control与ETag 来控制

Okhttp中对于网络请求的缓存使用的就是Http自带的的缓存机制
Volley则是自己实现一套缓存策略

Cache-control主要包含以及几个字段:

  • private:则只有客户端可以缓存
  • public:客户端和代理服务器都可以缓存
  • max-age:缓存的过期时间
  • no-cache:需要使用对比缓存来验证缓存数据
  • no-store:所有内存都不会进行缓存

ETag:即用来进行对比缓存,Etag是服务端资源的一个标识码,当客户端发送第一次请求时服务端会下发当前请求资源的标识码Etag,下次再请求时,客户端则会通过header里的If-None-Match将这个标识码Etag带上,服务端将客户端传来的Etag与最新的资源Etag做对比,如果一样,则表示资源没有更新,返回304。通过Cache-control和Etag的配合来实现Http的缓存机制。

6,版本差异:

http1.1的特点:

  • 默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求(即长连接)
  • 管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应
  • 断点续传,实际上就是利用HTTP消息头使用分块传输编码,将实体主体分块传输。

http2.0特点:

  • 二进制格式:http1.x是文本协议,而http2.0是二进制以帧为基本单位,是一个二进制协议,一帧中除了包含数据外同时还包含该帧的标识:Stream Identifier,即标识了该帧属于哪个request,使得网络传输变得十分灵活。
  • 多路复用:多个请求共用一个TCP连接,多个请求可以同时在这个TCP连接上并发,一个是解决了建立多个TCP连接的消耗问题,一个也解决了效率的问题。那么是什么原理支撑多个请求可以在一个TCP连接上并发呢?基本原理就是上面的二进制分帧,因为每一帧都有一个身份标识,所以多个请求的不同帧可以并发的无序发送出去,在服务端会根据每一帧的身份标识,将其整理到对应的request中。
  • header头部压缩:主要是通过压缩header来减少请求的大小,减少流量消耗,提高效率。
  • 支持服务端推送:服务器主动向客户端推送消息。

7,https : 是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP+SSL,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。使用的是非对称加密算法。公钥加密的信息只能用私钥解开,而私钥加密的信息也只能被公钥解开。

http与https的对比:
1,http是超文本传输协议,信息是明文传输;https则是具有安全性的ssl加密传输协议。
2,http和https使用的是完全不同的连接方式,用的端口也不一样。前者直接运行在TCP上,默认端口号为80,后者运行在SSL/TLS之上,SSL/TLS运行在TCP之上,默认端口为443。

TCP与UDP

1,TCP与UDP的区别

  • UDP面向无连接,面向报文,数据不安全,速度快,不区分客户端与服务端,最大读取64K
  • TCP面向连接,面向字节流,数据安全,速度相应慢,区分客户端与服务端,是HTTP的底层实现机制。

2,TCP的三次握手与四次挥手

  • 三次握手:
    1,客户端向服务端请求连接,
    2,服务端向客户端回复收到了,此时只能证明客户端到服务端畅通
    3,客户端收到服务端的回应,再给服务端发通知,我收到了,证明服务端到客户端是畅通。
  • 四次挥手:
    1,客户端发送完成后,发送数据FIN通知服务端,客户端要关闭了
    2, 服务端关闭读取数据的功能并回应数据ACK,此时客户端发关闭写的功能
    3,服务端写完成发送FIN通知客户端,服务端也要关闭,次数服务端关闭写的功能,
    4,客户端收到通知后关闭读的功能并发送ACK回应服务端。

补充小知识:常见的标识符合
SYN 是发起一个连接
ACK 是回复
RST 是重新连接
FIN 是结束连接

3,TCP的滑动窗口协议:是保证TCP的可靠传输的根本,因为发送窗口只有收到确认帧才会向后移动窗口继续发送其他帧。

  • 停止-等待协议:每发一帧都要等到确认消息才能发送下一帧,缺点:效率较差。
  • 后退N帧协议:采取累计确认的方式,接收方正确的接受到N帧后发一个累计确认消息给发送窗口,确认N帧已正确收到,如果发送方规定时间内未收到确认消息则认为超时或数据丢失,则会重新发送确认帧之后的所有帧。缺点:出错序号后面的PDU已经发送过了,但是还是要重新发送,比较浪费。
  • 选择重传协议:若出现差错,只重新传输出现差错涉及需要的PDU,提高了传输效率,减少不必要的重传。

4,TCP的流量控制与拥塞控制:

  • 流量控制:是对一条通信路径上的流量进行控制,就是发送方通过获取接收方的回馈来动态调整发送的速率,来达到控制流量的效果,其目的是保证发送者的发送速度不超过接收者的接收速度。
  • 拥塞控制:对网络中的路由和链路传输进行速度限制,避免网络过载;包含四个过程:慢启动、拥塞避免、快重传和快恢复
慢开始+拥塞避免
慢开始+快恢复

Socket通讯

Socket(套接字):本质上是对TCP/UDP操作进行封装的API,从而实现TCP/UDP的网络通讯。特点:
1,网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
2,通信的两端都有Socket。
3,网络通信其实就是Socket间的通信。
4,数据在两个Socket间通过IO流传输。
5,Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port。

  • UDP通讯常用的类:
    DatagramSocket:此类表示用来发送和接收数据报包的套接字。
    DatagramPacket:封装的数据报文,包含数据内容,ip,端口号。
  • TCP通讯中使用的类:
    Socket:用于客户端创建套接字进行封装目标ip和端口号
    ServerSocket:用于服务端指定监听端口。通过accept()方法获取Scoket对象,然后进行数据读写。
//UDP发送数据(在子线程的run方法中执行)
public void run() {
    DatagramSocket scoket = null;
    try {
        scoket = new DatagramSocket();
        Log.d(TAG, "run : ip1 = " + scoket.getLocalAddress().getHostName());
        InetAddress inetAddress = InetAddress.getByName(mIp);
        DatagramPacket datagramPacket = new DatagramPacket(mSendData.getBytes(), mSendData.getBytes().length, inetAddress, UDP_PORT);
        for (int i = 0; i < 3; i++) {
            Thread.sleep(100);
            Log.d(TAG, "run 11");
            scoket.send(datagramPacket);
        }
    } catch (SocketException e) {
        Log.d(TAG, "run : 111" + e.getMessage());
        e.printStackTrace();
    } catch (IOException e) {
        Log.d(TAG, "run : 211" + e.getMessage());
        e.printStackTrace();
    } catch (InterruptedException e) {
        Log.d(TAG, "run : 311" + e.getMessage());
        e.printStackTrace();
    } finally {
        closeConnection(scoket);
    }
}

//UDO接收数据
private void getUdpBack() {
    while (true) {
        DatagramSocket scoket = null;
        try {
            Thread.sleep(100);
            scoket = new DatagramSocket(UDP_PORT);
            byte[] tem = new byte[1024];
            DatagramPacket datagramPacket = new DatagramPacket(tem, tem.length);
            scoket.receive(datagramPacket);
            String hostName = datagramPacket.getAddress().getHostName();
            int port = datagramPacket.getPort();
            Log.d(TAG, "run : receive : ip = " + hostName + " ; port = " + port);
            int length = datagramPacket.getLength();
            byte[] data = new byte[length];
            System.arraycopy(datagramPacket.getData(), 0, data, 0, length);
            mUdpResult = new String(data);
            mHandler.sendEmptyMessage(UDP_BACK);
        } catch (SocketException e) {
            Log.d(TAG, "run 422: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            Log.d(TAG, "run 522: " + e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            closeConnection(scoket);
        }
    }
}

//关闭数据流的方法
private void closeConnection(Closeable... closeables) {
    if (closeables == null || closeables.length == 0) {
        return;
    }
    for (int i = 0; i < closeables.length; i++) {
        Closeable closeable = closeables[i];
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

//TCP客户端发送数据 (这里会出现Socket无法创建的情况,可以修改端口号进行尝试)
public void run() {
    Socket socket = null;
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
        //1,创建Socket对象,指定目标ip与端口号
        socket = new Socket(mIp, TCP_PORT);
        inputStream = socket.getInputStream();
        String readData = readData(inputStream);
        Log.d(TAG, "run: 客户端读 = " + readData);
        outputStream = socket.getOutputStream();
        //2,发送数据
        outputStream.write(mSendData.getBytes());
        outputStream.flush();
        Log.d(TAG, "run: 客户端读 : 11");
    } catch (IOException e) {
        Log.d(TAG, "run: 客户端读 : 22 : " + e.getMessage());
        e.printStackTrace();
    } finally {
        //关闭流
        closeConnection(inputStream, outputStream, socket);
    }
}

/**
     * 读取数据流
     * @param inputStream
     * @return
     */
    private String readData(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = inputStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        closeConnection(outSteam);
        return outSteam.toString();
    }

//TCP服务端接收数据
private void getTcpBack() {
    ServerSocket serverSocket = null;
    try {
        //1,构造ServerSocket实例,指定端口监听客户端的连接请求
        serverSocket = new ServerSocket(TCP_PORT);
        while (true) {
            try {
                Thread.sleep(100);
                Socket accept = null;
                InputStream inputStream = null;
                OutputStream outputStream = null;
                try {
                    //2,建立跟客户端的连接
                    accept = serverSocket.accept();
                    inputStream = accept.getInputStream();
                    outputStream = accept.getOutputStream();
                    Log.d(TAG, "getTcpBack: 11");
                    //3,读取接收的内容
                    mTcpResult = readData(inputStream);
                    Log.d(TAG, "getTcpBack: 22 : " + mTcpResult);
                    mHandler.sendEmptyMessage(TCP_BACK);
                } catch (IOException e) {
                    Log.d(TAG, "getTcpBack: 33 : " + e.getMessage());
                    e.printStackTrace();
                } finally {
                    //关闭流
                    closeConnection(accept, inputStream, outputStream);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        closeConnection(serverSocket);
    }
}

参考与摘录:
https://www.cnblogs.com/sunny-sl/p/6529830.html
http://blog.chinaunix.net/uid-26275986-id-4109679.html
https://juejin.im/post/5b49f9fbf265da0f563dc9d8#heading-3

你可能感兴趣的:(网络基础(Socket通讯))