一、 实验目的
- 了解Winsock网络编程的原理
- 了解C/S模式的程序编写
- 掌握TCP socket网络程序的编写
- 掌握UCP socket网络程序的编写
- 能够解决实验过程中的出现的问题
二、 实验环境
- 运行windows 7或windows7以上的PC
- 每台PC机具有一块网卡,通过双绞线或无线与网络相连
- 连网的PC机可以互相访问
三、 实验步骤
- 在连网环境下,确定哪台设备充当服务器,哪台设备充当客户机,记录客户机和服务器的IP地址。
- 利用ping命令,确保服务器主机和客户机主机能够互相访问。
- 基于TCP的套接字编程,客户端程序TCPClient.py和服务器端程序
TCPServer.py,使客户端和服务器端能够双向通信。 - 基于UDP的套接字编程,客户端程序UDPClient.py和服务器端程序
UDPServer.py,使客户端和服务器端能够双向通信。
四、 实验分析
1.填写参与网络编程两端的客户机IP地址和服务器IP地址和端口号
客户机IP地址 | 192.168.61.159 |
服务器IP地址 | 192.168.61.244 |
指定服务器端守候的端口号 | 25565 |
2.运行ping命令,确保两台设备能互相访问。
服务端(192.168.61.244):
客户端(192.168.61.159):
3.搞清楚基于TCP通信的框架流程,调用相应API函数,实现基于TCP的客户端和服务器端的scoket程序。要求逐步实现如下功能:客户端发送一行字符串数据给服务器端,服务器返回大写字符串。要求服务器端打印客户端发送的数据,打印客户端发起TCP连接的IP地址和端口号;附服务器端截图。
服务端运行结果:
客户端运行结果:
服务端代码(稍作修改):
4.搞清楚基于UDP通信的框架流程,完成客户端发送一行字符串数据给服务器端,服务器返回大写字符串,并在服务器端打印客户端的IP和端口号。附服务器端截图。
服务端运行结果:
客户端运行结果:
服务端代码(稍作修改):
5.在完成上述任务之后,修改源代码,实现基于TCP或UDP更复杂的网络通信功能。附最终客户端和服务器端运行结果的截图。
(1)通过TCP socket发送和接收图片文件(可自定义文件路径)
服务端:
客户端:
客户端文件夹内容(Image.jpg为发送的图片文件):
服务端文件夹内容(received_Image.jpg为接收的图片文件):
6.规范程序代码,提高程序的可读性,添加必要的注释信息。附上规范的最终客户端和服务器的代码。
服务端代码:
import socket
import os
import sys
import struct
# 初始化TCP监听
try:
# 创建一个IPv4 TCP套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('192.168.76.159', 25565))
# 开始监听连接,最大连接数设置为10
s.listen(10)
# 处理并抛出错误信息
except socket.error as msg:
print(msg)
sys.exit(1)
# 无限循环监听客户端连接
while True:
print('等待客户端连接……………………')
sock, addr = s.accept()
print('接收客户端连接: {0}'.format(addr)) # 查看发送端的ip和端口
# 针对每个连接进行无限循环处理
while True:
# 计算文件名和文件大小结构体的字节大小
fileinfo_size = struct.calcsize('128sq')
# 接收文件名和文件大小信息
buf = sock.recv(fileinfo_size) # 接收图片名
if buf:
# 解包接收到的数据,得到文件名和文件大小
filename, filesize = struct.unpack('128sq', buf)
# 移除文件名末尾的空格和空字符('\x00')
fn = filename.decode().strip('\x00')
# 在服务器端构建新的文件名
new_filename = os.path.join(
'./',
'received_' + fn
)
# 初始化接收到的数据大小
recvd_size = 0
# 打开文件准备写入数据(以二进制方式写入)
fp = open(new_filename, 'wb')
# 循环接收文件数据,直到接收完整个文件
while not recvd_size == filesize:
# 如果剩余数据大于1024字节,则一次接收1024字节
if filesize - recvd_size > 1024:
data = sock.recv(1024)
recvd_size += len(data)
# 如果剩余数据小于或等于1024字节,则接收剩余数据
else:
data = sock.recv(filesize - recvd_size)
recvd_size = filesize
# 将接收到的数据写入文件
fp.write(data)
print("新文件写入完毕,文件目录: {0}".format(new_filename))
fp.close()
sock.close()
print("TCP连接关闭")
# 跳出内部循环,等待下一个客户端连接
break
客户端代码:
import socket
import os
import sys
import struct
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 尝试TCP连接
s.connect(('192.168.76.159', 25565))
except socket.error as msg: # 捕获socket异常
print(msg)
print(sys.exit(1))
filepath = input('请输入文件名:') # 输入图片的目录
fhead = struct.pack( # 将数据打包为二进制字符串
b'128sq', # 设置长度为固定值128bytes
bytes(
os.path.basename(filepath), # 得到的文件名字符串编码为UTF-8格式的字节串
encoding='utf-8'
),
os.stat(filepath).st_size
)
s.send(fhead) # 发送文件
fp = open(filepath, 'rb') # 通过bytes的形式打开图片文件
while True:
data = fp.read(1024) # 读入图片数据,每次读入1024个bytes
if not data:
print('文件:{0} 已成功发送'.format(filepath))
break
s.send(data) # 以二进制格式发送图片数据
s.close()
五、 实验总结
1.基于TCP的套接字编程如果先运行客户端程序,程序会有什么现象,为什么?
由于目标计算机积极拒绝,无法连接。
此时的服务器端接收不到客户端的请求。
因为TCP是面向连接的传输协议,所以在TCP传输之前会主动建立连接,若无法建立连接,则无后续的传输过程。这个过程要求TCP server首先主动开始监听(server拥有固定的IP地址和端口号供client连接),若client首先运行,则无法找到正在监听端口的TCP server从而连接失败。
2.基于UDP的套接字编程如果先运行客户端程序,程序会有什么现象,为什么?
远程主机强迫关闭了一个现有的连接。
如果客户端过早地启动并发送数据报,但服务器还没有准备好接收数据报,那么这个数据报可能会丢失或者被丢弃。
由于UDP是不可靠的传输协议,因此数据包可能在传输过程中直接丢失并报错。
3.基于TCP和UDP套接字编程流程有何不同?
TCP是面向连接的传输层协议,因此在TCP编程中需要有连接的过程,具体如下
客户端:
clientSocket.connect((serverName,serverPort))
服务端:
connectionSocket, addr = serverSocket.accept()
print('客户端:'+str(addr[0])+'端口号:'+str(addr[1])+'连接成功')
而UDP由于不面向连接,因此省去了上述步骤,使链接更简洁,时间开销小。
4.实验过程中都出现了哪些问题,你都是如何解决的?
(1)TCP先启动客户端造成报错:由于目标计算机积极拒绝,无法连接。
解决方法:先启动服务端监听端口。
(2)发送图片或加密数据时,格式不统一的问题
解决方法:
统一使用encode()将图片或加密数据转化为二进制数据bytes()在另一方再使用decode()将二进制解码为utf-8格式。