TCP协议抓包分析

1 环境搭建

  建议阅读此文前先了解TCP的原理。此文章仅为了加深对TCP的理解。
  为了在抓包过程中捕获尽可能多种类的TCP报文,本文需要自己编写java socket程序,并安装Wireshark配套软件。
  为了方便理解TCP传输过程,仅客户端向服务端发送数据。

1.1 编写java程序

  程序中需要注意的几点:

  1. 客户端发送数据,服务端接受数据。将服务端buffer大小设置的明显小于客户端,是为了捕获流量控制报文。
  2. 客户端发送的数据(即d://bb.jpg),应该选择合适的大小。本文中bb.jpg大小为7M(推荐),这是为了捕获足够多的样本来进行分析。
  3. 服务端中并没有关闭socket,这是为了捕获reset报文。
/**
 * 服务端
 * 
 * @author youngaoo
 * @created 2018年5月16日上午11:09:51
 */
public class Server {

    public static void main(String[] args) {
        Server s = new Server();
        s.doServer();
    }

    public void doServer() {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(8081));
            SocketChannel socketChannel = serverSocketChannel.accept();

            ByteBuffer dst = ByteBuffer.allocate(1024);

            FileOutputStream fileOut = new FileOutputStream("d://cc.jpg");
            FileChannel fileChannel = fileOut.getChannel();

            int len = 0;
            while ((len = socketChannel.read(dst)) != -1) {
                dst.flip();
                fileChannel.write(dst);
                dst.clear();
            }
            fileOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

/**
 * 客户端
 * 
 * @author youngaoo
 * @created 2018年5月15日下午12:06:15
 */
public class Client {

    public static void main(String[] args) {
        Client c = new Client();
        c.doClent();
    }

    public void doClent() {
        try {
            FileInputStream fileInputStream = new FileInputStream("d://bb.jpg");
            FileChannel fileChannel = fileInputStream.getChannel();
            ByteBuffer dst = ByteBuffer.allocate(1024*10);

            SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8081));

            int len = 0;
            while ((len = fileChannel.read(dst)) != -1) {
                dst.flip();
                socketChannel.write(dst);
                dst.clear();
            }
            fileInputStream.close();
            socketChannel.close();

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.2 安装并配置Wireshark

  安装和Wireshark和npcap。并打开Wireshark,做如下配置:

  1. 网卡选择本地回环
  2. 过滤器填写port 8081 and host 127.0.0.1
    TCP协议抓包分析_第1张图片


2 抓包并分析

  开启Wireshark抓包功能,然后依次运行服务端和客户端,得到tcp报文段,如图(图中报文段并不完整,省略了多个意义重复的报文段):
TCP协议抓包分析_第2张图片
  可以看到,图中包含了多种报文类型。编号为1的红框为三次握手过程;编号为3的代表“二次挥手”reset报文段。
  图中凡是带有中括号[]的文字,均是Wireshark添加的注释,并不是TCP协议的内容。可以看到传输数据过程中,发生了多次流量控制,具体体现在[TCP Window Update]报文和编号为②的报文段上。
  下面,将会详细分析几种报文的含义,和出现的原因。

2.1 TCP协议首部格式

  TCP协议的几乎所有功能都与首部相关,因此这里放上此图,供后文说明使用。
TCP协议抓包分析_第3张图片

2.2 三次握手

  编号为①的红框为三次握手的过程。在这个阶段,通信双方通过协商,得出了一系列信息,包括初始序列号,自己的接收窗口大小,最大报文段长度MSS,窗口扩大选项WS,选择确认SACK。三次握手完成以后,通信双方就可以根据这些信息,分别构建出自己发送窗口,MSS等。此时,一条逻辑上的连接就建立成功了。
TCP协议抓包分析_第4张图片
  红框中的内容,就对应TCP首部的各个部分。同样,使用中括号[]括起来的文字是Wireshark添加的,便于使用者理解。

2.3 传送数据

  三次握手下面紧接着,就是传送数据的报文。31306 → 8081 [ACK] Seq=1 Ack=1 Win=8192 Len=1460。其中Seq代表本报文段发送数据的第一个字节的序号;Win代表发送本报文段一方的接收窗口大小,在这里,即代表客户端的大小;Len即发送的数据的长度(单位为字节);当建立连接完毕以后,所发送的所有报文段,Ack字段都必须为1。
  该报文段发送的数据长度(Len),受到MSS值控制。之所以要协商MSS值,是因为从网络利用率来考虑。

2.4 流量控制

  流量控制发生在接收方来不及处理数据时,接收方要求发送发降低发送速率,即减小发送方的发送窗口大小。在第一个[Tcp Window Update]报文和其前一个报文,即流量控制报文。前一个报文将Win改为768,即告诉客户端,我的接收窗口为768个字节,你应该据此修改自己的发送窗口。后面的[Tcp Window Update]报文又将自己的接受窗口增大至4096字节。
  最坏的情况是服务端的应用程序读取数据的速度太慢,导致接受缓存达到最大容量,使接收窗口变为0。那么此时服务端就应该发送流量控制报文,[TCP ZeroWindow] 8081 → 31306 [ACK] Seq=1 Ack=31421 Win=0 Len=0,将Win设置为0,通知客户端不要再发送报文段了,等我处理完积压数据再通知你。注意,在[TCP ZeroWindow]报文前,发送了一个[TCP Window Full]报文。此报文是客户端根据服务端的接收窗口大小,MSS值计算出来的。计算方法为:5120/1460=3…740。
  当服务器处理完毕积压数据,又有了一些缓存空间,于是发送Win=3584的报文段。在Wireshark中,凡是扩大窗口的报文段都被注释为[Tcp Window Update]。

2.5 RESET报文

  编号为③的报文段中,前两个是四次挥手的前两次挥手,此时客户端到服务端的单向连接已经被关闭。接着应该进行服务端到客户端连接的关闭。但是服务端的应用程序并没有向TCP发送close命令,当应用程序进程结束后,操作系统的TCP就向客户端发送了reset报文。

2.6 PSH报文

  发生在TCP层清空缓存时,才将push置为1。

你可能感兴趣的:(TCP)