基于SOCKET的简单网络程序的设计
实验配置
实验使用两个虚拟机同时挂在主机NAT服务上互相通信来实现,使用系统:Ubuntu22.04desktop和Ubuntu16.04server。
安装两个系统后,查看两个虚拟机的网络地址,查看是否可以互相ping通。


以太网ARP请求测试
使用sftp将老师提供的示例代码上传至192.168.81.51的机器
安装gcc:
sudo apt-get install gcc
编译发送请求的代码:
sudo gcc arp_send_request.c -o arp_send_request
同时编译接收端的代码:
sudo gcc arp_recv_request_and_send_reply.c -o arp_test
两机一个监听一个发送:(51用来发送请求,50用来监听与回复请求)

可以看到192.168.81.51服务器成功获取到了192.168.81.50的MAC地址。
同时可以通过wireshark捕获虚拟机网卡的包来查看到ARP协议记录:

可以看到由于ARP请求包为广播包,所以我们使用主机抓包可以抓取到请求包,但由于应答包为单播包,所以无法抓取到,此时我们可以开启虚拟机上的wireshark,即可抓取到应答包。
或者我们可以测试抓取网关的包,此时网关的包会过主机,所以就可以被抓取到。

网络ping功能测试
编译并运行老师提供的ping命令代码:
gcc ping.c -o ping && sudo ./ping 192.168.81.52
可以看到收到应答报文,并可以在wireshark中抓包抓到响应的请求与响应。

在wireshark中可以看到每个请求包和响应包的编号,并且可以定位到一组发送和请求。

可以看到分组内容和已知PING格式是相同的。
将代码中的数据长修改为60。
int datalen = 60;
重新启动程序,可以看到每个ping的数据长度变为68(60字节的数据长度和8字节的头部)。其中,8字节为固定的时间戳,数据长度从以往的48字节变为52字节。

可以看到目前的ping请求的数据部分没有内容,尝试在其中填充内容,将代码修改为:
gettimeofday((struct timeval *)icmp->icmp_data, NULL);
for (int i = 0; i < 0x34; i++)
{
icmp->icmp_data[i + 8] = i;
}
len = 8+datalen;
icmp->icmp_cksum = 0;
icmp->icmp_cksum = in_cksum((u_short *)icmp, len);
重新编译运行,可以看到数据部分被填充了对应的数:

网络Traceroute功能测试
编译运行老师提供的程序:
ping www.baidu.com
gcc main.c traceroute.c -o traceroute && ./traceroute -I 39.156.70.46
由于VMWare的NAT是一个七层的NAT,NAT转换了数据流的源地址,转成了宿主机的无线网卡的地址,导致无法收到路由器的包,所以路过的所有都无法显示出来,如下图:

将虚拟机网络模式修改为桥接,重新启动虚拟机,可以看到traceroute可以正常工作。

使用wireshark进行抓包,可以看到ttl逐渐从1增加到11,这与我们使用traceroute得到的结果是一致的。

此处分析一下TTL=1的数据包
TTL=1的请求数据包:

可以得到以下信息 .
IP 层信息
IP 版本: IPv4 总长度: 84bytes, 包括 IP 首部和数据的总长度 标识字段: 0x8d88, 数据包的分片标识符, 用于 IP 分片 标志(Flags): 0x40,表示此包未设置分片标志 片偏移(Fragment Offset): 0, 说明这是一个未分片的数据包 IP地址 源IP地址: 192.168.10.23,这是发出Echo Request的桥接虚拟机的 IP 地址 目标地址: 39.156.70.46,这是目标主机的 IP 地址, 即我们希望测试连通性的设备 TTL=1:表示这是请求数据包的初始 TTL 值, 经过第一个路由设备时将被递减为 0, 因此路由器会返回Time-to-live exceeded 响应
ICMP 协议信息
ICMP类型:8,表示这是一个 ICMP Echo Request 数据包。 校验和:0xc92f,用于校验数据包内容的完整性。
总结
从图中下方显示的信息可以清楚地看到: 此数据包是由源地址192.168.10.23发出的Echo Request, 目标是39.156.70.46 数据包的 TTL 为 1, 是tracert的第一个跳数, 尝试探测第一个路由器的地址和路径延迟 IP 层和以太网层的详细信息展示了路由和传输的基本特性, 同时包含数据完整性校验字段
TTL=1的返回数据包:

可以得到以下信息 .
IP 层信息
IP 版本: IPv4 标识字段: 0x0000, 数据包的分片标识符, 表明返回的 ICMP 数据包没有被分片 标志(Flags): 0x0,表示此包未设置分片标志 片偏移(Fragment Offset): 0, 说明这是一个未分片的数据包
IP地址
源IP地址:192.168.10.82:这是第一跳设备的 IP 地址(是虚拟机主机的IP地址) 目标地址:192.168.10.23, 这是发送 ICMP 请求的主机的 IP 地址
ICMP协议信息
ICMP类型: 11(Time-to-live exceeded)表示原始数据包的 TTL 已耗尽, 第一跳设备返回了超时消息 校验和: 0xf4ff, 用于用于验证 IP 首部的完整性 嵌套的原始Echo Request 信息: ICMP 消息的负载部分包含了导致 TTL 超时的原始请求包头部和部分数据
TCP/UDP客户/服务程序测试
切换为NAT模式,在192.168.81.52的机器的80端口编译并开启udp_server程序。
gcc udp_server.c -o udp_server && ./udp_server 192.168.81.52 80
在192.168.81.50的机器编译并开启udp_client程序,访问开启的服务。
gcc udp_client.c -o udp_client && ./udp_client 192.168.81.52 80
输入数据,即可看到两台机器的访问结果:

在192.168.81.52的机器的8081端口编译并开启tcp_server程序。
gcc tcp_server.c -o tcp_server && ./tcp_server 192.168.81.52 8081
在192.168.81.50的机器编译并开启tcp_client程序,访问开启的服务。
gcc tcp_client.c -o tcp_client && ./tcp_client 192.168.81.52 8081
输入数据,即可看到两台机器的访问结果:

通过wireshark可以看到完整的tcp通信过程:

经过测试可以看到二者区别:当udp的client服务目标未开启时,是可以正常开启功能的,只是发出的数据包没有回复。而当tcp的服务器未开启时是无法成功握手的,也就无法启动tcp的client服务。
TCP聊天应用程序测试
选用与主机在同网段下的一个树莓派作为服务器运行服务器程序,使用两个虚拟机链接服务器进行聊天测试。


可以看到聊天室运行正常,两台虚拟机可以作为从机登录到网络服务器上进行通信。
测试网络应用平台搭建
同样在树莓派上进行环境搭建,首先安装apache2
apt-get install apache2
systemctl enable apache2
systemctl status apache2

可以看到apache2运行正常,使用虚拟机访问设备,可以看到apache主页。

将老师提供的网页复制到www目录下

登录mysql创建对应的数据库和表,为应用创建一个新用户并且赋予其权限。

修改conn.php中的配置,输入数据库的新用户账号密码。

然后注册账号,登录账号。



问题解决
1.
ubuntu系统使用NAT无法上网的问题,首先需要确保主机上面的VMWare的相关服务处于启动状态:
win+R启动service.msc找到VMWare NAT Service和VMWare DHCP Service两个服务,确保他们处于正在运行状态,如果不是就手动重启。

接下来来到ubuntu系统上,输入nmcli device status查看当前网络状态,发现ens33网卡处于未启用状态。使用sudo ip link set ens33 up将网卡启动。

再键入ifconfig可以看到ens33网卡已经出现,但是还没有获取到IP地址,此时再输入sudo dhclient ens33重新通过dhcp服务器获取IP地址,再输入ifconfig可以看到已经没有问题了。

2.
编译老师提供的ping命令代码:
gcc ping.c -o ping
可能会出现警告:
ping.c: In function ‘err_doit’:
ping.c:279:38: warning: format not a string literal and no format arguments [-Wformat-security]
279 | syslog(level, (char*)buf);
| ^~~
是因为syslog函数需要传参是需要格式化字符串。
需要进入ping.c文件中找到err_doit函数,并将其中对应的代码修改为:
if (daemon_proc)
{
syslog(level, "%s", buf);
}
再次编译便不会报警告。
3.
编译老师提供的traceroute代码发现报错:
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0x0): multiple definition of `troptions'; /tmp/ccps0vjl.o:(.bss+0x0): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0x10): multiple definition of `sport'; /tmp/ccps0vjl.o:(.bss+0x10): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0x12): multiple definition of `dport'; /tmp/ccps0vjl.o:(.data+0x0): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0x20): multiple definition of `destaddr'; /tmp/ccps0vjl.o:(.bss+0x20): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0x30): multiple definition of `hostname'; /tmp/ccps0vjl.o:(.bss+0x30): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0x40): multiple definition of `sendbuf'; /tmp/ccps0vjl.o:(.bss+0x40): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0x620): multiple definition of `recvbuf'; /tmp/ccps0vjl.o:(.bss+0x620): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0xbfc): multiple definition of `datalen'; /tmp/ccps0vjl.o:(.bss+0xbfc): first defined here
/usr/bin/ld: /tmp/ccnd7Qn4.o:(.bss+0xc00): multiple definition of `alarm_flag'; /tmp/ccps0vjl.o:(.bss+0xc00): first defined here
是由于全局变量定义在了头文件中,需要进行修改:
将traceroute.h中的如下代码改到traceroute.c中:
struct troptions troptions;
uint16_t sport, dport; //源端端口和目的端端口
struct sockaddr_in destaddr; //目的端套接字地址结构
char *hostname; //目的端主机名
char sendbuf[BUFSIZE], recvbuf[BUFSIZE]; //发送缓冲区和接收缓冲区
int datalen;
int alarm_flag; //闹钟标记
并在main.c中加入如下代码:
extern struct troptions troptions;
extern uint16_t sport, dport; //源端端口和目的端端口
extern struct sockaddr_in destaddr; //目的端套接字地址结构
extern char *hostname; //目的端主机名
extern char sendbuf[BUFSIZE], recvbuf[BUFSIZE]; //发送缓冲区和接收缓冲区
extern int datalen;
extern int alarm_flag; //闹钟标记
dport = 32768 + 666;
4.
web应用在打开的时候出现乱码现象

是因为页面源码使用的是utf-8编码,使用nodepad++转码为ansi编码之后即可。
