文章目录
- 引言
- 设计要求
- 分工安排
- 文献查阅
- 总体设计流程
- 具体设计内容
🌕博客x主页:己不由心王道长🌕!
🌎文章说明:TCP协议包自动生成工具🌎
✅系列专栏:计算机网络
🌴本篇内容:对计算机网络课设进行一个欺骗式过关🌴
☕️每日一语:这个世界本来就不完美,如果我们再不接受不完美的自己,那我们要怎么活。☕️
🚩 交流社区:己不由心王道长(优质编程社区)
引言
传输控制协议(TCP,Transmission Control Protocol)是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。
当应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,TCP则把数据流分割成适当长度的报文段,最大传输段大小(MSS)通常受该计算机连接的网络的数据链路层的最大传送单元(MTU) 限制。之后TCP把数据包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。
TCP为了保证报文传输的可靠,就给每人包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认 (ACK),如果发送端实体在合理的往返时延(RTT)内末收到确认,那么对应的数据(假设丢失了) 将会被重传。
在数据正确性与合法性上,TCP用一个校验和函数来检验数据是否有错误,在发送和接收时都要计算校验和,同时可以使用md5认证对数据进行加密。
在保证可靠性上,采用超时重传和挡带确认机制
在流量控制上,采用滑动窗口协议,协议中规定,对于窗口内未经确认的分组需要重传
设计要求
1)按照TCP协议,设计TCP协议包头的图形界面。
2)给定相应字段的信息,能够自动计算出校验和,并自动生成TCP协议包。要使用真实的抓包数据进行验证校验和计算结果。
3)设计和实现TCP包发送程序,采用网络助手工具进行接收,测试发送包的有效性。
4)输入输出内容能够保存。
分工安排
成员一:复制编写代码实现TCP自动计算校验和的实现,并编写生成TCP协议包代码,查阅资料,组织成员完成任务和编写报告。
成员二:负责图形用户界面的设计和实现,并对输入输出的内容进行保存,编写代码实现。
成员三:负责设计和实现TCP包的发送程序,查阅资料,进行报告编写。
文献查阅
4.1 TCP数据包首部各部分介绍
(1)源端口号 16位,表示自己主机要发送数据包的进程是哪个。
(2)目标端口号 16位,表示发送要将自己的数据包,发送给目标主机的哪个进程。
(3)序列号 32位,用来标识发送数据的顺序的,每发送一个数据包,下一个序列号就会编程,此序列号加上数据包中数据的大小,并且这个序列号并不会从零开始,而是随机生成一个序列号,然后按照发送数据包的大小进行累加。
(4)确认号 32位,,表示接收端收到了发送端发送的数据包,并且对这个数据包的序列号进行确认回复,回复方式是发送发送端数据包的序列号,加上数据包中数据的大小。
(5)数据偏移(identification) 占4位,这个部分虽然只有4位,但是它的单位是4字节,也就相当于这个部分的值算出来后,要乘以4,然后单位变成字节,比如0010,换成10进制就表示,2*4=8字节,它的功能是用来表示真正的数据部分和TCP首部的偏移量,也就是数据部分从哪个字节开始,也可以表示这个TCP包的首部有多大。
(6)保留 占4位,这部分是为了以后扩展用的,目前没什么用,一般情况下置0,即使不置0也不会影响这个TCP包。
(7)控制位(8位),它是用来表示标志位的,一共有8个标志,只要这8位中哪个位为1,就是表示哪个标志有效,从左往右第一个标志位是CWR,当它标志为1时,表示已经将拥塞窗口缩小(如果不知道拥塞窗口,可以看我之前总结的TCP的拥塞窗口机制,同时这个字段也影响IP部,相当于区分服务中的ECI示志位),第二个标志位是ECE,如果这个标志位为1,表示从发送端到接收端的网络环境拥塞(同时这个字段也影响IP首部,相当一起区分服务中的CE段),第三个标志位为URG,如果这个标志位为1,表示这个数据包中有紧急要外理的数据,第四个标志位为ACK,这人标志位为1时,表示确认应答有效(如果不知道ACK,可以看看我之前总结的确认应答机制),第五个标志位为PSH,这个标志位为1时,表示让接收端将此教据包立即上传给上层应用协议,如果为0则表示不用立即上传,而是先进行缓存,第六个标志位为RST,这个标志位为1时,表示TCP连接出现异常,需要强制断开连接,第七个标志位为SYN,这个标志位为1时,表示在进行三次握手建立连接时,客户端需要向服务端先发送一个SYN请求,表示想和服务端建立连接(如果不知道三次握手机制,可以看我之前总结的三次握手),第八标志位为FIN,这个标志位为1时,表示希望断开TCP连接(正学断开),在进行四次挥手时,用FN来表示要开始断开连接了(如果不了解四次挥手的过程,可以看我之前总结的四次挥手
(8)窗口大小(16位),用来表示接收端允许的窗口大小是多少,如果这个窗口大小是0,表示发送的是一个窗口探测
(9)校验和 占16位,用来检验TCP数据包是否在传输途中受到损坏,保证数据的正确性。
(10)紧急指针 占16位,这个部分只有在标志位URG为1有效时,才会使用,它是用来表示紧急数据在哪个位置,一般情况下,紧急数据是指,从数据开头到紧急指针所指的位置为紧急数据,需要立即进行处理。
(11)选项(24位),选项部分是一个可选部分,它其中有很多选项,可以根据我们的需要来进行选择,不同的选项用不同的位数来表示,。
(12)填充(8位),是用来补0的,因为选项只有24位,但这人部分必须是32的整教倍,因为TCP句要求这两人位置必须等于32位。
(13)数据部分(32位),这人部分虽然有32位,但它的长度是不确定的,因为在加上TCP包的首部后,不能超过TCP包限制的总大小。
4.2 TCP数据包首部检验和原理
TCP校验和(Checksum)是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。
TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。TCP校验和、IP校验和的计算方法是基本一致的,除了计算的范围不同。
TCP的校验和是必需的,而UDP的校验和是可选的。TCP和UDP计算校验和时,都要加上一个12字节的伪首部。
首先,把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。把TCP报头中的校验和字段置为0(否则就陷入鸡生蛋还是蛋生鸡的问题)。
其次,用反码相加法累加所有的16位字(进位也要累加)。
最后,对计算结果取反,作为TCP的校验和。
举个例子来解释该校验方法:
1、首先将检验和置零;
2、然后将TCP伪首部部分,TCP首部部分,数据部分都划分成16位的一个个16进制数
3、将这些数逐个相加,记得溢出的部分加到最低位上,这是循环加法:
0xc0a8+ 0x0166+……+0x0402=0x9b49
4、最后将得到的结果取反,则可以得到检验和位0x64b6
总体设计流程
5.1 系统架构图
IP数据包自动生成工具系统分为四层,分别是页面服务层,主要是完成的功能方面,包含校验和计算、发送TCP包和生成TCP包和保存输入的数据等。然后是数据处理、校验层,这一层主要负责对输入的IP进行判断,对输入数据进行对象转换便于求和,对转换的对象进行拷贝和输入数据是否为空进行判断。其次是算法模型层,这一层主要负责实现TCP校验和算法。最后一层是输入数据层,输入层也属于页面服务层,这一部分与用户直接进行交互,通过这一层来输入数据。
图3 系统架构图
5.2 TCP数据包自动生成工具系统功能框图
TCP数据包自动生成工具系统一共有四个模块,第一个模块是信息储存模块,用来将输入输出信息保存到txt文件中。第二个模块是计算模块,将用户所输入的信息进行计算,可以计算校验和。第三个模块是发送模块,根据页面输入信息,把已经构建好的tcp包和数据发送到网络助手上。第四个模块是生成TCP报文段模块,用来生成TCP报文段,以便发送。
图4 TCP协议包自动生成工具功能框图
5.3 TCP数据包自动生成工具系统操作流程
TCP数据包自动生成工具系统的流程分为四个模块。
第一个模块为合法性检验和计算校验和模块,这一模块用户首先需要输入TCP伪首部信息、源端口和目的端口,数据可以输入也可以不输入。如果不合法用户就需要重新输入并给出提示信息。当用户点击“计算校验和”按钮,可以对用户输入的目的IP地址与源IP地址进行校验,判断其是否合法,不合法给出提示信息重新输入。用户“点击计算校验和“按钮“,发送三个请求,并且把输入数据带到后台,进行ip包组建、tcp伪首部组建、tcp首部组建,数据保存并计算校验和
第二个模块为生成TCP包模块,该模块用户点击按钮,后台对TCP进行组建,把TCP首部和数据生成TCP报文段
第三个模块为发送数据包模块,这一模块可以点击发送按钮,后台发送第二个模块生成的TCP报文段到网络助手上,网络助手得先开始服务。
第四个保存数据模块,这一模块通过输入输出流保存用户在界面输入的数据到本地的存储器中,保存位置固定。
具体设计内容
6.1 图形用户界面设计
6.1.1图形化界面内的基本内容
本程序的图形化界面主要包括:根据查询资料TCP协议包头的结构设计了界面中的元素主要有源IP地址、目的IP地址、协议类型、TCP总长度、源端口、目的端口、序号、确认号、数据偏移等等TCP首部信息、TCP数据,校验和,又因为本程序有检验用户输入是否是正确的所以添加了ip地址合法性与计算校验和按钮绑定,源ip地址、目的ip地址和源端口和目的端口都有默认值且可以更改,四个按钮,前三个按钮的顺序是从左到右依次执行。TCP数据是保存输入输出按钮。
图形化界面基本如下图
图6 图形化基本界面
6.1.2图形化扩展功能
1)在用户点击检验计算校验和,如果ip地址不合法,则进行以下输出,如下图所示
图9 检验提示框
6.1.3关键代码
1)对输入的的ip和端口在后台进行组包方便计算校验和。
/**
* 通过ip结构调用构建tcp伪首部
* @param protocol_Type
* @param TCPLength
*/
public void structure_PsdTCPHeader(char protocol_Type, short TCPLength){
log.info("开始构造TCP伪首部");
//创建TCP伪首部实体类
this.psdTcpHeader= new PsdTcpHeader();
//构造TCP伪首部
psdTcpHeader.setSourceAddr(ipHeader.getSourceAddr());
psdTcpHeader.setDestinationAddr(ipHeader.getDestinationAddr());
psdTcpHeader.setProtocol_Type(protocol_Type);
psdTcpHeader.setTCP_Length(TCPLength);
//填充位置为0
psdTcpHeader.setZero((char) 0);
log.info("TCP伪首部构建成功");
log.info("tcp伪首部构建数据如下:");
System.out.println(psdTcpHeader.toString());
}
/**
* 构建tcp首部字段
* @param sourcePort
* @param destPort
*/
@PostMapping("/fill_TCPHeader")
public void fill_TCPHeader(short sourcePort,short destPort){
log.info("开始构建tcp首部字段");
//新建一个TCP首部字段
this.tcpHeader = new TCPHeader();
//设置TCP16位的源端口号
tcpHeader.setSrcPort(sourcePort);
//设置TCP16位目的端口号
tcpHeader.setDstPost(destPort);
//设置SYN序列号
tcpHeader.setSequenceNum(0);
//设置ACK序列号置为0
tcpHeader.setAcknowledgment(0);
//TCP长度和保留位
tcpHeader.setHdrLen((char) (sizeOf(tcpHeader)/4<<4|0));
//TCP标志位:修改这里来实现不同的标志位探测,2是SYN,1是FIN,16是ACK探测,等等。
tcpHeader.setFlags((char) 2);
//窗口大小
tcpHeader.setAdvertisedWindow((short) 16);
//偏移大小
tcpHeader.setTh_urp((short) 0);
//校验和暂时填为0
tcpHeader.setChecksum((short) 0);
log.info("tcp首部字段构建完成,校验和暂定为0");
log.info("TCP首部数据如下");
System.out.println(tcpHeader.toString());
}
2)计算校验和
/**
* 计算校验和,填充tcp首部的校验和字段,并返回tcp对象给前端对tcp首段进行渲染
* @return
*/
@RequestMapping("/checkSum")
public TCPHeader checkSum() throws IOException {
log.info("开始计算校验和");
//初始化校验和缓冲区
byte[] check_buff = new byte[MAX_BUFF_LEN];
//把tcp伪首部转换为数组对象
byte[] psdTCP = objectToByte(psdTcpHeader);
//把tcp数组复制到缓冲区
check_buff = StringUtils.copyOf(psdTCP, check_buff, 0);
// 把tcp首部转换为数组对象
byte[] TCPHeader = objectToByte(tcpHeader);
//把tcp数组复制到缓冲区
check_buff = StringUtils.copyOf(TCPHeader,check_buff,psdTCP.length-1);
//把数据转换为byte[]对象
byte[] bytes = TCP_Data.getBytes();
//把数组复制到缓冲区
check_buff = StringUtils.copyOf(bytes,check_buff,psdTCP.length-1+TCPHeader.length-1);
//计算校验和并设置到tcp首部字段中
short s = (short) checkUtil.computeChecksum(check_buff);
log.info(check_buff.toString());
tcpHeader.setChecksum(s);
log.info(checkUtil.encodeHEX(s));
log.info("校验和计算完成,返回数据完成");
log.info(tcpHeader.toString());
//返回tcp数据,填充前端界面
return tcpHeader;
}
3)填写TCP报文段
/**
* 填充TCP发送缓冲区(报文段) 生成TCP包
* 返回值是tcp报文段
*/
@RequestMapping("/TCP_Message")
public void fill_TCP_Message() throws IOException {
log.info("正在生成tcp报文段");
log.info("我是分割线");
//设置TCP缓冲区
this.TCP_Buff = new byte[MAX_BUFF_LEN];
// 把tcp首部转换为数组对象
byte[] TCPHeader = objectToByte(tcpHeader);
//把tcp数组复制到缓冲区
TCP_Buff = StringUtils.copyOf(TCPHeader,TCP_Buff,0);
//把数据转换为byte[]对象
byte[] bytes = TCP_Data.getBytes();
//把数组复制到缓冲区
TCP_Buff = StringUtils.copyOf(bytes,TCP_Buff,TCPHeader.length-1);
log.info(Arrays.toString(TCP_Buff));
}
6.2 校验和算法设计与实现
6.2.1 校验和检验举例
图11抓包成功后TCP数据报的数据位置
Source Port 80:源端口号
Destination 14167:目的端口号
Stream index 566:流指数
TCP Segment Len :0 TCP段总长度
Sequence Number:0(relative sequence number):序列号(相对序列号)
Sequence Number(raw):3509507033
Next Sequence Number:1(relative sequence number):下一个序列号
Acknowledgment Number: 1(relative ack number)
Acknowledgment Number(raw):3563183757 原始确认号
1000… = Header Length:32 bytes(8)
Flags:0x012(SYN,ACK):TCP 标记字段
45 00:其中4代表IPv4,5代表首部长度,表明没有可变部分
00 34:表示IP包总长度
d2 a8:标识字段
40 00:片段偏移量
37:生存时间TTL
06:协议号,包装封装的上层协议为TCP(6)
ef 6e:头部数据校验和
0e d7 b1 27:源IP地址
C0 a8 01 06:目的IP地址
进行校验和的计算:
(1)校验和置0:0000
(2)相加(16位一组):45 00+00 34+d2 a8+40 00+37 06+ef 6e+0e d7+b1 27+c0 a8+01 06
(3)进位加到最后
(4)结果取反
6.2.2 校验和计算程序设计流程
图12校验和计算程序设计流程
6.2.3 TCP包首部校验和的计算方法
(1)将TCP首部的校验和字段置为0000。
(2)将首部各个字段的值都转化成二进制,按顺序连接成一起,形成一段由二进制组成的字符串。
(3)将二进制字符串每隔16位进制相加
(4)如果有溢出,将溢出的部分加到尾部
(5)将结果得到的16位二进制取反码
(6)将16位二进制,转化成4位十六进制
6.2.4 计算校验和算法流程图如下
图13计算校验和算法流程图
6.2.5 关键代码
**
* 计算校验和,填充tcp首部的校验和字段,并返回tcp对象给前端对tcp首段进行渲染
* @return
*/
@RequestMapping("/checkSum")
public TCPHeader checkSum() throws IOException {
log.info("开始计算校验和");
//初始化校验和缓冲区
byte[] check_buff = new byte[MAX_BUFF_LEN];
//把tcp伪首部转换为数组对象
byte[] psdTCP = objectToByte(psdTcpHeader);
//把tcp数组复制到缓冲区
check_buff = StringUtils.copyOf(psdTCP, check_buff, 0);
// 把tcp首部转换为数组对象
byte[] TCPHeader = objectToByte(tcpHeader);
//把tcp数组复制到缓冲区
check_buff = StringUtils.copyOf(TCPHeader,check_buff,psdTCP.length-1);
//把数据转换为byte[]对象
byte[] bytes = TCP_Data.getBytes();
//把数组复制到缓冲区
check_buff = StringUtils.copyOf(bytes,check_buff,psdTCP.length-1+TCPHeader.length-1);
//计算校验和并设置到tcp首部字段中
short s = (short) checkUtil.computeChecksum(check_buff);
log.info(check_buff.toString());
tcpHeader.setChecksum(s);
log.info(checkUtil.encodeHEX(s));
log.info("校验和计算完成,返回数据完成");
log.info(tcpHeader.toString());
//返回tcp数据,填充前端界面
return tcpHeader;
}
/**
* 计算校验和
* @param buf
* @return
*/
public static long computeChecksum( byte[] buf ){
int length = buf.length;
log.info(String.valueOf(buf.length));
int i = 0;
long sum = 0;
long data;
// 循环通过所有16位字,除非剩下0或1字节。
while( length > 1 ){
data = ( ((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0xFF));
sum += data;
if( (sum & 0xFFFF0000) > 0 ){
sum = sum & 0xFFFF;
sum += 1;
}
i += 2;
length -= 2;
}
if (length > 0 ){ // 即剩余8位数据。
sum += (buf[i] << 8 & 0xFF00); // 创建一个16位字,其中8个lsb为0,并将其添加到总和中。
if( (sum & 0xFFFF0000) > 0) {
sum = sum & 0xFFFF;
sum += 1;
}
}
sum = ~sum;
sum = sum & 0xFFFF;
return sum;
}
6.3生成TCP包报文段的方法
6.3.1 流程
6.3.2 关键代码
1、对输入的源ip、目的IP进行接收并且在后台进行ip组包
/**
* 填充IP首部结构
* @param sourceIp
* @param destIp
* @param protocol_Type
* @param TCPLength
*/
@RequestMapping("/fill_IpHeader")
@ResponseBody
public void fill_IPHeader(@RequestParam(value = "sourceIp[]") int[] sourceIp, @RequestParam(value = "destIp[]") int[] destIp, @RequestParam(value = "protocol_Type") char protocol_Type, @RequestParam(value = "TCPLength") short TCPLength){
/**
* 转换成IpAddress格式
*/
//源ip转换
IpAddress sourceIp1 = new IpAddress((short) sourceIp[0], (short) sourceIp[1], (short) sourceIp[2], (short) sourceIp[3]);
//目的ip转换
IpAddress destIp1 = new IpAddress((short) destIp[0], (short) destIp[1], (short) destIp[2], (short) destIp[3]);
log.info("sourceIp:{}",sourceIp);
log.info("protocol_Type:{}",protocol_Type);
log.info("destIp:{}",destIp);
log.info("TCPLength:{}",TCPLength);
//构建IP首部
this.ipHeader = new IpHeader();
/**
* 填充IP首部
*/
//IP首部长度
ipHeader.setVersion_HLen((char) (IPVER<<4|sizeOf(ipHeader)/4));
//IP服务类型
ipHeader.setTOS((char) 0);
//IP总长度
ipHeader.setLength((short) (sizeOf(ipHeader)+sizeOf(tcpHeader)+sizeOf(tcp_send_data)));
//数据包标识
ipHeader.setIdent((short) 0);
//标志位
ipHeader.setFlags_Offset((short) 0);
//生存时间
ipHeader.setTTL((char) 128);
//协议类型
ipHeader.setProtocol(protocol_Type);
//IP校验和 暂时设置为0
ipHeader.setChecksum((short) 0);
//32位源地址IP地址
ipHeader.setSourceAddr(sourceIp1);
//32位目标IP地址
ipHeader.setDestinationAddr(destIp1);
log.info("IP包构建完成");
log.info("IP包数据如下:");
System.out.println(ipHeader.toString());
structure_PsdTCPHeader(protocol_Type,TCPLength);
}
2、根据ip信息、和输入的端口信息进行tcp伪首部组包
/**
* 通过ip结构调用构建tcp伪首部
* @param protocol_Type
* @param TCPLength
*/
public void structure_PsdTCPHeader(char protocol_Type, short TCPLength){
log.info("开始构造TCP伪首部");
//创建TCP伪首部实体类
this.psdTcpHeader= new PsdTcpHeader();
//构造TCP伪首部
psdTcpHeader.setSourceAddr(ipHeader.getSourceAddr());
psdTcpHeader.setDestinationAddr(ipHeader.getDestinationAddr());
psdTcpHeader.setProtocol_Type(protocol_Type);
psdTcpHeader.setTCP_Length(TCPLength);
//填充位置为0
psdTcpHeader.setZero((char) 0);
log.info("TCP伪首部构建成功");
log.info("tcp伪首部构建数据如下:");
System.out.println(psdTcpHeader.toString());
}
3、根据以上信息进行tcp组包
/**
* 构建tcp首部字段
* @param sourcePort
* @param destPort
*/
@PostMapping("/fill_TCPHeader")
public void fill_TCPHeader(short sourcePort,short destPort){
log.info("开始构建tcp首部字段");
//新建一个TCP首部字段
this.tcpHeader = new TCPHeader();
//设置TCP16位的源端口号
tcpHeader.setSrcPort(sourcePort);
//设置TCP16位目的端口号
tcpHeader.setDstPost(destPort);
//设置SYN序列号
tcpHeader.setSequenceNum(0);
//设置ACK序列号置为0
tcpHeader.setAcknowledgment(0);
//TCP长度和保留位
tcpHeader.setHdrLen((char) (sizeOf(tcpHeader)/4<<4|0));
//TCP标志位:修改这里来实现不同的标志位探测,2是SYN,1是FIN,16是ACK探测,等等。
tcpHeader.setFlags((char) 2);
//窗口大小
tcpHeader.setAdvertisedWindow((short) 16);
//偏移大小
tcpHeader.setTh_urp((short) 0);
//校验和暂时填为0
tcpHeader.setChecksum((short) 0);
log.info("tcp首部字段构建完成,校验和暂定为0");
log.info("TCP首部数据如下");
System.out.println(tcpHeader.toString());
}
4、根据输入数据,在后台进行接收保存
/**
* tcp数据部分,接收修改数据
* @param data
*/
@RequestMapping("/tcpData")
public void TCP_data(String data){
log.info("数据修改为{}",data);
this.TCP_Data = data;
}
5、根据tcp首部和数据进行tcp报文段组建
/**
* 填充TCP发送缓冲区(报文段) 生成TCP包
* 返回值是tcp报文段
*/
@RequestMapping("/TCP_Message")
public void fill_TCP_Message() throws IOException {
log.info("正在生成tcp报文段");
log.info("我是分割线");
//设置TCP缓冲区
this.TCP_Buff = new byte[MAX_BUFF_LEN];
// 把tcp首部转换为数组对象
byte[] TCPHeader = objectToByte(tcpHeader);
//把tcp数组复制到缓冲区
TCP_Buff = StringUtils.copyOf(TCPHeader,TCP_Buff,0);
//把数据转换为byte[]对象
byte[] bytes = TCP_Data.getBytes();
//把数组复制到缓冲区
TCP_Buff = StringUtils.copyOf(bytes,TCP_Buff,TCPHeader.length-1);
log.info(Arrays.toString(TCP_Buff));
}
6.4发送TCP包报文段的方法
6.4.1 流程
图14发送TCP包
6.4.2 关键代码
1.构建目标IP地址对象,获取目标端口,使用Java的net包的Socket套接字进行连接,并利用输出流发送数据到网络助手上
```java
@RequestMapping(“/sendTCP”)
public void send_Out_TCP_Protocol_Packet() {
//获取并构建目的ip对象
String ip = ipHeader.getDestinationAddr().getByte1()+“.”+ipHeader.getDestinationAddr().getByte2()+“.”+ipHeader.getDestinationAddr().getByte3()+“.”+ipHeader.getDestinationAddr().getByte4();
log.info(ip.toString());
//获取目的端口
short destPort = tcpHeader.getDstPost();
try {
//1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
socket = SocketUtils.getSocket(ip,destPort);
//获取输出流对象
OutputStream os = socket.getOutputStream();
//获取输出字节流
OutputStreamWriter osw = new OutputStreamWriter(os);
osw.write(String.valueOf(tcpHeader+TCP_Data));
//刷新
osw.flush();
//关闭socket
SocketUtils.close(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
七、程序验证
系统的主界面图如下图,伪首部的源ip地址、目的ip地址需要用户输入(已有默认值,可以修改),tcp首部的源端口、目的端口需要用户输入(已有默认值),TCP数据部分可以输入也可以不输入
图20 系统主界面
若用户输入IP地址不符合规定,则会报错,如下图所示;
图21 IP地址错误提示图
计算校验和展示,用wireshark抓取包,用程序进行验证他,发现结果正确,说明程序的正确性。
图23校验和验证图
利用程序和网络调试助手进行发送验证。
图25目的主机1图
保存输入输出内容的txt文件展示。
八、所遇到的问题及解决方案
成员一:
第一个问题:当我运行程序时,程序报错端口冲突,由于已经开启了某个IP地址的端口号。
解决办法:查看本主机可用的端口进行修改,关闭已经开启的端口。
第二个问题:前端后端相连接时的数据传输,点击一个校验按钮,发送三个信息。
解决办法:利用Ajax发送请求,为了保证步骤的顺序性,利用同步策略,依次发送三个请求
第三个问题:计算校验和的时候,Java语言不支持很多c语言的内存函数。
解决办法:编写方法,把已经组包的对象变为byte数组,通过数组相加,算出校验和。
第四个问题:校验和计算出现负数情况。
解决办法:由于Java语言的byte型数据的大小范围为-127~127范围,而ip地址会出现0~255范围的数,导致数据溢出,转为正数计算iData = datas[i] & 0xff; // 变为正数计算
。
成员二:
问题一:在制作图形化界面时,难度在于获取界面上输入的数据,在学习了js的dom函数之后,成功解决了获取数据的问题。
问题二:对数据的保存,在利用字节输出流保存数据到本地文件时,保存的内容具有不确定性
解决方案:利用文件读写流FileWriter 对数据进行保存
成员三:在对TCP包进行发送时,对Socket需要提供的ip地址需要进行拼接出现错误
解决方案:利用字符串,取出每一个ip,按照IP格式进行拼接,并创建套接字进行输入。
九、设计心得
成员一:
在本次课程设计中我负责了TCP计算校验和和生成TCP协议包,并编写代码对前端界面数据进行连接交互。在计算校验和模块需要同时接收三个请求,并构建三个包,分别是ip包、tcp包、tcp伪首部,在这个模块中,我复习了ip协议包头结构和TCP协议包头,使我对tcp/ip的知识有了更深的理解和认识,在生成TCP协议包模块,这个模块是生成tcp报文段,以便于发送。在编写界面交互的时候,需要与界面制作的队友进行沟通和交流,加深了我们的友谊和团队合作的心得,但是在本次课程设计中我也发现了我许多的不足,例如代码的书写习惯不好,程序的逻辑不清晰等等,但是在不断的学习与钻研下,这些困难都被一一破解,我的技术也得到了提升,收获十分充足。通过实践我学到了更多的知识,也知道了只有通过实践才能更好的学习知识,在实践的过程中我更加深刻的理解了书本上所学的知识,并加以实现,这对我的学习是十分有帮助。
成员二:
在本次实验中,我负责了设计和实现TCP包的发送程序,并对课设进行资料收集,对报告内容进行编写,在设计发送程序时,需要使用套接字,我们团队经过讨论选择了Java语言作为课程设计的语言,在确定语言之后我对Java关于网络编程的相关知识进行了深入了解,学习了如何使用Java语言创建套接字,并利用IO流对数据进行传输,我对网络助手也进行了学习和使用,成功在网络助手上接收到我从Java上发送的信息。
通过本次课设,强化了我动手的能力,也促使我进行了自学,这对于计算机专业的学生来说是十分重要的,本次课程设计也强化了我的团队协作和沟通能力。
成员三:
在本次实验中,我负责了程序的图形化界面的设计和代码编写,在这次的图形化界面的编写过程中我根据TCP协议包头结构设计了包含有版本、首部长度、服务类型、总长度、标识、标志、片偏移、生存时间、协议、首部校验和、源IP、目的IP的基本界面、数据、TCP报文等等,设计了计算校验和、生成TCP协议包、发送TCP包、保存输入输出内容等四个按钮,在我也设计了保存输入输出内容的代码,使得我对Java代码和前端html语言都有了更加深刻的理解。
通过实践我学到了更多的知识,只有通过实践才能更好的学习知识,在实践的过程中我更加深刻的理解了书本上所学的知识,并加以实现,这对我的学习是十分有帮助,在以后的学习中,动手动脑和团队合作会更加在我的生活中得以突出。
十、参考文献
[1]《计算机网络教程》 张晓明 清华大学出版社,2010;
[2]《以十进制为中心的进制转换算法的设计与实现》 崔方送,程国忠,潘大志
[3]《软硬件技术》(2012年03期);
[5]刘派.IP首部校验算法[J].电脑知识与技术,2010,6(19):5194-5196.