Android Socket了解一下

前段时间做了关于Socket的项目,总结一些在这个过程中学到的东西和需要注意的地方
socket 的使用在Android的开发中还是很常见的,也是非常重要的

先看下思维导图

socket.png

1.网络基础知识

首先回一下以前学过的OSI七层网络模型分别是:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。

物理层

  • 作用:传输比特流
  • 传输单位: 比特
  • 功能:1.为数据端设备提供传送数据的通路,数据通路可以是一个物理媒体,也可以是多个物理媒体连接而成。一次完整的数据传输,包括激活物理连接、传送数据和终止物理连接;2.传输数据;3.完成物理层的一些管理工作。
  • 对应协议:IEEE 802.1A, IEEE 802.2到IEEE 802.11

数据链路层

  • 作用:是为网络层提供数据传送服务的
  • 传输单位:帧
  • 功能:组帧,差错控制,流量控制和传输管理
  • 对应协议:FDDI, Ethernet, Arpanet, PDN, SLIP, PPP

网络层

  • 作用:为不同主机提供通信服务,网络层的分组数据从源端传到目的端
  • 传输单位:数据报
  • 功能:1.路由选择和中继;
    2.激活,终止网络连接;
    3.在一条数据链路上复用多条网络连接,多采取分时复用技术;
    4.检测与恢复;
    5.排序,流量控制;
    6.服务选择;
    7.网络管理
  • 对应协议:IP, ICMP, ARP, RARP, AKP, UUCP

传输层

  • 作用:选择网络层提供最合适的服务,在系统之间提供可靠的透明的数据传送,提供端到端的错误恢复和流量控制
  • 传输单位:报文段
  • 功能:差错恢复,流量控制,数据传送
  • 对应协议:TCP, UDP

会话层

  • 作用:允许不同主机上各进程之间的会话
  • 功能:1.将会话地址映射为运输地址;
    2.数据传输阶段;
    3.连接释放。
  • 对应协议:SMTP, DNS

表示层

  • 作用:处理在两个通信系统中交换信息的表达方式
  • 功能:把应用层提供的信息变换为能够共同理解的形式,提供字符代码、数据格式、控制信息格式、加密等的统一表示。
  • 对应协议:Telnet, Rlogin, SNMP, Gopher

应用层

  • 作用:为特性类的网络应用提供访问OSI用户服务
  • 功能:实现应用进程(如用户程序、终端操作员等)之间的信息交换。同时,还具有一系列业务处理所需要的服务功能
  • 对应协议:HTTP、TFTP, FTP, NFS, WAIS、SMTP

接下来看对我们开发比较重要的对比模型


模型对比.png

TCP协议

定义: Transmission Control Protocol,即 传输控制协议

特点:

  • 属于 传输层通信协议
  • 基于TCP的应用层协议有HTTP、SMTP、FTP、Telnet 和 POP3
  • 面向连接、面向字节流、全双工通信、传输可靠

缺点:效率慢

TCP 三次握手和四次挥手

三次握手
  • 第一次握手:建立连接时发送SYN会选择一个初始序号(ISN),每个连接的ISN都是不同的。客户端发送数据包(SYN=1,seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认

  • 第二次握手:服务器收到数据包包,必须确认客户的SYN(ACK=1,ack=x+1),同时自己也发送一个SYN包(SYN=1,seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态

  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK=1(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

四次挥手
  • 第一次挥手:主动断开连接一方向被动断开连接发送FIN数据包,FIN=1,seq=x,告诉被动断开连接一方“我要跟你断开连接了,我不会再给你发送数据了”,这是主动断开连接方式可以接受数据的,如果一直没有收到被动连接方的确认包,则可以重新发送这个包。此时,主动断开连接方处于FIN_WAIT_1状态

  • 第二次挥手:被动连接方接收到FIN包以后,发送确认包ACK=1,ack=x+1(FIN和SYN一样占用一个序列号),这个动作是告诉主动断开连接方我知道你要断开了,但是我还有数据没有发送完,等发送完了所有的数据就进行第三次挥手,此时被动断开连接方处于CLOSE_WAIT状态,主动断开连接方处于FIN_WAIT_2状态

  • 第三次挥手:被动断开连接方发送FIN=1,seq=y+1包,用来停止向主动断开连接方发送数据,也就是告诉主动断开连接方,我的数据也发完了,我也不给你发数据了,此时被动断开连接方处于LAST_ACK状态,主动断开连接方处于TIME_WAIT 状态

  • 第四次挥手:等过了一定时间(2MSL(报文段最大生存时间):为了保证最后ACK报文能够到达B,防止已失效连接请求报文段出现在此连接中)过后,主动断开连接方发送确认包ACK=1, ack=y+2,至此,四次挥手已经完成

UDP协议

  • 定义 :User Datagram Protocol,即 用户数据报协议

特点:

  • 属于传输层通信协议
  • 基于UDP的应用层协议有 TFTP、SNMP 与 DNS
  • 无连接的、不可靠的、面向报文、无拥塞控制

优点:速度快
缺点:消息容易丢失

HTTP协议

  • 定义:Hyper Text Transfer Protocol 即,超文本传输协议

特点:

  • 属于应用层的面向对象的协议

  • 简捷:客户向服务器请求服务时,只需传送请求方法和路径。
    请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同

  • 快速: HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

  • 无连接:意思是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

  • 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

  • 支持B/S和C/S模式

以上是作为今天的主题,需要复习的一些简单知识点,当然以上知识点里面的细节远远不止这些,这边知识对知识的一个梳理,打通的过程,接下看说说Socket

什么是Socket?

  • 起源:socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“open–> write/read –> close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

  • 定义:即套接字

  • 属于应用和TCP/IP 协议族通信的中间抽象层

  • 注意 Socket不是一种协议,而是一个编程调用接口(API)

看图理解


socket看图理解.png

原理

基于 TCP协议流套接字(streamsocket),采用 流的方式 提供可靠的字节流服务

基于 UDP协议数据报套接字(datagramsocket),采用 数据报文 提供数据打包发送的服务

怎么使用

  • 连接过程

一张图就能看懂


socket3.jpg

具体步骤

    /*
    * 步骤1:创建客户端 & 服务器的连接
    */ 

    // 创建Socket对象 & 指定服务端的IP及端口号 
    Socket socket = new Socket("ip地址", 端口号);  

    // 判断客户端和服务器是否连接成功  
    socket.isConnected());

下面是一些简单的测试方法,当然在实际的应用中,这些测试方法不一定实用,(仅供参考),因为每个企业的业务是不一样的,比如说可能有的企业会对每个消息进行了组装,有包头,包体,报内容,包长度等,那么你读取、发送消息的时候都需要去解包、组包的。这一点需要注意


    private Socket socket;
    private OutputStream outputStream;
    private InputStream inputStream;
    private InputStreamReader inputStreamReader;
    private BufferedReader bufferedReader;
    private String response;// 接收服务器发送过来的消息

  /**
     * 建立服务端连接
     */
    public void connect() {
        new Thread() {

            @Override
            public void run() {

                try {
                    socket = new Socket("ip地址", 9000);
                    Log.d("socket", "建立连接:" + socket.isConnected());
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    
      /**
     * 发送消息
     */
    public void send() {
        new Thread() {
            @Override
            public void run() {

                try {
                    //从Socket 获得输出流对象OutputStream
                    outputStream = socket.getOutputStream();
                    DataOutputStream writer = new DataOutputStream(outputStream);
                    //写入需要发送的数据到输出流对象中
                    writer.writeUTF("你好,我是队长"); // 写一个UTF-8的信息
                    //发送数据到服务端
                    outputStream.flush();
                   Log.d("socket", "发送消息" );
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    
    
      /**
     * 读取数据
     */
    public void read() {
        new Thread() {
            @Override
            public void run() {
                try {
                    //创建输入流对象InputStream
                    inputStream = socket.getInputStream();

                    //实际的应用中也可以这样的 read()传入的三个参数根据你的项目具体情况去写
                    //byte[] buffer =new byte[1024*1024];
                    //inputStream.read(buffer,0,1024);

                    // 创建输入流读取器对象 并传入输入流对象
                    inputStreamReader = new InputStreamReader(inputStream);
                    //该对象作用:获取服务器返回的数据
                    bufferedReader = new BufferedReader(inputStreamReader);
                    //通过输入流读取器对象 接收服务器发送过来的数据
                    response = bufferedReader.readLine();
                    Log.d("socket", "接受到的消息: "+response );
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    
    
    
    /**
     * 断开连接
     */
    public void closeSocket() {
        try {
            if (bufferedReader != null) {
                bufferedReader.close();
            }
            if (inputStreamReader != null) {
                inputStreamReader.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

总结

socket和http 对比
类别 在TCP/IP模型中哪一层 工作方式 解决的问题
socket 传输层和应用层之间 服务器主动发送数据的方式 数据如何在网络中传输的问题
http 应用层 请求-响应 如何包装数据的问题

注:因为ICP/IP属于传输层,socket也可以划分到传输层,其实socket和http的对比没有太大的实际意义,因为他们本身不是处于同一层级

一般在实际的应用中都会采用多线程去处理socket,一个线程负责读取,一个线程负责发送。注意多线程的使用

实际的应用中可能会遇到的坑

1, 客户端发送的一条消息到服务器,如何保证客户端发送成功了,要不然会出现客户端说我发给你了,服务器说我没收到,到时候锅帅给谁?

2,服务器向客户端发送一条消息,如何知道客户端接受成功了呢?

3,在网络不好的情况下,消息发出去了,但服务器还没有收到,这条消息就此丢掉,还是下次网络恢复的时候继续发送该消息

  • 上面的问题可以参考TCP
    握手和挥手的过程,首先可以对消息定义一个双方都认的规则,比如说每条消息中都有一个消息id(messageId)
  • 客户端每次发送消息出去后,记录那条消息的id,当服务器收到消息时,返回一个feedback告诉客户端收到那条消息了,如果没有收到feedback,就说明服务器没有收到,但是客户端已经发送了,那么就可以很快查找出问题的位置了
  • 同理服务器发送给客户端的消息也是一样
  • 对于第三个问题,可以建立一个消息队列,记录下来每次发送的消息,客户端先把消息给消息队列,send的时候从队列拿出来,当收到服务器返回的feedback时,就从消息队列中删除,没有收到的话,继续发送,然后里面可以加入每条消息的重发次数限制
  • 当然每个公司的业务不同,这些规则都需要调整,这里只提供一个思路

你可能感兴趣的:(Android Socket了解一下)