Android网络编程 - TCP/IP协议实践 - OkHttp

前言

  • 简要回顾了 TCP/IP 分层模型及 IP、TCP、UDP 等主要协议,并且在此基础上联系 Android,做出一定的代码实现。
  • 推荐书目:《深入理解Android网络编程》、《计算机网络 - 自顶向下方法》、《TCP/IP详解》

网络协议概述

  • 大多数网络都采用分层的体系结构,每一层都建立在它下层之上,同时向它的上一层提供服务。
  • 网络各层中存在许多协议,接收方与发送方同层协议必须一致。
  • 由于网络结点之间联系复杂,制定协议,通常把复杂成分分解成简单的成分,再将它们组合。
    常用的复合技术 - “层级结构”:
    • 结构中每一层都规定明确的任务以及接口标准
    • 将用户的应用程序作为最高层
    • 除了最高层,中间的每一层都向上提供服务,同时又是下一层的用户
    • 物理层座位最低层,使用最高层传来的参数,是提供服务的基础
  • ISO提出的OSI/RM模型将计算机网络体系结构通讯协议划分为七层:物理层、数据链路层、网络层、传输层、;会话层、表示层、应用层(物数网传会表应)
    低4层完成数据传输服务、高3层面向用户:
    • 应用层 - 为应用提供访问网络服务接口
    • 表示层 - 提供数据/信息表示变换,让不同编码计算机可相互理解
    • 会话层 - 组织同步不同计算机的进程通信(对话),对话的建立与拆除;还提供在数据流中插入同步点机制,数据传输中断后,也不必从头开始,仅重传最近一个同步点以后的数据
    • 传输层 - 源主机与目的主机的连接与数据传输(端到端的数据传输)
    • 网络层 - 寻找合适的路由,使网络层数据传输单元(分组)可以正确找到目的站
    • 数据链路层 - 两个相邻结点间无差错地以帧为单位的数据传送
    • 物理层 - 物理介质传输,比特流传输

IP、TCP 和 UDP 协议

IP 协议

  • 即互联网协议(Internet Protocol),用于报文交换网络的一种面向数据的协议,是TCP/IP协议中网络层的主要协议。
  • 作用:根据源主机和目的主机的地址传送数据。
  • IP定义了寻址方法和数据报的封装结构。(IPv4、IPv6)

TCP 协议

  • TCP/IP模型中,面向连接的、可靠的、基于字节流的传输层通信协议;
  • 传输层存在的意义:应用层之间需要可靠的,像管道一样的连接,但网络层不提供这样的流机制,只提供不可靠的包交换,因此需要传输层的协调。
  • TCP协议作用:
    • TCP协议把应用层向传输层发送网间传输的、用8位字节表示的数据流分成适当长度的报文段(受数据链路层最大传输单元MTU限制);
    • 把包传给网络层,由网络层将包传送给接收端实体的传输层;TCP为了不发生丢包,给每个包一个序号,同时序号保证了传送到接收端实体的包能按序接收。然后接收实体成功收到包后回复相应的ACK确认;
    • 如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就会被认为丢失,将被重传;
    • TCP协议用一个校验和(Checksum)函数来检验数据是否错误,发送与接收时候都需要计算校验和。

UDP 协议

  • TCP/IP模型中面向无连接的传输层协议,提供面向事务的简单不可靠信息传输服务。
  • UDP不提供对IP协议的可靠机制、流控制以及错误恢复功能等,在数据传输之前不需要简历连接。
  • 由于UDP较简单,因此比TCP负载消耗少。

Android 中的运用?

TCP、UDP报文结构中,每段报文除了数据本身,还包含了许多其他重要信息,而且长度有限,传输时候需要拆解,到达目的地之后再组合还原,如果包有丢失或者损坏还需要重传,乱序发送的包还需要重新排序。
处理传输过程中的一系列逻辑,需要大量可靠的代码完成。于是人们他用过 Socket 对网络纠错、包大小、包重传等进行封装。

Socket 基础

  • 从字面意思来看,“Socket”通常被称为“套接字”。相信很多人都不理解“套接字”是什么意思,包括我,也是疑惑了很久。
  • Socket 作用距离:一台服务器可能会提供多种服务,每种服务对应一个 Socket,客户也持有一个 Socket,客户需要哪种服务,就需要找到对应的Socket来进行通信。
  • Socket 是应用层与TCP/IP协议族通信的中间软件抽象层,它本质是一组接口。Socket 接口将复杂的TCP/IP协议族隐藏,对用户而言,一组简单的接口就是全部,让 Socket 组织数据,以符合指定协议。
  • 应用程序常通过 Socket 向网络发送/响应请求
  • Socket 基本操作:1、连接远程机器;2、发送数据;3、接收数据;4、关闭连接;5、绑定端口;6、监听到达数据;7、在绑定端口上接收来自远程及其的连接
  • 服务器要和客户端通信,两者需要实例化一个Socket。服务器和客户端的Socket不一样,客户端可以实现连接远程机器、发送数据、接收数据、关闭连接等,服务器还需要实现绑定端口、监听到达数据、接收来自远程机器的连接。Android 中,java.net里面提供的两个类:ServerSocket、Socket,前者用于实例化服务器 Socket,后者用于实例化客户端的 Socket。
  • Socket 的两种类型:TCP、UDP
    TCP 与 UDP 在传输过程中的具体实现方式不同,两者都接收传输协议数据包并将其内容向前传递到应用层。
    TCP 把消息分解成数据包,并在接收端以正确的顺序将他们重新装配起来;TCP 还处理丢包的重传请求,位于上层的应用层要处理的事情就相对较少。
    UDP 不提供装配与处理重传请求,只是向前传递信息包,位于上层的应用必须确保消息是完整的,并且是以正确的顺序装配的。

使用 TCP 通信与 UDP 通信

  • TCP:保证可靠性上,采用超时重传和捎带确认机制;流量控制上,采用滑动窗口协议(协议规定,对于窗口内,未经确认的分组需要重传);拥塞机制上,采用慢启动算法。
    场景应用:当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。

    • TCP 服务器端工作流程:

      1. SerserSocket(int port) 创建 ServerSocket,并将其绑定到指定端口
      2. 调用 accept(),监听连接请求,如果客户端请求连接,则接受并返回通信 Socket
      3. 调用 Socket 类的 getOutputStream()、getInputStream() 获取输出和输出流,开始网络数据发送与接收
      4. 通信结束,关闭通信套接字
    • TCP 客户端工作流程:

      1. 调用 Socket() 创建一个流套接字,并且连接至服务器端
      2. 调用 Socket 类中的 getOutputStream()、getInputStream() 方法获取输出和输入流,开始网络数据的发送与接收
      3. 通信结束,关闭通信套接字
  • UDP:有不提供数据报分组、组装和不能对数据包排序的缺点,无法得知其是否安全完整到达。UDP 主要用来支持那些需要在计算机之间传输数据的网络应用,对网络通讯质量要求不高,要求较快的通信速度时使用。
    主要作用:将网络数据流量压缩成数据报的形式。(典型数据报:一个二进制的传输单位)

    • UDP 服务器端工作流程:

      1. 调用 DatagramSocket(int port) 创建一个数据报套接字,并绑定到指定端口
      2. 调用 DatagramPacket(byte[]buf,int length),建立字节数组以接收 UDP 包
      3. 调用 DatagramSocket 类的receiver(),接受 UDP 包
      4. 通信结束,关闭数据报套接字
    • UDP 客户端工作流程:

      1. 调用 DatagramSocket() 创建一个数据报套接字
      2. 调用 DatagramPacket(byte[]buf,int offset,int length,InetAddress address,int port),建立要发送的 UDP 包
      3. 调用 DatagramSocket 类的 send() 发送 UDP 包
      4. 通信结束,关闭数据报套接字

简单的通信 Demo

实现了服务器端与客户端的交互(客户端发送消息至服务器端);

ChatServer.java
public class ChatServer extends Thread {
    /**
 * 服务器Socket对象
 */
    private ServerSocket server = null;

    /**
 * 端口
 */
    public static final int PORT = 5000;

    /**
 * 读写Buffer
 */
    private BufferedReader reader;
    private BufferedWriter writer;

    /**
 * 主线程Handler
 */
    private final Handler handler;

    /**
 * 标识
 */
    public static final int SERVER_TAG = 12345;
    public static final String MSG_KEY = "server";

    public ChatServer(Handler handler) throws IOException {
        this.handler = handler;
        createSocket();
    }

    /**
 * 创建ServerSocket
 */
    private void createSocket() throws IOException {
        server = new ServerSocket(PORT, 100);
    }

    @Override
    public void run() {
        Socket client;
        String text;
        try {
            //死循环监听
            while (true) {
                //响应客户端连接请求
                client = responseSocket();
                while (true) {
                    //接收客户端发送的消息
                    text = receiveMsg(client);
                    //显示消息结果
                    makeTips(text);
                    break;
                }
                closeSocket(client);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
 * 关闭连接及缓存
 *
 * @param client
 */
    private void closeSocket(Socket client) throws IOException {
        reader.close();
// writer.close();
        client.close();
    }

    /**
 * 发送消息到客户端
 *
 * @param client
 * @param text
 */
    private void sendMsg(Socket client, String text) throws IOException {
        writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
        writer.write(text + "\n");
        writer.flush();//发送
    }

    /**
 * 提示
 *
 * @param text
 */
    private void makeTips(String text) {
        Message msg = new Message();
        Bundle bundle = new Bundle();
        bundle.putString(ChatServer.MSG_KEY, text);
        msg.setData(bundle);
        msg.what = SERVER_TAG;
        handler.sendMessage(msg);
    }

    private String receiveMsg(Socket client) throws IOException {
        reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
        String result = reader.readLine();
        return "服务器收到:" + result;
    }

    private Socket responseSocket() throws IOException {
        return server.accept();
    }
}
  • 1
ChatClient.java
public class ChatClient {
    private Socket socket = null;

    public ChatClient(String host, int port) throws IOException {
        socket = new Socket(host, port);
    }

    /**
 * 向服务器端发送消息
 *
 * @param msg
 * @throws IOException
 */
    public void sendMsg(String msg) throws IOException {
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write(msg.replace("\n", "") + "\n");
        writer.flush();
    }

}
  • 1
MainActivity.java
public class MainActivity extends AppCompatActivity {

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case ChatServer.SERVER_TAG:
                    Bundle bundle = msg.getData();
                    Toast.makeText(MainActivity.this, bundle.getString(ChatServer.MSG_KEY), Toast.LENGTH_LONG).show();
                    break;
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
        setContentView(R.layout.activity_main);
        launchServer();
        launchClient();
    }

    private void launchServer() {
        //启动服务器端
        try {
            ChatServer
                    chatServer = new ChatServer(handler);
            chatServer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
 * 启动客户端
 */
    private void launchClient() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ChatClient client = null;
                try {
                    client = new ChatClient(null, ChatServer.PORT);
                    client.sendMsg("客户端给服务器端,发了一条信息");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
  • 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xxpr_ybgg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值