通过Socket通信理解TCP
1、什么是TCP协议
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。即客户端和服务器之间在交换数据之前会先建立一个TCP连接,才能相互传输数据。并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
2、什么是Socket协议
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。socket里面已经封装好了UDP和TCP/IP协议,直接使用就可以了。
网上好多说TCP 三次握手 四次挥手,如下图
那么什么是三次握手四次挥手,我们通过实践来理解并检验一下真理。
通过代码运行来分析TCP
服务端:
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
class TcpServer {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(10002);// 建立服务端的socket服务
Socket s = ss.accept();// 获取客户端对象
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + ".....connected");
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf, 0, len);
System.out.println("接收到客户端消息:" + text);
OutputStream out = s.getOutputStream();
out.write("Server Message".getBytes());
s.close();
ss.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端接受客户端的消息,并回复“Server Message”,最后关闭Socket和 ServerSocket
客户端:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) {
try {
Socket s = new Socket("127.0.0.1", 10002);
OutputStream out = s.getOutputStream();// 获取了socket流中的输出流对象。
out.write("Client message".getBytes());
InputStream in = s.getInputStream();// 获取了socket流中的输出流对象。
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf, 0, len);
System.out.println("接收到服务端消息:" + text);
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端在建立连接后发送一条消息“Client message”到服务器,最后关闭socket。
需要注意,代码中可以理解到,客户端收到消息后,发起的断开请求。
首先运行服务端(10002端口),在运行客户端(系统随机建立端口)
通过 Wireshark 工具进行监听,如图
服务端口10002,客户端口52500
1、进行三次握手
第一次握手:
客户端主动打开,发送连接请求报文段,将SYN标识位置为1,Sequence Number置为x(TCP规定SYN=1时不能携带数据,x为随机产生的一个值),然后进入SYN_SEND状态。
第二次握手:
服务器收到SYN报文段进行确认,将SYN标识位置为1,ACK置为1,Sequence Number置为y,Acknowledgment Number置为x+1,然后进入SYN_RECV状态,这个状态被称为半连接状态。
第三次握手:
客户端再进行一次确认,将ACK置为1(此时不用SYN),Sequence Number置为x+1,Acknowledgment Number置为y+1发向服务器,最后客户端与服务器都进入ESTABLISHED状态。
2、进行数据传输
由哪一端发送的数据就有那一段标记为PSH,ACK
另一端回复 标记为ACK
客户端发送给服务器
服务器发送到客户端
这一部分内容涉及了TCP 的深层次内容和精髓,有传输超时相关问题,阻塞相关问题,请参考其他文章:
https://www.cnblogs.com/duan2/p/9180861.html
https://www.cnblogs.com/linuxprobe-sarah/p/10634626.html
3、进行四次挥手
第一次挥手: 关闭客户端到服务器的连接:首先客户端A发送一个FIN,用来关闭客户到服务器的数据传送,然后等待服务器的确认。其中终止标志位FIN=1,序列号seq=u。
第二次挥手: 服务器收到这个FIN,它发回一个ACK,确认号ack为收到的序号加1。
第三次挥手: 关闭服务器到客户端的连接:也是发送一个FIN给客户端。
第四次挥手: 客户段收到FIN后,并发回一个ACK报文确认,并将确认序号seq设置为收到序号加1。
首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。(这也就是为什么在客户端后面写的 “需要注意,代码中可以理解到,客户端收到消息后,发起的断开请求”)
-
客户端A发送FIN后,进入终止等待状态, 服务器B收到客户端A连接释放报文段后,就立即给客户端A发送确认,然后服务器B就进入close-wait状态,此时TCP服务器进程就通知上层应用进程,因而从A到B的连接就释放了。此时是“半关闭”状态。即A不可以发送给B,但是B可以发送给A。
-
此时,若B没有数据报要发送给A了,其应用进程就通知TCP释放连接,然后发送给A连接释放报文段,并等待确认。A发送确认后,进入time-wait,注意,此时TCP连接还没有释放掉,然后经过时间等待计时器设置的2MSL后,A才进入到close状态。
为什么要等待2MSL呢? -
MSL即Maximum Segment Lifetime,也就是最大报文生存时间,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。引用《TCP/IP详解》中的话:“它(MSL)是任何报文段被丢弃前在网络内的最长时间”。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。
参考:https://blog.csdn.net/laomumu1992/article/details/83011898