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

时间:2022-07-05 13:48:33

前言

  • 简要回顾了 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();
}
}
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();
}

}
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();
}
}