资源描述
实 验 报 告
试验名称: 计算机网络课程设计
学生姓名: xxxxxxxxxxxxxxx
专 业: xxxxxxxxxxxxxxx
班 级: xxxxxxxxxxxxxxx
学 号: xxxxxxxxxxxxxxx
指导老师: xxxxxxxxxxxxxxx
试验成绩:
试验地点:
试验时间: 年 5 月 6 日
一、试验目标和试验要求
1、试验目标
将书本上抽象概念和具体实现技术结合,经过网络软件编程实践,深入了解理论课上学习到ARP、IP、TCP等关键网络协议原理,经过自己动手编程封装和发送这些数据包,加深对网络协议了解,掌握协议帧结构和工作原理及其对协议栈贡献。
2、试验要求
网络课程设计包含两个部分内容:
题目一是数据包封装发送和解析(ARP/IP/TCP),要求使用Winpcap技术和Socket技术,依据ARP/IP/TCP帧结构,封装数据包发送到局域网中。另外要捕捉网络中TCP/IP/ARP数据包,解析数据包内容,并将结果显示,并同时写入日志文件。
题目二是从可选题目中选择一个,可选题目均是网络应用小程序,要求小组使用网络编程技术设计并实现一个网络应用程序,加深对网络协议协了解,并锻炼网络编程能力。
二、试验设备(环境)及要求
1、试验硬件设备:
计算机型号:联想ThinkPad T430u
处理器型号:Intel i5 主频:1.8Hz
网卡型号:(1)Realtek PCIe GBE (2)Broadcom 802.11n
2、试验软件要求:
操作系统:Windows10
应用软件:Visual Studio Pro
3、小组组员及分工:
三、试验内容和步骤
1、试验1:数据包封装发送和解析(ARP/IP/TCP)
(1)试验内容
1) 程序目标:
依据IP帧结构,封装IP数据包发送到局域网中。并捕捉网络中IP数据包,解析数据包内容,并将结果显示,并同时写入日志文件。
2) 程序功效:
以命令行形式运行
在标准输出中显示捕捉IP报文首部字段内容。
使用winpcap访问网卡,手动封装
定义IP首部数据结构
填充数据包,发送数据包,捕捉数据包
使用winpcap,捕捉IP数据包
(2)关键步骤
1) 总体设计:
a. 获取设备列表并打印,打开所选择适配器;
b. 准备工作:定义ip相关结构体、打开要存放结果文件,设置过滤器,手写ip数据报(内容有没有效MAC源和目标地址,和均为本机地址ip源地址和目标地址,即发给自己一个ip报文),设置抓到数据报解析和输出到文件操作函数(解析ip报,打印并写入文件:报文版本、协议、源和目标地址等)。
c. 发包、抓包。
d. 分析获取数据。
2) 具体实现:
#define HAVE_REMOTE
#include "pcap.h"
#include "remote-ext.h"
#include "stdio.h"
#include "stdlib.h"
/*4字节IP地址*/
typedef struct ip_address{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/*IPv4首部*/
typedef struct ip_header{
u_char ver_ihl; //版本(4bits)+首部长度(4bits)
u_char tos; //服务类型
u_short tlen; //总长类型
u_short identification; //标识
u_short flags_fo; //标志位+段偏移量
u_char ttl; //存活时间
u_char proto; //协议
u_short crc; //首部校验和
ip_address daddr; //目标地址
ip_address saddr; //源地址
u_int op_pad; //选项和填充
}ip_header;
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
/* packet handler 函数原型 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
int main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
u_int netmask;
char packet_filter[] = "ip"; //抓包类型
struct bpf_program fcode;
pcap_dumper_t *dumpfile;
/* 获取本机设备列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 打印适配器列表 */
for(d=alldevs; d; d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if(i==0)
{
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):",i);
scanf("%d", &inum);
if(inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/* 跳转到选中适配器 */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* 打开设备 */
if ( (adhandle= pcap_open(d->name, // 设备名
65536, // 65535确保能捕捉到不一样数据链路层上每个数据包全部内容
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式
1000, // 读取超时时间
NULL, // 远程机器验证
errbuf // 错误缓冲池
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/* 打开堆文件*/
dumpfile = pcap_dump_open(adhandle, "D:\\save.txt");
if(dumpfile==NULL)
{
fprintf(stderr,"\nError opening output file\n");
return -1;
}
/* 检验数据链路层,只考虑以太网 */
if(pcap_datalink(adhandle)!=DLT_EN10MB)
{
fprintf(stderr, "nThis program works only on Ethernet networds.n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
if(d->addresses != NULL)
//取得接口第一个地址掩码
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
//假如接口没有地址,那么我们假设一个C类掩码
netmask=0xffffff;
//编译过滤器
if(pcap_compile(adhandle,&fcode,packet_filter,1,netmask)<0)
{
fprintf(stderr,"nUnable to compile the packet filter. Check the syntax.n");
//释放设备列表
pcap_freealldevs(alldevs);
return -1;
}
//设置过滤器
if(pcap_setfilter(adhandle,&fcode)<0)
{
fprintf(stderr,"nError setting the filter.n");
//释放设备列表
pcap_freealldevs(alldevs);
return -1;
}
printf("\nlistening on %s...\n", d->description);
//释放设备列表
pcap_freealldevs(alldevs);
/*手写数据包*/
u_char packet[100];
/*假设在以太网上,设置MAC目标地址为1:1:1:1:1:1*/
packet[0]=1;
packet[1]=1;
packet[2]=1;
packet[3]=1;
packet[4]=1;
packet[5]=1;
/*设置MAC源地址为2:2:2:2:2:2*/
packet[6]=2;
packet[7]=2;
packet[8]=2;
packet[9]=2;
packet[10]=2;
packet[11]=2;
/*设置ip类型*/
packet[12]=0x08;
packet[13]=0x00;
packet[14]=0x45;
packet[15]=0x20;
packet[16]=0x00;
packet[17]=0x28;
packet[18]=0xcb;
packet[19]=0x16;
packet[20]=0x00;
packet[21]=0x00;
packet[22]=0x2e;
packet[23]=0x06;
packet[24]=0x3e;
packet[25]=0xe6;
packet[26]=0xc0;//192
packet[27]=0xa8;//168
packet[28]=0x01;//1
packet[29]=0x64;//100
packet[30]=0xc0;
packet[31]=0xa8;
packet[32]=0x01;
packet[33]=0x64;
packet[34]=0x8f;
packet[35]=0x50;
/*填充剩下内容*/
for(i=36;i<100;i++)
{
packet[i]=i%256;
}
/*发送数据包*/
if (pcap_sendpacket(adhandle, packet, 100 /* size */) != 0)
{
fprintf(stderr,"nError sending the packet: n", pcap_geterr(adhandle));
return 0;
}else{
printf("Send successed");
}
/* 开始捕捉 */
pcap_loop(adhandle, 0, packet_handler, (unsigned char *)dumpfile); //回调方法捕捉数据包
pcap_close(adhandle);
return 0;
}
/* 每次捕捉到数据包时,libpcap全部会自动调用这个回调函数 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct tm *ltime;
char timestr[16];
ip_header *ih;
u_int ip_len;
time_t local_tv_sec;
//保留数据包到文件中
pcap_dump((u_char*)param, header, pkt_data);
/* 将时间戳转换成可识别格式 */
local_tv_sec = header->ts.tv_sec;
ltime=localtime(&local_tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
//打印数据包时间戳和长度
printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
//取得IP数据包头部位置
ih = (ip_header*)(pkt_data+14);//以太网头部长度
/* 打印 IP 地址和 UDP 端口 */
printf("版本+首部长度:[%u]",ih->ver_ihl);
printf("协议:[%u]",ih->proto);
printf("首部校验和:[%u]",ih->crc);
printf("目标地址:[%u.%u.%u.%u]", ih->daddr.byte1,ih->daddr.byte2,ih->daddr.byte3,ih->daddr.byte4);
printf("源地址:[%u.%u.%u.%u]\n", ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4);
FILE *fp;
fp=fopen("D:\\jiexi.txt", "a+");
fprintf(fp,"解析结果:");
fprintf(fp,"版本+首部长度:[%u]",ih->ver_ihl);
fprintf(fp,"协议:[%u]",ih->proto);
fprintf(fp,"首部校验和:[%u]",ih->crc);
fprintf(fp,"目标地址:[%u.%u.%u.%u]", ih->daddr.byte1,ih->daddr.byte2,ih->daddr.byte3,ih->daddr.byte4);
fprintf(fp,"源地址:[%u.%u.%u.%u]\n", ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4);
fclose(fp);
}
2、试验2:子网内文件传送
(1)试验内容
(明确试验内容)
设计并实现一个局域网内部文件传送工具,使用TCP协议进行可靠文件传输。以图形界面运行,不一样结点上文件自动同时
(2)关键步骤
(具体试验步骤(系统/方法/算法等),图文结合)
1) 问题定义:
实现一个局域网内文件传送和聊天软件
2) 需求分析:
局域网内文件传送应该含有以下功效:
l 在线用户立即发觉和更新
l 选择一对一或一对多对话模式
l 和选择用户进行聊天和文件传输
l 用户下线通知和更新用户列表
3) 系统设计:
参考飞鸽传书实现原理,总结设计方案以下:
l 架构设计:本系统采取当地应用程序设计,仅适适用于在同一局域网主机通信;
l 相互发觉:本系统运行时开启局域网广播线程,发出包含主机名称、主机IP和新加入标识符广播表明自己新加入;
l 用户列表:监听到其它用户广播信息后更新列表,显示目前在线用户名和用户IP;
l 更新列表:在系统退出时会广播本机信息,标识表明自己离开,其它在线用户接收到信息后更新提醒该用户下线;
l 选择用户:能够单独选择或多项选择用户列表中用户;
l 发送消息:选择用户后,在输入框中输入聊天信息,点击“发送”按钮系统会发送UDP数据包给对应用户ip地址,端口是8011;
l 接收消息:系统实时监听8011端口以接收不一样用户发来消息并展示在聊天窗口中;
l 发送文件:选择指定用户,点击“发送文件”按钮,选择本机对应文件后,和目标用户建立TCP连接,发送字节流。端口为8011;
l 接收文件:监听到有TCP连接请求时建立实时连接,系统提醒用户是否接收文件,选择接收后选择对应路径和文件名后进行接收,接收成功后再聊天窗口显示提醒信息。
4) 具体设计:
① 局域网用户列表建立:软件开启后定时使用UDP协议向255.255.255.255这个广播地址发送广播包,端口设置为8001。广播包内容包含主机名、IP、标识符等信息,已开启软件用户经过8001端口收到此广播包后,就会在自己用户列表中添加这个用户用户名、IP地址等信息,从而局域网内本系统用户全部能建立起用户列表;
② 传送和接收信息:传送聊天信息时一样使用UDP协议,用户填写消息后点击按钮调用发送消息函数,然后经过8011端口发送UDP包。系统在开启时经过开辟新线程来监听8011端口,时刻监听发送到本机信息。
③ 发送文件:用户发送文件时建立一个带参数线程来发送文件,调用对应TCP发送函数来发送文件,使用是8001端口,因为协议不一样所以和之前广播并不冲突;
④ 接收文件:软件开启时开启一个线程用来接收文件,此线程在正常工作条件下处于阻塞监听状态,直到收到建立TCP连接请求,以后提醒用户收到文件选择是否接收,以后选择保留路径,进行接收。
⑤ 用户离开:用户离线时发送一个离线广播包到255.255.255.2558001端口,包中含有LEAVE标识符,收到此广播包用户,依据包中IP地址删除用户列表中对应用户信息,并在聊天窗口显示“XXX用户已经离开”;
⑥ 聊天统计保留:考虑到用户有保留聊天统计需求,在聊天统计右侧点击“保留”按钮,能够将目前聊天统计保留为txt文件到用户指定目录中。或点击“清空”,清空目前用户聊天统计。
5) 具体实现:(C#)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace IPMessage
{
public partial class frmMain : Form
{
//用户实体
public class User
{
private string localname; //用户名
public string localName
{
get { return localname; }
set { localname = value; }
}
private string localip; //用户IP
public string localIP
{
get { return localip; }
set { localip = value; }
}
}
//用户数组申明
public static List<User> Userslist = new List<User>();
//取得本机名和本机IP
public User getInfo()
{
User u = new User();
string localName = Dns.GetHostName();//获取主机名
//Console.WriteLine("主机名:{0}", localName);
u.localName = localName;
IPHostEntry localHost = Dns.GetHostEntry(localName);
//输出对应IP地址
IPAddress localIP = null;
for (int i = 0; i < localHost.AddressList.Length; i++)
{
if (localHost.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
localIP = localHost.AddressList[i];
break;
}
}
u.localIP = localIP.ToString();
return u;
}
//发送广播消息
public void broadMessage()
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,ProtocolType.Udp);
IPEndPoint iep = new IPEndPoint(IPAddress.Parse("255.255.255.255"), 8001);
//设置Broadcast值为表示许可套接字发送广播消息,该值默认为不许可
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
User u = getInfo();
//标识符为NEW
string send = "NEW"+","+u.localName + "," + u.localIP;
byte[] sendM = System.Text.Encoding.Unicode.GetBytes(send);
//byte[] sendIP = System.Text.Encoding.Unicode.GetBytes(u.localIP);
while (isBroad)
{
socket.SendTo(sendM, iep);
Thread.Sleep(200);
}
//socket.SendTo(sendIP, iep);
socket.Close();
}
public void remove(User u)
{
Userslist.Remove(u);
this.rbChatContent.AppendText("用户:" + u.localName + "离开" + "\n");
this.lbUser.Items.Remove(u.localName + "|" + u.localIP); ;
}
//接收广播消息
public void reciveBroad()
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 8001);
socket.Bind(iep);
EndPoint ep = (EndPoint)iep;
//System.Timers.Timer t = new System.Timers.Timer(1000);
//t.Elapsed += new System.Timers.ElapsedEventHandler(theout);
while (true)
{
Byte[] getBytes = new byte[1024];
socket.ReceiveFrom(getBytes, ref ep);
string getData = System.Text.Encoding.Unicode.GetString(getBytes);
getData = getData.TrimEnd('\u0000');
string[] sArray = getData.Split(',');
//this.rbChatContent.AppendText("新上线用户:" + sArray[1] + "\n");
if (sArray.Length > 1)
{
bool exist = false;
if (Userslist!=null)
{
foreach (User us in Userslist)
{
if (us.localIP == sArray[2])
{
exist = true;
break;
}
}
}
if (exist == false && sArray[0]=="NEW")
{
User u = new User();
u.localName = sArray[1];
u.localIP = sArray[2];
if (u != null) {
Userslist.Add(u);
}
this.rbChatContent.AppendText("新上线用户:"+u.localName + "\n");
this.lbUser.Items.Add(u.localName + "|" + u.localIP);
Thread.Sleep(100);
}
else if(sArray[0] == "LEAVE")
{
User u = new User();
u.localName = sArray[1];
u.localIP = sArray[2];
if (u != null)
{
Userslist.Remove(u);
}
this.rbChatContent.AppendText("用户:" + u.localName + "离开"+"\n");
this.lbUser.Items.Remove(u.localName + "|" + u.localIP);
}
}
Thread.Sleep(200);
}
}
/// <summary>
/// 经过TcpClient满足多用户发送文件
/// </summary>
/// <param name="ip"></param>
public void tcpSend(List<string> ips)
{
FileStream fs = new FileStream(filePath, FileMode.Open);
foreach(string ip in ips)
{
TcpClient client = new TcpClient();
client.Connect(ip, 8001);
NetworkStream ns = client.GetStream();
int size = 0;
long len = 0;
while (len < fs.Length)
{
byte[] buffer = new byte[1024];
size = fs.Read(buffer, 0, buffer.Length);
ns.Write(buffer, 0, size);
len += size;
}
fs.Flush();
展开阅读全文