前段时间做了关于Socket的项目,总结一些在这个过程中学到的东西和需要注意的地方
socket 的使用在Android的开发中还是很常见的,也是非常重要的
先看下思维导图
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
接下来看对我们开发比较重要的对比模型
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)
看图理解
原理
基于 TCP协议流套接字(streamsocket),采用 流的方式 提供可靠的字节流服务
基于 UDP协议数据报套接字(datagramsocket),采用 数据报文 提供数据打包发送的服务
怎么使用
- 连接过程
一张图就能看懂
具体步骤
/*
* 步骤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时,就从消息队列中删除,没有收到的话,继续发送,然后里面可以加入每条消息的重发次数限制
- 当然每个公司的业务不同,这些规则都需要调整,这里只提供一个思路