前一段时间通过Wireshark抓包,定位了一个客户端和服务器之间数据传输的问题。最近就抽空看了看《TCP/IP详解 卷1》中关于TCP的部分,书中用了很多例子展示了TCP/IP协议中的一些基本概念。

所以,也准备自己动手,通过一些简单的实验来进一步了解一下TCP中的一些基本概念。

环境搭建和配置

在开始进行实验之前,首先看看实验环境的搭建:

  1. Wireshark:用来抓取网络上的包,可以清楚的看到TCP/IP协议层,以及每层的详细信息,通过此处下载
  2. 一台虚拟机:如果客户端和服务端都在本机,那么数据通信是不经过网卡的,所以Wireshark就抓不到任何数据包。方便的办法就是本机安装一个虚拟机,通过本机和虚拟机通信进行实验。我使用的是VirtualBox+winXP.
  3. Pcap.Net:是一个WinPcap的.NET wrapper,基于这个库,我们就可以很方便的通过C#代码来实现下面功能(通过此处下载):
    1. 获取网络设备
    2. 接收、解析数据包
    3. 创建、发送数据包

在建立好实验环境之后,还需要进行一些简单的配置,保证宿主机和虚拟机之间的网络是畅通的。

将虚拟机网络设置为"Host-only Adapter"模式。

虚拟机网络设置好之后,就可以配置本机和虚拟机IP地址了,然后保证宿主机可以ping通虚拟机。

环境验证

通过上面的步骤,简单的实验环境就建立完成了,下面就要来实现客户端和服务端了,试试实验环境是否能够正常工作。

服务端

首先,将虚拟机(192.168.56.102)作为服务端,运行下面一段代码创建一个简单的socket server,服务端绑定192.168.56.102:8081:

import sys
from socket import * HOST = "192.168.56.102"
PORT = 8081
BUFSIZ = 1024
ADDR = (HOST, PORT) server = socket(AF_INET, SOCK_STREAM)
print "Socket created"
try:
server.bind(ADDR)
except error, msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit() server.listen(10)
print 'Socket now listening' while True:
conn, addr = server.accept()
try:
data = conn.recv(100)
if data:
print data
except Exception, e:
print e
conn.close()

客户端

客户端的实现在本机(192.168.56.101),使用一段基于Pcap.Net的代码向服务器发送一个[SYN]包(TCP连接建立需要进行三次握手,[SYN]包就是第一个握手包),来请求建立TCP连接。

在客户端代码中,通过Pcap.Net实现了两个工具函数,一个用来获取本机网卡设备列表,一个用在构造不同类型的TPC包。

获取本机网卡设备列表代码:

public static PacketDevice GetNICDevice()
{
// Retrieve the device list from the local machine
IList<LivePacketDevice> allDevices = LivePacketDevice.AllLocalMachine; if (allDevices.Count == )
{
Console.WriteLine("No interfaces found! Make sure WinPcap is installed.");
return null;
} // Print the device list
for (int i = ; i != allDevices.Count; ++i)
{
LivePacketDevice device = allDevices[i];
Console.Write((i + ) + ". " + device.Name);
if (device.Description != null)
Console.WriteLine(" (" + device.Description + ")");
else
Console.WriteLine(" (No description available)");
} int deviceIndex = ;
do
{
Console.WriteLine("Enter the interface number (1-" + allDevices.Count + "):");
string deviceIndexString = Console.ReadLine();
if (!int.TryParse(deviceIndexString, out deviceIndex) ||
deviceIndex < || deviceIndex > allDevices.Count)
{
deviceIndex = ;
}
} while (deviceIndex == ); return allDevices[deviceIndex - ];
}

另一段重要的代码就是构造TCP包的代码,根据OSI七层模型,下面代码中分别创建了链路层、网络层和传输层的部分,然后生成一个数据包:

public static Packet BuildTcpPacket(EndPointInfo endPointInfo, TcpControlBits tcpControlBits, List<TcpOption> tcpOptionList = null)
{
EthernetLayer ethernetLayer =
new EthernetLayer
{
Source = new MacAddress(endPointInfo.SourceMac),
Destination = new MacAddress(endPointInfo.DestinationMac),
EtherType = EthernetType.None, // Will be filled automatically.
}; IpV4Layer ipV4Layer =
new IpV4Layer
{
Source = new IpV4Address(endPointInfo.SourceIp),
CurrentDestination = new IpV4Address(endPointInfo.DestinationIp),
Fragmentation = IpV4Fragmentation.None,
HeaderChecksum = null, // Will be filled automatically.
Identification = ,
Options = IpV4Options.None,
Protocol = null, // Will be filled automatically.
Ttl = ,
TypeOfService = ,
}; TcpLayer tcpLayer =
new TcpLayer
{
SourcePort = endPointInfo.SourcePort,
DestinationPort = endPointInfo.DestinationPort,
Checksum = null, // Will be filled automatically.
SequenceNumber = seqNum,
AcknowledgmentNumber = ackNum,
ControlBits = tcpControlBits,
Window = windowSize,
UrgentPointer = ,
Options = (tcpOptionList == null) ? TcpOptions.None : new TcpOptions(tcpOptionList),
}; PacketBuilder builder = new PacketBuilder(ethernetLayer, ipV4Layer, tcpLayer); return builder.Build(DateTime.Now);
}

主程序中,首先配置了客户端和服务器的端口、IP/MAC地址信息,然后通过前面两个工具函数构造一个TCP连接建立请求包([SYN]包),并通过"VirtualBox Host-Only Network"网卡发送给服务端。

static void Main(string[] args)
{
// Take the selected adapter
PacketDevice selectedDevice = Utils.GetNICDevice(); // 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 = "08:00:27:70:A6:AE";
endPointInfo.SourceIp = "192.168.56.101";
endPointInfo.DestinationIp = "192.168.56.102";
endPointInfo.SourcePort = ;
endPointInfo.DestinationPort = ; using (BerkeleyPacketFilter filter = communicator.CreateFilter("tcp port " + endPointInfo.DestinationPort))
{
// Set the filter
communicator.SetFilter(filter);
} communicator.SendPacket(Utils.BuildTcpPacket(endPointInfo, TcpControlBits.Synchronize, null));
PacketHandler(communicator, endPointInfo);
} Console.WriteLine("Press Enter to Quit!");
Console.ReadLine(); } private static void PacketHandler(PacketCommunicator communicator, EndPointInfo endPointInfo)
{
Packet packet = null; do
{
PacketCommunicatorReceiveResult result = communicator.ReceivePacket(out packet); switch (result)
{
case PacketCommunicatorReceiveResult.Timeout:
// Timeout elapsed
continue;
case PacketCommunicatorReceiveResult.Ok:
Utils.PacketInfoPrinter(packet);
break;
default:
throw new InvalidOperationException("The result " + result + " should never be reached here");
}
} while (true);
}

运行代码

代码完成了,下面看看运行效果,为了直观的看到数据包的传输,这是就可以打开Wireshark了。

为了避免抓到不相关的数据包,可以设置Wireshark中的filter,然后开始抓取。

下面运行代码,并选择正确的网卡。通过console和Wireshark的输出可以看到,我们成功的生产了一个[SYN]包并发送到了服务器。

根据TCP连接建立过程可以知道,客户端发送[SYN]包后,期待从服务器得到一个[SYN, ACK]包。

到这里,说明前面搭建的环境,以及客户端和服务端的代码都是可以正常工作的了。

谁的[RST]包

从上面的结果中看到,客户端在收到[SYN, ACK]包之后,发送了一个[RST]包重置这条TCP连接。

仔细查看了代码发现,客户端的代码中并没有发送[RST]包。那么这个[RST]包是哪里来的呢?

操作系统中有协议栈的概念,所以来自应用层的数据,都会一层层的经过操作系统协议栈处理,然后通过网卡发送出去。

当客户端网卡收到[SYN, ACK]包后,这个包会被我们的Pcap.Net程序捕获,也会被传送给客户端操作系统。由于通过Pcap.Net构造的[SYN]包是没有经过操作系统协议栈的,所以操作系统会认为[SYN, ACK]包是一个无效TCP包,并通过[RST]包重置TCP连接。

到这里,多余[RST]包就可以解释了。

避免[RST]包

为了避免操作系统协议栈对Pcap.Net程序的影响,通过IP安全策略(通过Run->"secpol.msc"打开设置)的设置,可以避免操作系统从本机(192.168.56.101)向虚拟机(192.168.56.102)发送数据包。

设置完成后,再次运行程序,这是程序就正常了。

由于客户端没有发送[ACK]包来确认来自服务端的[SYN, ACK]包,根据TCP工作原理,服务端会进行重传。

总结

本文中介绍了TCP实验环境的搭建,通过Pcap.Net创建了一个客户端,可以构造不同类型的TCP数据包,并通过特定网卡向服务器发送。

后面继续基于这个环境来看看TCP的一些基本概念,TCP连接、状态变迁等等。

动手学习TCP: 环境搭建的更多相关文章

  1. 动手学习TCP:总结和索引

    TCP是一个十分复杂的协议,通过前面几篇文章只涉及了TCP协议中一些基本的概念. 虽然说都是一些TCP最基本的概念,但是试验过程中一直在踩坑,例如:TCP flag设置错误,seq.ack号没有计算正 ...

  2. 动手学习TCP:TCP特殊状态

    前面两篇文章介绍了TCP状态变迁,以及通过实验演示了客户端和服务端的正常状态变迁. 下面就来看看TCP状态变迁过程中的几个特殊状态. SYN_RCVD 在TCP连接建立的过程中,当服务端接收到[SYN ...

  3. 动手学习TCP:4种定时器

    上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...

  4. hive_学习_01_hive环境搭建(单机)

    一.前言 本文承接上一篇:hbase_学习_01_HBase环境搭建(单机),主要是搭建 hive 的单机环境 二.环境准备 1.说明 hive 的下载来源有: 官方版本:http://archive ...

  5. 动手学习TCP:数据传输

    前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...

  6. 动手学习TCP:数据传输(转)

    前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...

  7. hbase_学习_01_HBase环境搭建(单机)

    一.前言 本文承接上一篇:hadoop_学习_02_Hadoop环境搭建(单机)  ,主要是搭建HBase的单机环境 二.环境准备 1.说明 hbase 的下载来源有: 官方版本:http://arc ...

  8. hadoop_学习_02_Hadoop环境搭建(单机)

    一.环境准备 1.说明 hadoop的下载来源有: 官方版本:http://archive.apache.org/dist/hadoop/ CDH版本:http://archive.cloudera. ...

  9. 从0开始学爬虫9之requests库的学习之环境搭建

    从0开始学爬虫9之requests库的学习之环境搭建 Requests库的环境搭建 环境:python2.7.9版本 参考文档:http://2.python-requests.org/zh_CN/l ...

随机推荐

  1. Ajax学习资源大全[本来是转载的,但是现在我增加了很多]

    本欲放转载区,但是这样一文章放那里基本是没有用的,帮助不了任何人!所以放新手了!! 我一般非经典或者自己用不上不转载,所以如果你不幸看见了的话,恰恰你又对AJAX有兴趣的话不防看下,也许对你有用的!! ...

  2. MAC OS设置JDK小结

    开始折腾mac pro,在网上找了些资料,自己也尝试了一下,特在这里做个小结. mac中JDK的位置 OSX默认的JDK /System/Library/Frameworks/JavaVM.frame ...

  3. Python学习之Python简介

    Python简介 Python的由来 Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种面向对象.解释型计算机程序设计语言,它是吉多·范罗苏姆(Guide van ...

  4. 【原创】使用.NET Core 1.0创建一个Self-Contained控制台应用

    开发机器:win7-x64 .NET Core版本:1.0.0-preview2-003121 Visual Studio Code:1.2.1 至于什么是Self-Contained应用类型以及与P ...

  5. 跨平台的 SQL 客户端

    The major update to SQL client was to move to the .NET Core networking libraries instead of the nati ...

  6. [Intellij] 编译报错 javacTask

    报错信息: Idea 编译报错 javacTask: 源发行版 1.6 需要目标发行版 1.6 解决方案:

  7. js事件小记

    参考javascript编程全解  javascript高级程序设计 javascript经典实例 对事件的处理方式称为事件处理程序或事件侦听器 ,对于一个元素或事件,只能设定1个事件处理程序,却可以 ...

  8. .net学习总结

    .NET 学前入门 了解.Net能做什么 了解.NET,C#语言及其特点(分清.NET和C#的关系),对.Net学习有系统全面的认识. C#基础 变量,赋值运算符.数据类型转换等. 选择结构控制(if ...

  9. javascript的错误处理

    1 onerror事件,实例代码如下: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind= ...

  10. CSS文本溢出显示省略号

    项目中常常有这种需要我们对溢出文本进行"..."显示的操作,单行多行的情况都有(具体几行得看设计师心情了),这篇随笔是我个人对这种情况解决办法的归纳,欢迎各路英雄指教. 单行 语法 ...