计算机网络上机实习报告
#题目一:文件传输系统
为巩固提⾼⽹络编程能⼒,加深对socket套接字的使⽤,并进⼀步的理解UDP和TCP之间的不同,该题⽬主要需完成以下两个⽬标:
(一)实现跨物理主机的⽂件传输系统
首先,在Windows操作系统环境下,分别使用TCP和UDP两种协议,利用socket库,利用C++语言编写完成文件传输系统。
#Windows下Socket编程实现基于TCP协议的⼤⽂件传输
1、功能描述
在客户端,⽤户选择本地某个⽂件,发送到服务端。如果客户端⽆法连接到服务端,客户端应该给出提⽰。
在服务端,接受客户端发送的⽂件,并保存。如果在传输过程中服务端检测到客户端断开连接,则服务器端应该删除未完整传送的⽂件,并给出提⽰。
2、Socket通信流程
3、程序代码
(1)FileHelper.h
#include<stdio.h>
#include<stdlib.h>
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib,"ws2_32.lib")
class FileHelper
{
private:
FILE *f;
char path_buffer[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
public:
FILE * selectfile()
{
printf("请输入要传送的文件名\n");
scanf("%s",path_buffer);
if (f=fopen(path_buffer,"rb"))
{
printf("文件打开成功\n");
return f;
}
else
{
printf("文件不存在,请重新输入\n");
return selectfile();
}
}
char * getFileName()
{
_splitpath(path_buffer, drive, dir, fname, ext);
return strcat(fname, ext);
}
FILE * createFile(char *name)
{
remove(name);
if (f = fopen(name, "ab"))
{
printf("文件创建成功\n");
}
else
{
printf("文件创建失败\n");
}
return f;
}
bool createDir(char *dir)
{
char head[MAX_PATH] = "md ";
return system(strcat(head, dir));
}
};
(2)TCP客户端
#include <stdio.h>
#include <WINSOCK2.H>
#include <STDIO.H>
#include"FileHelper.h"
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2,2);//版本号
WSADATA data; //用来保存WSAStartup调用后返回的windows Sockets数据
FileHelper fh;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.110");
while (true)
{
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
FILE *f=fh.selectfile();
char sendData[BUFSIZ];
char recData[BUFSIZ];
char over[BUFSIZ] = "Finnal";
char * name = fh.getFileName();
strcpy(sendData, name);
printf("%s\n", sendData);
int nCount;
long long sum = 0;
send(sclient, sendData, strlen(sendData)+1, 0);
int ret = recv(sclient, recData, BUFSIZ, 0);
printf(recData);
while ((nCount=fread(sendData,1,BUFSIZ,f))>0)
{
printf("%db\n",sum+=nCount);
send(sclient, sendData, nCount, 0);
int ret = recv(sclient, recData, BUFSIZ, 0);
if (ret >0)
{
//recData[ret] = 0x00;
printf(recData);
}
else
{
printf("与服务器失去连接");
break;
}
}
send(sclient, over, BUFSIZ, 0);
ret = recv(sclient, recData, BUFSIZ, 0);
if (ret>0&&strcmp(recData,over)==0)
{
printf("传输成功!");
}
fclose(f);
closesocket(sclient);
}
WSACleanup();
return 0;
}
(3)TCP服务端
#include <stdio.h>
#include <stdio.h>
#include <winsock2.h>
#include "FileHelper.h"
#pragma comment(lib,"ws2_32.lib")
#include<time.h>
int main(int argc, char* argv[])
{
long i = 10000000L;
clock_t start, finish;
double duration;
//初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
FileHelper fh;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if (listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[BUFSIZ];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
if (fh.createDir(inet_ntoa(remoteAddr.sin_addr)))
printf("文件夹创建成功!");
int ret = 0;
long long count = 0;
char sendData[BUFSIZ] = "你好,TCP客户端!\n";
ret = recv(sClient, revData, BUFSIZ, 0);
char fromname[BUFSIZ] = {};
strcpy(fromname, revData);
char mid[3] = "\\";
char finame[MAX_PATH] = {};
char over[BUFSIZ] = "Finnal";
strcat(finame, inet_ntoa(remoteAddr.sin_addr));
printf(finame);
strcat(finame, mid);
strcat(finame, revData);
//printf(finame);
FILE *f = fh.createFile(finame);
send(sClient, sendData, BUFSIZ, 0);
printf( "Time to do %ld empty loops is ", i );
start = clock();
while ((ret = recv(sClient, revData, BUFSIZ, 0)) > 0,i--)
{
//printf("%d\n", ret);
printf("%db\n", count += ret);
if (strcmp(revData,over)==0)
{
printf("文件%s传输成功\n", fromname);
break;
send(sClient, over, BUFSIZ, 0);
}
fwrite(revData, 1, ret, f);
send(sClient, sendData, BUFSIZ, 0);
}
fclose(f);
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "%f seconds/n", duration );
if (strcmp(revData, over) != 0)
{
printf("IP:%s发来的%s传输过程中失去连接\n", inet_ntoa(remoteAddr.sin_addr),fromname);
remove(finame);
}
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
#Windows下Socket编程实现基于UDP协议的⼤⽂件传输
1、功能描述
在客户端,⽤户选择本地某个⽂件,发送到服务端。如果客户端⽆法连接到服务端,客户端应该给出提⽰。
在服务端,接受客户端发送的⽂件,并保存。如果在传输过程中服务端检测到客户端断开连接,则服务器端应该删除未完整传送的⽂件,并给出提⽰。
2、UDP协议流程图
3、程序代码
(1)UDP服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
//#define PORT 8082
#define PORT 1029 //需要更改你可用的端口
#define SERVER_IP "172.30.159.53"
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
#pragma comment(lib, "WS2_32")
int main()
{
// 初始化socket dll
WSADATA wsaData;
WORD socketVersion = MAKEWORD(2, 0);
if(WSAStartup(socketVersion, &wsaData) != 0)
{
printf("Init socket dll error!");
exit(1);
}
while(1){
//创建socket
SOCKET c_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == c_Socket)
{
printf("Create Socket Error!");
system("pause");
exit(1);
}
//指定服务端的地址
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
server_addr.sin_port = htons(PORT);
if (SOCKET_ERROR == connect(c_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
{
printf("Can Not Connect To Client IP!\n");
system("pause");
exit(1);
}
//输入文件名
char file_name[FILE_NAME_MAX_SIZE+1];
memset(file_name, 0, FILE_NAME_MAX_SIZE+1);
printf("Please Input File Name On Client: ");
scanf("%s", &file_name);
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE);
strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE ? BUFFER_SIZE:strlen(file_name));
//向服务器发送文件名
if(send(c_Socket, buffer, BUFFER_SIZE, 0) < 0)
{
printf("Send File Name Failed\n");
system("pause");
exit(1);
}
//打开文件,准备写入
FILE * fp = fopen(file_name, "wb"); //windows下是"wb",表示打开一个只写的二进制文件
if(NULL == fp)
{
printf("File: %s Can Not Open To Write\n", file_name);
system("pause");
exit(1);
}
else
{
memset(buffer, 0, BUFFER_SIZE);
int length = 0;
while ((length = recv(c_Socket, buffer, BUFFER_SIZE, 0)) > 0)
{
if (fwrite(buffer, sizeof(char), length, fp) < length)
{
printf("File: %s Write Failed\n", file_name);
break;
}
memset(buffer, 0, BUFFER_SIZE);
}
printf("Receive File: %s From Client Successful!\n", file_name);
}
fclose(fp);
closesocket(c_Socket);
}
//释放winsock库
WSACleanup();
system("pause");
return 0;
}
(2)UDP客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
//#define PORT 8082
#define PORT 1029 //需要更改你可用的端口
#define SERVER_IP "172.30.159.53"
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
#pragma comment(lib, "WS2_32")
int main()
{
// 声明并初始化一个服务端(本地)的地址结构
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 初始化socket dll
WSADATA wsaData;
WORD socketVersion = MAKEWORD(2, 0);
if(WSAStartup(socketVersion, &wsaData) != 0)
{
printf("Init socket dll error!");
exit(1);
}
// 创建socket
SOCKET m_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == m_Socket)
{
printf("Create Socket Error!");
exit(1);
}
//绑定socket和服务端(本地)地址
if (SOCKET_ERROR == bind(m_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
{
printf("Server Bind Failed: %d", WSAGetLastError());
exit(1);
}
//监听
if (SOCKET_ERROR == listen(m_Socket, 10))
{
printf("Server Listen Failed: %d", WSAGetLastError());
exit(1);
}
while(1)
{
printf("Listening To Server...\n"); //监听服务器需求
sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
SOCKET m_New_Socket = accept(m_Socket, (sockaddr *)&client_addr, &client_addr_len);
if (SOCKET_ERROR == m_New_Socket)
{
printf("Server Accept Failed: %d", WSAGetLastError());
break;
}
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE);
if (recv(m_New_Socket, buffer, BUFFER_SIZE, 0) < 0)
{
printf("Client Receive Data Failed!");
break;
}
char file_name[FILE_NAME_MAX_SIZE+1];
memset(file_name, 0, FILE_NAME_MAX_SIZE+1);
strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE
? FILE_NAME_MAX_SIZE:strlen(buffer));
printf("%s\n", file_name);
FILE * fp = fopen(file_name, "rb"); //windows下是"rb",表示打开一个只读的二进制文件
if (NULL == fp)
{
printf("File: %s Not Found\n", file_name);
}
else
{
memset(buffer, 0, BUFFER_SIZE);
int length = 0;
while ((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
{
if (send(m_New_Socket, buffer, length, 0) < 0)
{
printf("Send File: %s Failed\n", file_name);
break;
}
memset(buffer, 0, BUFFER_SIZE);
}
fclose(fp);
printf("File: %s Transfer Successful!\n", file_name);
}
closesocket(m_New_Socket);
}
closesocket(m_Socket);
//释放winsock库
WSACleanup();
return 0;
}
(二)分析⽂件传输的时延
1、实验数据准备
首先,同过Dos随即生成大小分别为100M,200M,300M,400M,500M,
600M,700M,800M,900M,1G的.txt文档
2、分析在TCP协议下,⽂件传输系统在传输不同⼤⼩的⽂件时的传输时延。
实验结果如下
3、分析在UDP协议下,⽂件传输系统在传输不同⼤⼩的⽂件时的传输时延。
实验结果如下
4、综上得到折现图如
#题目二 WireShark 抓包
为了加强巩固⽹络课程中五层⽹络协议的重点内容,该题⽬主要利⽤WireShark
抓包⼯具完成以下四个⽬标:
一、IP层:利⽤抓包⼯具抓取ARP报⽂,分析ARP协议
1、ARP 协议
ARP根据IP地址获取物理地址的⼀个TCP/IP协议。ARP为IP地址到对应的硬件地址之间提供动态映射。主机发送信息时将包含⽬标IP地址的ARP请求⼴播到⽹络上的所有主机,并接收返回消息,以此确定⽬标的物理地址;收到返回消息后将该IP地址和物理地址存⼊本机ARP缓存中并保留⼀定时间,下次请求时直接查询ARP缓存以节约资源。地址解析协议是建⽴在⽹络中各个主机互相信任的基础上的,⽹络上的主机可以⾃主发送ARP应答消息,其他主机收到应答报⽂时不会检测该报⽂的真实性就会将其记⼊本机ARP缓存。
2、具体实验
本实验使⽤的版本是wireshark3.0.0版,打开软件,选择“捕获”-“选项”。
此时会有海量的数据包,为了便于分析,我们使⽤过滤器输⼊“arp”以筛选出arp
报⽂。
选取的发送和接收的报⽂进⾏分析,arp⼀次请求由⼴播的请求报⽂和单播的应答报⽂组成,所以这⼀组报⽂的源MAC地址和⽬的MAC地址是对调的。可以看⼀下Info的信息很有意思,请求报⽂⼴播说“谁有192.168.1.110的mac地址?告诉192.168.1.1吧!”,响应的报⽂对请求的地址单播说“192.168.1.110的mac地址是68:07:15:71:c5:96”。
下⾯的Address ResolutionProtocol就是arp协议,这个报⽂是请求包,因为⽬标的MAC地址是全0。
具体的字段解释如下,源IP是192.168.1.1,⽬的IP是192.168.1.110,但是不知道具体的⽬的地的mac地址。
以下是响应包的内容,它在响应包⾥⾯把mac地址补全,单播发给了源地址192.168.1.110。
二、传输层:抓取TCP/UDP报⽂,分析TCP的握⼿协议以及与UDP的区别
(一)TCP三次握手
1、TCP 三次握⼿示意图
2、使⽤ WireShark 进⾏抓包
为了能够抓取到 TCP 三次握⼿建⽴连接的数据,我们可以通过 WireShark 在本地主机访问⽹⻚时开始抓取数据。抓取的数据量⽐较⼤,包括了此时段本地主机所有的通信数据包,为了⽅便我们的分析,我们可以使⽤ WireShark 的显⽰过滤规则来过滤掉不需要的数据⽽只显⽰本地主机与⽬标主机之间通信的数据包。过滤规则如下:
ip.src==源IP or ip.dst==⽬的IP
过滤结果如图所⽰:
3、分析三次握手
(1)第⼀次 客户端发送 SYN 报⽂到服务器
(2)第二次 服务器收到客户端的 SYN 报⽂,回复 SYN + ACK 报⽂
(3)第三次 客户端接收到服务器的 SYN + ACK 报⽂后。回复ACK 报⽂
(二)TCP四次挥手
1、TCP四次挥手示意图
由于TCP连接是全双⼯的,因此每个⽅向都必须单独进⾏关闭。这个原则是当⼀⽅完成它的数据发送任务后就能发送⼀个FIN来终⽌这个⽅向的连接。收到⼀个FIN只意味着这⼀⽅向上没有数据流动,⼀个TCP连接在收到⼀个FIN后仍能发送数据。⾸先进⾏关闭的⼀⽅将执⾏主动关闭,⽽另⼀⽅执⾏被动关闭。TCP的连接的拆除需要发送四个包,因此称为四次挥⼿(four-way handshake)。客户端或服务器均可主动发起挥⼿动作,在socket编程中,任何⼀⽅执⾏close()操作即可产⽣挥⼿操作。
(1)客户端A发送⼀个FIN,⽤来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回⼀个ACK,确认序号为收到的序号加1。和SYN⼀样,⼀个FIN将占⽤⼀个序号。
(3)服务器B关闭与客户端A的连接,发送⼀个FIN给客户端A。
(4)客户端A发回ACK报⽂确认,并将确认序号设置为收到序号加1。
2、分析数据包
(1)第⼀次挥⼿ FIN + ACK
(2)第⼆次挥⼿ ACK
(3)第三次挥⼿ FIN + ACK
(4)第四次挥手ACK