当前位置:   article > 正文

【计算机网络实验二】网络基础编程实验 (Python-Socket)_python 网络通信实验

python 网络通信实验

1、前言

实验所用语言为Python,参考前辈们的比较多,在此仅作记录,文章中出现的图片还请不要随便使用,本人编程能力较弱,代码仅供参考

参考/学习:
Python Socket编程
Python threading编程
参考1
参考2

2、实验目的

通过本实验,学习采用Socket(套接字)设计简单的网络数据收发程序,理解应用数据包是如何通过传输层进行传送的。

3、实验内容

Socket(套接字)是一种抽象层,应用程序通过它来发送和接收数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。一个socket允许应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。一台计算机上的应用程序向socket写入的信息能够被另一台计算机上的另一个应用程序读取,反之亦然。
不同类型的socket与不同类型的底层协议族以及同一协议族中的不同协议栈相关联。现在TCP/IP协议族中的主要socket类型为流套接字(sockets sockets)和数据报套接字(datagram sockets)。流套接字将TCP作为其端对端协议(底层使用IP协议),提供了一个可信赖的字节流服务。一个TCP/IP流套接字代表了TCP连接的一端。数据报套接字使用UDP协议(底层同样使用IP协议),提供了一个"尽力而为"(best-effort)的数据报服务,应用程序可以通过它发送最长65500字节的个人信息。一个TCP/IP套接字由一个互联网地址,一个端对端协议(TCP或UDP协议)以及一个端口号唯一确定。

3.1采用TCP进行数据发送的简单程序

在这里插入图片描述

服务端

# === TCP 服务端程序 ===

# 导入socket 库
from socket import *

# 主机地址为0.0.0.0, 表示绑定本机所有网络接口ip地址
# 主机地址127.0.0.1表示主机环回地址
# 等待客户端来连接
IP = '127.0.0.1'
# 端口号
PORT = 50000
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512

# 实例化一个socket对象
# 参数 AF_INET表示该socket网络层使用IP协议
# 参数SOCK_STREAM 表示socket传输层使用tcp协议
listenSocket = socket(AF_INET, SOCK_STREAM)

# socket绑定地址和端口
listenSocket.bind((IP, PORT))

# 使用socket处于监听状态, 等待客户端的连接请求
# 参数5表示,最多接收多少个等待连接的客户端
listenSocket.listen(5)
print(f"服务端启动成功, 在{PORT}端口等待客户端连接...")

# 当没有客户端连接时,服务端程序将会在此处阻塞,等待客户端连接
dataSocket, addr = listenSocket.accept()
# TCP连接建立之后accept()方法将会返回一个新的socket(用来收发数据)和一个客户端地址
print('接收一个客户端连接:', addr)

while True:
    # 尝试读取对方发送的消息
    # BUFLEN 指定从接收缓冲里最多读取多少字节
    recved = dataSocket.recv(BUFLEN)

    # 如果返回空bytes, 表示对方关闭了连接
    # 退出循环,结束消息收发
    if not recved:
        break

    # 读取的字节数据是bytes类型, 需要解码为字符串
    info = recved.decode()
    print(f'收到对方信息: {info}')

    # 发送的数据类型必须是bytes, 所以要编码
    dataSocket.send(f'服务端接收到了信息 {info}'.encode())

# 服务端也调用close()关闭socket
dataSocket.close()
listenSocket.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

客户端

# === TCP客户端程序 ===
from socket import *

IP = '127.0.0.1'
SERVER_PORT = 60000
BUFLEN = 512

# 实例化一个socket对象,指明协议
dataSocket = socket(AF_INET, SOCK_STREAM)

# 连接服务端socket
dataSocket.connect((IP, SERVER_PORT))

while True:
    # 从终端读入用户输入的字符串
    toSend = input('>>')
    if toSend == 'exit':
        break
    # 发送消息,也要编码为bytes
    dataSocket.send(toSend.encode())

    # 等待接收服务端的消息
    recved = dataSocket.recv(BUFLEN)
    # 如果返回空bytes, 表示对方关闭了连接
    if not recved:
        break
    # 打印读取的信息
    print(recved.decode())

dataSocket.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

实验结果

设置服务端在收到客户端消息之后将收到的消息再发送回去,验证二者收发能力正常。
在这里插入图片描述

3.2采用UDP进行数据发送的简单程序

在这里插入图片描述

服务端

# === UDP服务端程序 ===

# 导入socket 库
from socket import *

# 主机地址为0.0.0.0, 表示绑定本机所有网络接口ip地址
# 主机地址127.0.0.1表示主机环回地址
# 等待客户端来连接
IP = '127.0.0.1'
# 端口号
PORT = 50000
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512

# 实例化一个socket对象
# 参数 AF_INET表示该socket网络层使用IP协议
# 参数SOCK_DGRAM 表示socket传输层使用udp协议
listenSocket = socket(AF_INET, SOCK_DGRAM)

# socket绑定地址和端口
listenSocket.bind((IP, PORT))
print(f"服务端启动成功, 在{PORT}端口等待客户端连接...")

while True:
    # 接收UDP套接字的数据,返回接收到的数据和套接字地址
    recved, addr = listenSocket.recvfrom(BUFLEN)

    # 如果返回空bytes, 表示对方关闭了连接
    # 退出循环,结束消息收发
    if not recved:
        break

    # 读取的字节数据是bytes类型, 需要解码为字符串
    info = recved.decode()
    print(f'收到对方信息: {info}')

    # 发送的数据类型必须是bytes, 所以要编码
    listenSocket.sendto(f'服务端接收到了信息 {info}'.encode(), addr)

# 服务端也调用close()关闭socket
listenSocket.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

客户端

# === UDP客户端套接字 ===

from socket import *

IP = '127.0.0.1'
SERVER_PORT = 50000
BUFLEN = 512

# 实例化一个socket对象,指明UDP协议
dataSocket = socket(AF_INET, SOCK_DGRAM)

while True:
    # 从终端读入用户输入的字符串
    toSend = input('>>')
    if toSend == 'exit':
        break
    # 发送UDP数据, 将数据发送到套接字
    dataSocket.sendto(toSend.encode(), (IP, SERVER_PORT))

    # 接收服务端的消息
    recved, addr = dataSocket.recvfrom(BUFLEN)
    # 如果返回空bytes, 表示对方关闭了连接
    if not recved:
        break
    # 打印读取的信息
    print(recved.decode())

dataSocket.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

实验结果

设置服务端在收到客户端消息之后将收到的消息再发送回去,验证二者收发能力正常。
在这里插入图片描述

3.3多线程\线程池对比

当一个客户端向一个已经被其他客户端占用的服务器发送连接请求时,虽然其在连接建立后即可向服务器端发送数据,服务器端在处理完已有客户端的请求前,却不会对新的客户端作出响应。
并行服务器:可以单独处理没一个连接,且不会产生干扰。并行服务器分为两种:一客户一线程和线程池。
每个新线程都会消耗系统资源:创建一个线程将占用CPU周期,而且每个线程都自己的数据结构(如,栈)也要消耗系统内存。另外,当一个线程阻塞(block)时,JVM将保存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状态。随着线程数的增加,线程将消耗越来越多的系统资源。这将最终导致系统花费更多的时间来处理上下文转换和线程管理,更少的时间来对连接进行服务。那种情况下,加入一个额外的线程实际上可能增加客户端总服务时间。
我们可以通过限制总线程数并重复使用线程来避免这个问题。与为每个连接创建一个新的线程不同,服务器在启动时创建一个由固定数量线程组成的线程池(thread pool)。当一个新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理。当该线程处理完这个客户端后,又返回线程池,并为下一次请求处理做好准备。如果连接请求到达服务器时,线程池中的所有线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。
在这里插入图片描述

多线程服务端

# === 多线程 TCP 服务端 ===

# 导入库
from socket import *
import threading

# 主机地址为0.0.0.0, 表示绑定本机所有网络接口ip地址
# 主机地址127.0.0.1表示主机环回地址
# 等待客户端来连接
IP = '127.0.0.1'
# 端口号
PORT = 50000
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512

# 实例化一个socket对象
# 参数 AF_INET表示该socket网络层使用IP协议
# 参数SOCK_STREAM 表示socket传输层使用tcp协议
listenSocket = socket(AF_INET, SOCK_STREAM)

# socket绑定地址和端口
listenSocket.bind((IP, PORT))

# 使用socket处于监听状态, 等待客户端的连接请求
# 参数5表示,最多接收多少个等待连接的客户端
listenSocket.listen(5)
print(f"服务端启动成功, 在{PORT}端口等待客户端连接...")


# 接收到连接之后进行通信的方法
def TCP_Link(dataSocket, addr):
    while True:
        # 尝试读取对方发送的消息
        # BUFLEN 指定从接收缓冲里最多读取多少字节
        recved = dataSocket.recv(BUFLEN)

        # 如果返回空bytes, 表示对方关闭了连接
        # 退出循环,结束消息收发
        if not recved:
            break

        # 读取的字节数据是bytes类型, 需要解码为字符串
        info = recved.decode()
        print(f'收到来自{addr}的信息: {info}')

        # 发送的数据类型必须是bytes, 所以要编码
        dataSocket.send(f'服务端接收到了信息 {info}'.encode())

        # 服务端也调用close()关闭socket
    print(f'客户端{addr}断开连接')
    dataSocket.close()


# 服务端socket 持续接收客户端连接
while True:
    # 当没有客户端连接时,服务端程序将会在此处阻塞,等待客户端连接
    dataSocket, addr = listenSocket.accept()
    # TCP连接建立之后accept()方法将会返回一个新的socket(用来收发数据)和一个客户端地址
    print('接收一个客户端连接:', addr)

    # 接收到一个连接之后, 为该连接建立新的线程
    addThread = threading.Thread(target=TCP_Link, args=(dataSocket, addr))
    # 运行该线程
    addThread.start()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

[多线程的客户端与使用TCP的客户端相同,可以复制多个程序与服务端连接模拟多线程的情况]

实验结果

这里以服务端同时接收两个客户端连接为例,验证服务端具备多线程处理客户端发送信息的能力。
在这里插入图片描述

线程池服务端

# === 线程池 TCP 服务端 ===

# 导入库
from socket import *
from concurrent.futures import ThreadPoolExecutor

# 主机地址为0.0.0.0, 表示绑定本机所有网络接口ip地址
# 主机地址127.0.0.1表示主机环回地址
# 等待客户端来连接
IP = '127.0.0.1'
# 端口号
PORT = 60000
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512

# 实例化一个socket对象
# 参数 AF_INET表示该socket网络层使用IP协议
# 参数SOCK_STREAM 表示socket传输层使用tcp协议
listenSocket = socket(AF_INET, SOCK_STREAM)

# socket绑定地址和端口
listenSocket.bind((IP, PORT))

# 使用socket处于监听状态, 等待客户端的连接请求
# 参数5表示,最多接收多少个等待连接的客户端
listenSocket.listen(5)
print(f"服务端启动成功, 在{PORT}端口等待客户端连接...")


# 接收到连接之后进行通信的方法
def TCP_Link(dataSocket, addr):
    while True:
        # 尝试读取对方发送的消息
        # BUFLEN 指定从接收缓冲里最多读取多少字节
        recved = dataSocket.recv(BUFLEN)

        # 如果返回空bytes, 表示对方关闭了连接
        # 退出循环,结束消息收发
        if not recved:
            break

        # 读取的字节数据是bytes类型, 需要解码为字符串
        info = recved.decode()
        print(f'收到来自{addr}的信息: {info}')

        # 发送的数据类型必须是bytes, 所以要编码
        dataSocket.send(f'服务端接收到了信息 {info}'.encode())

        # 服务端也调用close()关闭socket
    print(f'客户端{addr}断开连接')
    dataSocket.close()


# 建立一个线程池
thread_pool = ThreadPoolExecutor(max_workers=5)

# 服务端socket 持续接收连接
while True:
    # 当没有客户端连接时,服务端程序将会在此处阻塞,等待客户端连接
    dataSocket, addr = listenSocket.accept()
    # TCP连接建立之后accept()方法将会返回一个新的socket(用来收发数据)和一个客户端地址
    print('接收一个客户端连接:', addr)

    # 接收到一个连接之后, 为该连接建立新的线程
    thread_pool.submit(TCP_Link, dataSocket, addr)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

[线程池的客户端与使用TCP的客户端相同,可以复制多个程序与服务端连接模拟多线程的情况]

实验结果

这里以服务端同时接收两个客户端连接为例,验证服务端具备多线程处理客户端发送信息的能力。
在这里插入图片描述

3.4写一个简单的chat程序,并能互传文件

在这里插入图片描述

服务端

# === Chat程序编写 TCP 服务端 ===

# 导入库
from socket import *
import threading
import os
import json

# 主机地址为0.0.0.0, 表示绑定本机所有网络接口ip地址
# 主机地址127.0.0.1表示主机环回地址
# 等待客户端来连接
IP = '127.0.0.1'
# 端口号
PORT = 40000
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512
# 上传文件的源地址 /serverfile.pdf
UPLOAD_PATH = r"ServerUPLOAD"
# 文件下载地址
DOWNLOAD_PATH = r"ServerDOWNLOAD"


# 实例化一个socket对象
# 参数 AF_INET表示该socket网络层使用IP协议
# 参数SOCK_STREAM 表示socket传输层使用tcp协议
listenSocket = socket(AF_INET, SOCK_STREAM)

# socket绑定地址和端口
listenSocket.bind((IP, PORT))

# 使用socket处于监听状态, 等待客户端的连接请求
# 参数5表示,最多接收多少个等待连接的客户端
listenSocket.listen(5)
print(f"服务端启动成功, 在{PORT}端口等待客户端连接...")

# 创建一个列表存放服务器所接收到的连接
LinkList = {}

# 定义服务端连接方法
def Chat(dataSocket, addr):
    global LinkList
    while True:
        # 尝试读取对方发送的消息
        # BUFLEN 指定从接收缓冲里最多读取多少字节
        recved = dataSocket.recv(BUFLEN).decode()

        # 如果返回空bytes, 表示对方关闭了连接
        # 退出循环,结束消息收发
        if not recved:
            break
        # 读取的字节数据是bytes类型, 需要解码为字符串
        # 客户端聊天
        if recved == "chat":
            print(f'收到来自{addr}的聊天请求')
            recved = dataSocket.recv(BUFLEN).decode()
            print(f'收到来自{addr}的信息: {recved}')
            toSend = input(f"发送给{addr}>>")
            dataSocket.send('chat'.encode())
            dataSocket.send(('收到来自服务器的消息: ' + toSend).encode())
        # 客户端转发聊天请求
        elif recved == "sendto":
            print(f'收到来自{addr}的转发聊天请求')
            recved = dataSocket.recv(BUFLEN).decode()
            target = dataSocket.recv(BUFLEN).decode()
            target = int(target)
            if target in LinkList:
                LinkList[target].send('chat'.encode())
                LinkList[target].send((f'收到来自{addr[1]}的消息: ' + recved).encode())
                print(f'成功将消息{recved}发送给{target}')
        # 客户端向服务端请求文件
        elif recved == "get":
            print(f'收到来自{addr}的文件请求')
            # 获取将要上传文件的名称、大小
            file_name = dataSocket.recv(BUFLEN).decode()
            if os.path.isfile(UPLOAD_PATH+'\\'+file_name):
                file_size = os.path.getsize(UPLOAD_PATH+'\\'+file_name)
                dict = {
                    'filename': file_name,
                    'filesize': file_size
                }
                # 将文件基本信息编码成JSON发送给客户端
                fileinfo = json.dumps(dict)
                dataSocket.send('file'.encode())
                dataSocket.send('0'.encode())
                dataSocket.send(fileinfo.encode('utf-8'))
                with open(UPLOAD_PATH+'\\'+file_name, 'rb') as f:
                    data = f.read()
                    dataSocket.sendall(data)
                print(f'已成功将文件{file_name}上传给{addr}')
            else:
                dataSocket.send('chat'.encode())
                dataSocket.send('请求文件不存在'.encode())
        # 收到来自用户上传的文件
        elif recved == "deliver":
            print(f'收到来自{addr}的上传请求')
            fileinfo = dataSocket.recv(BUFLEN)
            # 将从客户端收到的JSON文件信息解码
            dict = json.loads(fileinfo)
            file_name = dict['filename']
            file_size = dict['filesize']
            print(f'收到来自{addr}的文件:{file_name}, 文件大小:{file_size}')
            recv_size = 0
            recv_file = b''
            f = open(DOWNLOAD_PATH + '\\' + file_name, 'wb')
            while recv_size < file_size:
                if file_size - recv_size > BUFLEN:
                    recv_file = dataSocket.recv(BUFLEN)
                    f.write(recv_file)
                    recv_size += len(recv_file)
                else:
                    recv_file = dataSocket.recv(file_size - recv_size)
                    recv_size += len(recv_file)
                    f.write(recv_file)
            f.close()
            print('文件接收成功!')
        # 收到来自客户端的转发文件请求
        elif recved == "deliverto":
            print(f'收到来自{addr}的转发文件请求')
            target = int(dataSocket.recv(BUFLEN).decode())
            # 接收
            fileinfo = dataSocket.recv(BUFLEN)
            # 将从客户端收到的JSON文件信息解码
            dict = json.loads(fileinfo)
            file_name = dict['filename']
            file_size = dict['filesize']
            print(f'收到来自{addr}的文件:{file_name}, 文件大小:{file_size}')
            recv_size = 0
            recv_file = b''
            f = open(DOWNLOAD_PATH + '\\' + file_name, 'wb')
            while recv_size < file_size:
                if file_size - recv_size > BUFLEN:
                    recv_file = dataSocket.recv(BUFLEN)
                    f.write(recv_file)
                    recv_size += len(recv_file)
                else:
                    recv_file = dataSocket.recv(file_size - recv_size)
                    recv_size += len(recv_file)
                    f.write(recv_file)
            f.close()
            print('文件接收成功!')
            # 转发
            if target in LinkList.keys():
                # 将文件基本信息编码成JSON发送给客户端
                fileinfo = json.dumps(dict)
                LinkList[target].send('file'.encode())
                LinkList[target].send(str(target).encode())
                LinkList[target].send(fileinfo.encode('utf-8'))
                with open(DOWNLOAD_PATH + '\\' +file_name, 'rb') as f:
                    data = f.read()
                    LinkList[target].sendall(data)
                print(f'已成功将文件{file_name}转发给{target}')
                dataSocket.send('chat'.encode())
                dataSocket.send(f'服务器已成功将{file_name}转发给{target}'.encode())
            else:
                dataSocket.send('chat'.encode())
                dataSocket.send('目标用户不存在,文件已由服务器接收'.encode())
        elif recved == "exit":
            print(f'客户端{addr}断开连接')
            dataSocket.close()
            break

# 服务端socket 持续接收客户端连接
while True:
    # 当没有客户端连接时, 服务端程序在此处阻塞, 等待客户端连接
    dataSocket, addr = listenSocket.accept()
    # TCP连接建立之后accept()方法将会返回一个新的socket(用来收发数据)和一个客户端地址
    print('接收一个客户端连接:', addr)

    # 将该连接放入到连接的列表中
    print(addr[1])
    LinkList[addr[1]] = dataSocket
    # 接收到一个连接之后, 为该连接建立新的线程
    addThread = threading.Thread(target=Chat, args=(dataSocket, addr))
    # 运行该线程
    addThread.start()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176

客户端

# === Chat程序编写 TCP 客户端 ===

# 导入库
from socket import *
import os
import json
import threading

IP = '127.0.0.1'
PORT = 40000
BUFLEN = 512
# /client1file.pdf
UPLOAD_PATH = r"Client1UPLOAD"
DOWNLOAD_PATH = r"Client1DOWNLOAD"

# 实例化一个socket对象,指明协议
dataSocket = socket(AF_INET, SOCK_STREAM)

# 连接服务端socket
dataSocket.connect((IP, PORT))
print("已成功与服务端取得连接, 键入chat、sendto、get、deliver、deliverto进行不同功能")

def recvChat():
    while True:
        recved = dataSocket.recv(BUFLEN).decode()
        if recved == 'chat':
            recved = dataSocket.recv(BUFLEN).decode()
            print(f'{recved}')
        elif recved == 'file':
            source = dataSocket.recv(BUFLEN).decode()
            fileinfo = dataSocket.recv(BUFLEN)
            # 将收到的JSON文件信息解码
            dict = json.loads(fileinfo)
            file_name = dict['filename']
            file_size = dict['filesize']
            # 收到来自服务器的文件
            if source == '0':
                print(f'收到来自服务端的文件:{file_name}')
            # 收到来自客户端的文件
            else:
                print(f'收到来自{source}的文件:{file_name}')
            print(f'文件大小:{file_size}')
            recv_size = 0
            recv_file = b''
            f = open(DOWNLOAD_PATH + '\\' + file_name, 'wb')
            while recv_size < file_size:
                if file_size - recv_size > BUFLEN:
                    recv_file = dataSocket.recv(BUFLEN)
                    f.write(recv_file)
                    recv_size += len(recv_file)
                else:
                    recv_file = dataSocket.recv(file_size - recv_size)
                    recv_size += len(recv_file)
                    f.write(recv_file)
            f.close()
            print('文件接收成功!')

recvThreading = threading.Thread(target=recvChat)
recvThreading.start()

while True:
    # 从终端读入用户输入的字符串
    toSend = input()
    if toSend == 'exit':
        break
    # 聊天(与服务端)
    if toSend == 'chat':
        dataSocket.send(toSend.encode())
        toSend = input('输入消息内容:')
        dataSocket.send(toSend.encode())
        print('消息已发送, 等待服务端回应')
    # 聊天(与客户端)
    elif toSend == 'sendto':
        dataSocket.send(toSend.encode())
        toSend = input('输入消息内容:')
        dataSocket.send(toSend.encode())
        toSend = input('输入目标客户端地址:')
        dataSocket.send(toSend.encode())
    # 向服务端请求文件
    elif toSend == 'get':
        dataSocket.send(toSend.encode())
        file_name = input('输入请求文件: ')
        dataSocket.send(file_name.encode())
    # 向服务器上传文件
    elif toSend == 'deliver':
        file_name = input('输入要发送的文件: ')
        if os.path.isfile(UPLOAD_PATH + '\\' + file_name):
            dataSocket.send(toSend.encode())
            # 获取将要上传文件的名称、大小
            file_size = os.path.getsize(UPLOAD_PATH+'\\'+file_name)
            dict = {
                'filename': file_name,
                'filesize': file_size
            }
            # 将文件基本信息编码成JSON发送给客户端
            fileinfo = json.dumps(dict)
            dataSocket.send(fileinfo.encode('utf-8'))
            with open(UPLOAD_PATH+'\\'+file_name, 'rb') as f:
                data = f.read()
                dataSocket.sendall(data)
            print(f'已成功将文件{file_name}上传给服务端')
        else:
            print(f'文件不存在,请检查文件名{file_name}是否正确!')
    # 向指定客户端上传文件
    elif toSend == 'deliverto':
        file_name = input('输入要发送的文件: ')
        if os.path.isfile(UPLOAD_PATH + '\\' + file_name):
            dataSocket.send(toSend.encode())
            toSend = input('输入目标客户端地址: ')
            dataSocket.send(toSend.encode())
            file_size = os.path.getsize(UPLOAD_PATH+'\\'+file_name)
            dict = {
                'filename': file_name,
                'filesize': file_size
            }
            # 将文件基本信息编码成JSON发送给客户端
            fileinfo = json.dumps(dict)
            dataSocket.send(fileinfo.encode('utf-8'))
            with open(UPLOAD_PATH+'\\'+file_name, 'rb') as f:
                data = f.read()
                dataSocket.sendall(data)
            print(f'已成功将文件{file_name}上传给服务端')
        else:
            print(f'文件不存在,请检查文件名{file_name}是否正确!')
dataSocket.close()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126

实验结果

chat功能测试
chat功能实现客户端与服务器发送消息,此功能必须得到服务器的回应
在这里插入图片描述
sendto功能测试
sendto功能实现客户端与客户端之间的通信,这是一种更符合现实应用的一种场景,主要是利用服务器来记录所有连接的客户端,收到客户端的sendto请求时,根据客户端所指定的对象将消息转发给目标用户实现聊天功能
在这里插入图片描述
get功能测试
get功能实现客户端向服务器请求文件,与服务器建立连接的客户端键入get指令之后需要指明请求文件的文件名,服务端如果发现拥有该文件就将其发送给客户端,否则发送相应的消息告诉客户端不拥有该文件
初始的文件拥有情况:
在这里插入图片描述
首先客户端向服务端请求serverfile文件,服务端发现该文件未拥有,于是向客户端发送提示信息,之后客户端再次向服务端请求文件,并指明serverfile.pdf文件,服务端发现拥有该文件于是将其发送给客户端
在这里插入图片描述
执行get之后的文件拥有情况:
在这里插入图片描述
可以发现,客户端1的下载目录下已经拥有了从服务器所请求的文件
deliver功能测试
deliver功能用于实现客户端向服务器上传文件,与服务器建立连接的客户端在键入deliver之后需要指明上传文件的名称,之后将直接上传给服务器
初始的文件拥有情况:
在这里插入图片描述
首先客户端发起上传文件的请求,但此时该请求并不会发送给服务端,之后客户端键入要发送文件的名称,如果指定文件不存在,该请求将被忽略;如果成功找到要上传的文件,则向服务端发送该请求,服务端接收客户端上传的文件
在这里插入图片描述
执行deliver之后的文件拥有情况:
在这里插入图片描述
deliverto功能测试
deliverto功能用于实现客户端与客户端之间的互传文件,与服务器建立连接的客户端之间如果某一用户知道对方的地址时,就可以给对方发送文件,发送方先将文件发送给服务端,服务端再将文件发送给目标用户(如果存在的话)
初始的文件拥有情况:
在这里插入图片描述
客户端键入deliverto命令请求服务器转发文件,但是此时该条请求并不会立即发给服务器,在客户端程序确认确实拥有该文件时,在指定目标用户之后,将文件发送给服务器,服务器会首先接收该文件,之后再判断目标用户是否存在,在确定目标用户存在之后再将文件发送给目标用户完成文件传送
在这里插入图片描述
执行deliverto互传文件之后的文件拥有情况:
在这里插入图片描述

4.实验总结

通过本次实验,对socket编程有了初步的了解,学习了如何使用套接字采用TCP进行数据的收发、用UDP进行数据的收发,socket是应用层与TCP/IP协议中间的抽象层,作为一组接口,其将复杂的TCP/IP协议隐藏在socket接口后面,减轻了程序员编程的负担;同时也对比了多线程、线程池两种不同的多线程通信方案的实现方式和差别,相比于多线程方式,线程池预先创建多个线程,在需要新线程的时候将提前创建的线程直接取出来使用,在效率上是比多线程实现方式更优的,但两种方式都有自己的应用场景;最后利用python语言尝试编写了互传文件的程序,客户端与客户端之间的通信(聊天/传文件)本质上是一种通过服务器转发的方式,正确处理它们之间的关系非常重要,这次也是收获满满呀。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/143938
推荐阅读
相关标签
  

闽ICP备14008679号