前言
- 简要回顾了 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 服务器端工作流程:
- SerserSocket(int port) 创建 ServerSocket,并将其绑定到指定端口
- 调用 accept(),监听连接请求,如果客户端请求连接,则接受并返回通信 Socket
- 调用 Socket 类的 getOutputStream()、getInputStream() 获取输出和输出流,开始网络数据发送与接收
- 通信结束,关闭通信套接字
TCP 客户端工作流程:
- 调用 Socket() 创建一个流套接字,并且连接至服务器端
- 调用 Socket 类中的 getOutputStream()、getInputStream() 方法获取输出和输入流,开始网络数据的发送与接收
- 通信结束,关闭通信套接字
UDP:有不提供数据报分组、组装和不能对数据包排序的缺点,无法得知其是否安全完整到达。UDP 主要用来支持那些需要在计算机之间传输数据的网络应用,对网络通讯质量要求不高,要求较快的通信速度时使用。
主要作用:将网络数据流量压缩成数据报的形式。(典型数据报:一个二进制的传输单位)UDP 服务器端工作流程:
- 调用 DatagramSocket(int port) 创建一个数据报套接字,并绑定到指定端口
- 调用 DatagramPacket(byte[]buf,int length),建立字节数组以接收 UDP 包
- 调用 DatagramSocket 类的receiver(),接受 UDP 包
- 通信结束,关闭数据报套接字
UDP 客户端工作流程:
- 调用 DatagramSocket() 创建一个数据报套接字
- 调用 DatagramPacket(byte[]buf,int offset,int length,InetAddress address,int port),建立要发送的 UDP 包
- 调用 DatagramSocket 类的 send() 发送 UDP 包
- 通信结束,关闭数据报套接字
简单的通信 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();
}
}