动手学习TCP:服务端状态变迁
上一篇文章介绍了TCP状态机,并且通过实验了解了TCP客户端正常的状态变迁过程。
那么,本篇文章就一起看看TCP服务端的正常状态变迁过程
服务端状态变迁
根据上一篇文章中的TCP状态变迁图,可以得到服务器的正常状态变迁流程如下:
CLOSED -> LISTEN -> SYN_RECV -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
具体的将状态跟TCP包关联起来就如下表示:
|
From State |
To State |
Recv Packet |
Send Packet |
|
CLOSED |
LISTEN |
- |
- |
|
LISTEN |
SYN_RCVD |
[SYN] |
[SYN, ACK] |
|
SYN_RCVD |
ESTABLISHED |
[ACK] |
- |
|
ESTABLISHED |
CLOSE_WAIT |
[FIN, ACK] |
[ACK] |
|
CLOSE_WAIT |
LAST_ACK |
- |
[FIN, ACK] |
|
LAST_ACK |
CLOSED |
[ACK] |
- |
服务端状态变迁实验
下面就结合上面分析出来的服务端状态变迁表,利用Pcap.Net来模拟服务端正常的状态变迁过程。
代码实现
跟前面几次正好相反,这次我们将在宿主机运行Pcap.Net实现的服务端,然后在虚拟机运行一个客户端。
对于服务端,主程序中设置了源和目的端的连接信息,这次宿主机中的服务端将监听“3333”端口。
然后,程序中设置了服务端TCP初始状态为"LISTENING",然后就直接运行监听函数了。
// Open the output device
using (PacketCommunicator communicator = selectedDevice.Open(System.Int32.MaxValue, // name of the device
PacketDeviceOpenAttributes.Promiscuous, // promiscuous mode
)) // read timeout
{
EndPointInfo endPointInfo = new EndPointInfo();
endPointInfo.SourceMac = "08:00:27:00:C0:D5";
endPointInfo.DestinationMac = "";
endPointInfo.SourceIp = "192.168.56.101";
endPointInfo.DestinationIp = "";
endPointInfo.SourcePort = ;
endPointInfo.DestinationPort = ; using (BerkeleyPacketFilter filter = communicator.CreateFilter("tcp port " + endPointInfo.SourcePort))
{
// Set the filter
communicator.SetFilter(filter);
} tcpStatus = TCPStatus.LISTENING;
PacketHandler(communicator, endPointInfo);
}
这次的监听函数"PacketHandler"中的逻辑,跟上一次客户端的例子还是有很大差别的。
首先是期待接收和实际发送的TCP包类型有很大的差别,其次就是状态之间的变迁是完全不同的。但是,代码的逻辑依然是根据上面的服务端状态变迁表。
private static void PacketHandler(PacketCommunicator communicator, EndPointInfo endPointInfo)
{
Packet packet = null;
bool running = true; do
{
PacketCommunicatorReceiveResult result = communicator.ReceivePacket(out packet); switch (result)
{
case PacketCommunicatorReceiveResult.Timeout:
// Timeout elapsed
continue;
case PacketCommunicatorReceiveResult.Ok:
bool isRecvedPacket = (packet.Ethernet.IpV4.Destination.ToString() == endPointInfo.SourceIp) ? true : false; if (isRecvedPacket)
{
switch (packet.Ethernet.IpV4.Tcp.ControlBits)
{
case TcpControlBits.Synchronize:
if (tcpStatus == TCPStatus.LISTENING)
{
endPointInfo.DestinationMac = packet.Ethernet.Source.ToString();
endPointInfo.DestinationIp = packet.Ethernet.IpV4.Source.ToString();
endPointInfo.DestinationPort = packet.Ethernet.IpV4.Tcp.SourcePort; Utils.PacketInfoPrinter(packet);
Packet synAck = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Synchronize | TcpControlBits.Acknowledgment);
communicator.SendPacket(synAck);
tcpStatus = TCPStatus.SYN_RECEIVED;
}
break;
case TcpControlBits.Acknowledgment:
if (tcpStatus == TCPStatus.SYN_RECEIVED)
{
tcpStatus = TCPStatus.ESTABLISHED;
Utils.PacketInfoPrinter(packet, tcpStatus);
}
else if (tcpStatus == TCPStatus.LAST_ACK)
{
tcpStatus = TCPStatus.CLOSED;
Utils.PacketInfoPrinter(packet, tcpStatus);
tcpStatus = TCPStatus.LISTENING;
}
else if (tcpStatus == TCPStatus.FIN_WAIT_1)
{
tcpStatus = TCPStatus.FIN_WAIT_2;
Utils.PacketInfoPrinter(packet);
}
break;
case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
if (tcpStatus == TCPStatus.FIN_WAIT_2)
{
Utils.PacketInfoPrinter(packet);
Packet ack = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
communicator.SendPacket(ack);
tcpStatus = TCPStatus.TIME_WAIT;
}
else if (tcpStatus == TCPStatus.ESTABLISHED)
{
Utils.PacketInfoPrinter(packet);
Packet ack = Utils.BuildTcpResponsePacket(packet, TcpControlBits.Acknowledgment);
communicator.SendPacket(ack);
tcpStatus = TCPStatus.CLOSE_WAIT;
}
break;
default:
Utils.PacketInfoPrinter(packet);
break;
}
}
else
{
switch (packet.Ethernet.IpV4.Tcp.ControlBits)
{
case (TcpControlBits.Synchronize | TcpControlBits.Acknowledgment):
if (tcpStatus == TCPStatus.SYN_RECEIVED)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
}
break;
case (TcpControlBits.Fin | TcpControlBits.Acknowledgment):
if (tcpStatus == TCPStatus.FIN_WAIT_1)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
}
else if (tcpStatus == TCPStatus.LAST_ACK)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
}
break;
case TcpControlBits.Acknowledgment:
if (tcpStatus == TCPStatus.TIME_WAIT)
{
Utils.PacketInfoPrinter(packet, tcpStatus);
}
else if (tcpStatus == TCPStatus.CLOSE_WAIT)
{ Utils.PacketInfoPrinter(packet, tcpStatus); Packet fin = Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Fin | TcpControlBits.Acknowledgment);
communicator.SendPacket(fin);
tcpStatus = TCPStatus.LAST_ACK;
}
break;
default:
Utils.PacketInfoPrinter(packet);
break;
}
}
break;
default:
throw new InvalidOperationException("The result " + result + " should never be reached here");
}
} while (running); }
对于客户端,通过Python实现了一个简单的Socket程序来模拟客户端行为:
from socket import *
import time HOST = "192.168.56.101"
PORT = 3333
BUFSIZ = 1024
ADDR = (HOST, PORT) client = socket(AF_INET, SOCK_STREAM)
client.connect(ADDR) time.sleep(5) client.close()
运行效果
这次,宿主机上运行的是服务端,虚拟机运行的是客户端,打开Wireshark监听"VirtualBox Host-Only Network"网卡,并设置filter为"port 3333"。
运行服务端程序,服务端将处于监听状态。这是在虚拟机中运行"client.py"。这时,通过服务端console可以看到客户端和服务端之间的包,以及服务端的状态变迁。

Wireshark依然显示的是TCP连接建立和终止的过程。

netstat命令
netstat是控制台命令,是一个监控TCP/IP网络的非常有用的工具,它可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。
实验中的宿主机系统是Win7,下面看看通过 netatat /? 获得的帮助信息:

netstat命令失效?
虽然说上面的程序可以打印出服务端的状态变迁过程,但是这次让我们通过netstat命令查看一下。
为了方便查看,将"client.py"中的"time.sleep(5)"改为"time.sleep(300)",使客户端跟服务器之间的连接保持300秒。客户端的端口号为"1090"。

这时,分别在服务端和客户端cmd窗口中执行 netstat -anp TCP | findstr "192.168.56" 命令,查看包含"192.168.56"字符串的TCP连接:
服务端:

客户端:

为什么服务端看不到TCP连接?就像我们第一篇介绍的那样,Pcap.Net是不经过操作系统协议栈的,所以这也就解释了为什么"netstat"命令发现不了服务端的TCP连接。
等300秒结束后,客户端会发送终止连接请求。当连接终止后,可以看大客户端的TCP连接状态变成了"TIME_WAIT"。
客户端:

总结
本文中根据TCP状态变迁图,得到了服务端的状态变迁表。
然后使用Pcap.Net,基于服务端的状态变迁表,构建了一个简单的服务端,展示了服务端状态变迁的过程。
文中还简单的介绍了"netstat"命令,通过这个命令可以查看TCP连接的状态,结合这个命令,可以更好的了解TCP状态。
动手学习TCP:服务端状态变迁的更多相关文章
- 动手学习TCP:客户端状态变迁
上一篇文章中介绍了TCP连接的建立和终止. 通过实际操作了解到,在TCP协议工作过程中,客户端和服务端都会接收或者发送特定标志的TCP数据包,然后进入不同的状态. 也就是说,TCP协议就是一个包含多种 ...
- 动手学习TCP:总结和索引
TCP是一个十分复杂的协议,通过前面几篇文章只涉及了TCP协议中一些基本的概念. 虽然说都是一些TCP最基本的概念,但是试验过程中一直在踩坑,例如:TCP flag设置错误,seq.ack号没有计算正 ...
- 动手学习TCP:数据传输
前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...
- 动手学习TCP:数据传输(转)
前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...
- 动手学习TCP:TCP特殊状态
前面两篇文章介绍了TCP状态变迁,以及通过实验演示了客户端和服务端的正常状态变迁. 下面就来看看TCP状态变迁过程中的几个特殊状态. SYN_RCVD 在TCP连接建立的过程中,当服务端接收到[SYN ...
- 动手学习TCP: 环境搭建
前一段时间通过Wireshark抓包,定位了一个客户端和服务器之间数据传输的问题.最近就抽空看了看<TCP/IP详解 卷1>中关于TCP的部分,书中用了很多例子展示了TCP/IP协议中的一 ...
- 动手学习TCP:4种定时器
上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...
- C++封装的基于WinSock2的TCP服务端、客户端
无聊研究Winsock套接字编程,用原生的C语言接口写出来的代码看着难受,于是自己简单用C++封装一下,把思路过程理清,方便自己后续翻看和新手学习. 只写好了TCP通信服务端,有空把客户端流程也封装一 ...
- 利用select实现IO多路复用TCP服务端
一.相关函数 1. int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeva ...
随机推荐
- ActiveReports 报表控件官方中文入门教程 (3)-如何选择页面报表和区域报表
本篇文章将介绍区域报表和页面报表的常见使用场景.区别和选择报表类型的一些建议,两种报表的模板设计.数据源(设计时和运行时)设置.和浏览报表的区别. ActiveReports 报表控件官方中文入门教程 ...
- php中的常用数组函数(一)(比较多个数组的差集的函数们 array_diff_assoc() array_diff() array_diff_key() array_diff_ukey() array_diff_uassoc())
array_diff_assoc($arr1, $arr2, $arr3,... n); 返回:一个$arr1的副本,后续的数组中出现一个键值相同的元素,就在副本中删掉这个元素,最后返回这个副本. 如 ...
- Ajax基础实例
前端代码 <script type="text/javascript"> var xmlhttp; function go(url) { xmlhttp=null; i ...
- mongodb学习3---mongo的MapReduce
1,概述MapReduce是个非常灵活和强大的数据聚合工具.它的好处是可以把一个聚合任务分解为多个小的任务,分配到多服务器上并行处理.MongoDB也提供了MapReduce,当然查询语肯定是Java ...
- double 类型转化为Integer类型 ---DecimalFormat
假设x是你要转换的double类型变量: 不进行四舍五入操作: (int)x 进行四舍五入操作: Integer.parseInt(new java.text.DecimalFormat(" ...
- [翻译]:SQL死锁-阻塞探测
到了这篇,才是真正动手解决问题的时候,有了死锁之后就要分析死锁的原因,具体就是需要定位到具体的SQL语句上.那么如何发现产生死锁的问题本质呢?下面这篇讲的非常细了,还提到了不少实用的SQL,但对我个人 ...
- ArcEngine中License权限等级更改的问题
曾经认为自己对于ArcGIS 开发许可问题比较理解了,并小结在<ArcEngine10.x开发的许可问题>中. 01.权限问题 今天在调用GP时失败(插值式开发,使用的是他人框架),因为需 ...
- AutoCAD .NET二次开发(四)
在CAD中,属性信息一般是以注记的形式存在,但当属性数据内容较多时,显示就成了问题.扩展属性(Xdata)可以解决这一问题,比如南方Cass中就利用了这一点.我们经常用Lisp来读取操作扩展属性. 查 ...
- 高性能JS笔记1——加载执行
一.脚本位置 1.Script标签尽可能放到Body底部,以减少脚本文件下载对整个页面UI渲染的影响. 2.Script标签永远不要紧跟Link标签后面. 二.组织脚本 1.合并多个文件在一个Scri ...
- 读书笔记2014第3本:Visual Studio程序员箴言
Visual Studio 2010是我经常使用的程序开发工具,也知道VS中有大量的快捷键可以帮助提高效率,可惜就是不愿意记忆,最近在学vim的时候快速把<Visual Studio程序员箴言& ...