HNU-计算机网络-实验2-网络基础编程实验(Python3)

计算机网络 课程基础实验二
网络基础编程实验(Python3)

计科210X 甘晴void 202108010XXX

在这里插入图片描述

一、实验目的

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

二、实验内容

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协议)以及一个端口号唯一确定。

(1)问题与解决

①获取本机ip地址
手动查询
cmd > ipconfig

获取信息如下

C:\Users\y>ipconfig
Windows IP 配置
以太网适配器 以太网 2:媒体状态  . . . . . . . . . . . . : 媒体已断开连接连接特定的 DNS 后缀 . . . . . . . :以太网适配器 VirtualBox Host-Only Network:连接特定的 DNS 后缀 . . . . . . . :IPv4 地址 . . . . . . . . . . . . : 192.168.56.1子网掩码  . . . . . . . . . . . . : 255.255.255.0默认网关. . . . . . . . . . . . . :无线局域网适配器 本地连接* 10:媒体状态  . . . . . . . . . . . . : 媒体已断开连接连接特定的 DNS 后缀 . . . . . . . :无线局域网适配器 本地连接* 11:媒体状态  . . . . . . . . . . . . : 媒体已断开连接连接特定的 DNS 后缀 . . . . . . . :无线局域网适配器 WLAN:连接特定的 DNS 后缀 . . . . . . . :本地链接 IPv6 地址. . . . . . . . : fe80::77e3:b329:3430:3e8d%4IPv4 地址 . . . . . . . . . . . . : 192.168.1.111子网掩码  . . . . . . . . . . . . : 255.255.255.0默认网关. . . . . . . . . . . . . : 192.168.1.1以太网适配器 蓝牙网络连接:媒体状态  . . . . . . . . . . . . : 媒体已断开连接连接特定的 DNS 后缀 . . . . . . . :

重点关注无线局域网适配器WLAN的IPV4地址

192.168.1.111

这个就是本机的IP地址

自动查询

还有一种方法,可以通过python自动实现,具体原理是 创建一个socket连接到一个公共的主机(例如: Google DNS服务器)来获取本机IP地址。代码如下。

import socketdef get_local_ip():try:# 创建一个socket连接到一个公共的主机来获取本机IP地址sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock.connect(("8.8.8.8", 80))   #Google DNS服务器:8.8.8.8 或者 8.8.4.4local_ip = sock.getsockname()[0]sock.close()return local_ipexcept Exception as e:print("获取本机IP地址时发生错误:", str(e))return Noneif __name__ == "__main__":local_ip = get_local_ip()if local_ip:print(f"本机IP地址是: {local_ip}")else:print("无法获取本机IP地址。")
②地端口号以及管理员权限导致的问题

较低的端口号可能

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\UDPserver.py 
Traceback (most recent call last):File "E:\python_files\计网-实验2\UDPserver.py", line 11, in <module>server_socket.bind((server_host, server_port))
PermissionError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。
进程已结束,退出代码1
③防火墙

需要允许通过防火墙的请求

④python报错
E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\TCPserver-threadpool-plus.py 
Traceback (most recent call last):File "E:\python_files\计网-实验2\TCPserver-threadpool-plus.py", line 64, in <module>main()File "E:\python_files\计网-实验2\TCPserver-threadpool-plus.py", line 48, in mainserver.bind((server_host, server_port))
OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。进程已结束,退出代码1

连续停止开始并使用统一端口号可能出现该情况。这是因为此时上一个python的进程还没有完全分离,该端口还被占用。此时需要改用另一个端口号即可。

(2)需要实现的目标

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

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

  • 3 多线程、线程池对比

  • 当一个客户端向一个已经被其他客户端占用的服务器发送连接请求时,虽然其在连接建立后即可向服务器端发送数据,服务器端在处理完已有客户端的请求前,却不会对新的客户端作出响应。

    并行服务器:可以单独处理每一个连接,且不会产生干扰。并行服务器分为两种:一客户一线程和线程池。

    每个新线程都会消耗系统资源:创建一个线程将占用CPU周期,而且每个线程都自己的数据结构(如,栈)也要消耗系统内存。另外,当一个线程阻塞(block)时,JVM将保存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状态。随着线程数的增加,线程将消耗越来越多的系统资源。这将最终导致系统花费更多的时间来处理上下文转换和线程管理,更少的时间来对连接进行服务。那种情况下,加入一个额外的线程实际上可能增加客户端总服务时间。

    我们可以通过限制总线程数并重复使用线程来避免这个问题。与为每个连接创建一个新的线程不同,服务器在启动时创建一个由固定数量线程组成的线程池(thread pool)。当一个新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理。当该线程处理完这个客户端后,又返回线程池,并为下一次请求处理做好准备。如果连接请求到达服务器时,线程池中的所有线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。

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

(3)使用python语言实现

★在实现之前需要注意将所有的ip地址与端口号正确分配

所有python可执行文件及说明陈列如下

  • get_ip.py (获取本机ip)
  • TCPclient.py (TCP客户端)
  • TCPserver.py (TCP服务端-基础)
  • TCPserver-multithreading.py (TCP服务端-多线程-一客户一线程)
  • TCPserver-threadpool.py (TCP服务端-线程池)
  • TCPserver-threadpool-plus.py (TCP服务端-线程池-改进)
  • UDPclient.py (UDP客户端)
  • UDPserver.py (UDP服务端)
  • chatClient.py (chat程序客户端)
  • chatServer.py (chat程序服务端)
2.1 采用TCP进行数据发送的简单程序

TCPserver

import socket# 创建TCP服务器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定服务器地址和端口
server_host = '192.168.1.110'  # 服务器主机
server_port = 15000  # 服务器端口
server_socket.bind((server_host, server_port))# 开始监听客户端连接
server_socket.listen(1)  # 最多允许一个客户端连接print(f"等待客户端连接在 {server_host}:{server_port}...")
client_socket, client_address = server_socket.accept()
print(f"连接来自: {client_address}")while True:# 接收客户端发送的数据data = client_socket.recv(1024)if not data:breakprint(f"接收到的数据: {data.decode('utf-8')}")# 回复客户端response = "服务器已收到您的消息"client_socket.send(response.encode('utf-8'))# 关闭连接
client_socket.close()
server_socket.close()

TCPclient

import socket# 创建TCP客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 服务器地址和端口
server_host = '192.168.1.110'  # 服务器主机
server_port = 15000  # 服务器端口# 连接到服务器
client_socket.connect((server_host, server_port))while True:message = input("请输入要发送的消息: ")if message == 'exit':break# 发送消息到服务器client_socket.send(message.encode('utf-8'))# 接收服务器的响应response = client_socket.recv(1024)print(f"服务器响应: {response.decode('utf-8')}")# 关闭连接
client_socket.close()
2.2采用UDP进行数据发送的简单程序

UDPserver

import socket# 创建UDP服务器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 服务器地址和端口
server_host = '192.168.1.110'  # 监听所有网络接口
server_port = 15001  # 端口号# 绑定服务器地址和端口
server_socket.bind((server_host, server_port))print(f"UDP服务器已启动,等待数据传入在 {server_host}:{server_port}...")while True:# 接收客户端发送的数据data, client_address = server_socket.recvfrom(1024)print(f"接收到的数据: {data.decode('utf-8')} 来自 {client_address}")# 回复客户端response = "服务器已收到您的消息"server_socket.sendto(response.encode('utf-8'), client_address)

UDPclient

import socket# 创建UDP客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 服务器地址和端口
server_host = '192.168.1.110'  #
server_port = 15001  # 与服务器端相同的端口号while True:message = input("请输入要发送的消息: ")if message == 'exit':break# 发送消息到服务器client_socket.sendto(message.encode('utf-8'), (server_host, server_port))# 接收服务器的响应data, server_address = client_socket.recvfrom(1024)print(f"服务器响应: {data.decode('utf-8')} 来自 {server_address}")# 关闭连接
client_socket.close()
2.3多线程、线程池对比
①多线程(一客户一线程)
import socket
import threading# 处理客户端连接的函数
def handle_client(client_socket, client_address):try:while True:data = client_socket.recv(1024)if not data:breakprint(f"来自客户端 {client_address[0]}:{client_address[1]} 的消息: {data.decode('utf-8')}")# 服务器处理数据逻辑(范例置空)# 向客户端发送响应数据response = "服务器已收到您的消息"client_socket.send(response.encode('utf-8'))except Exception as e:print(f"来自客户端 {client_address[0]}:{client_address[1]} 的连接发生异常: {str(e)}")finally:# 关闭客户端连接client_socket.close()def main():server_host = '192.168.1.110'  # 监听所有网络接口server_port = 15007  # 选择一个未被占用的端口号server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((server_host, server_port))server.listen(5)  # 最多允许5个客户端排队等待连接print(f"等待客户端连接在 {server_host}:{server_port}...")while True:client_socket, client_address = server.accept()print(f"客户端已连接: {client_address[0]}:{client_address[1]}")# 创建一个新线程来处理客户端连接client_handler = threading.Thread(target=handle_client, args=(client_socket, client_address))client_handler.start()if __name__ == "__main__":main()
②线程池

该线程池可以实现至多5个客户端的同时连接,但在同时出现第6个客户端时,将不对该客户端进行回应。一旦前5个客户端中有结束连接的,第6个客户端就立刻加入。

import socket
import concurrent.futures# 处理客户端连接的函数
def handle_client(client_socket, client_address):try:while True:data = client_socket.recv(1024)if not data:breakprint(f"客户端 {client_address[0]}:{client_address[1]} 发来数据: {data.decode('utf-8')}")# 服务器处理数据逻辑(范例置空)# 向客户端发送响应数据response = "服务器已收到您的消息"client_socket.send(response.encode('utf-8'))except Exception as e:print(f"发生异常: {str(e)}")finally:# 关闭客户端连接client_socket.close()def main():server_host = '192.168.1.110'  # 服务器主机server_port = 15004  # 服务器端口server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((server_host, server_port))server.listen(5)  # 最多允许5个客户端排队等待连接print(f"等待客户端连接在 {server_host}:{server_port}...")# 创建一个线程池with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:while True:client_socket, client_address = server.accept()print(f"客户端已连接: {client_address[0]}:{client_address[1]}")# 使用线程池处理客户端连接executor.submit(handle_client, client_socket, client_address)if __name__ == "__main__":main()
③线程池改进

增加的功能是,当目前可同时连接的客户端数到达上限的时候,再次连入的客户端由一个新的线程处理,该线程向该客户端发送拒绝告知,并结束连接。由该线程告知所有在此时想要接入的新客户端。

在当前有空余可连接空间的时候,客户端正常连接。

不足在于,由于想公用之前写的TCPclient作为客户端,对其不做改变。有一点无法实现,即被拒绝的客户端无法在有空闲时自动接入连接,因为这需要对客户端的逻辑进行更改。在下面的程序中,客户端在被拒绝后接收到来自服务器的拒绝要求后直接被断开连接,需要增加一个客户端在被服务器主动断开连接之后的处理,才能解决这个问题。这里我就不做讨论了。

import socket
import concurrent.futures
import threading# 最大允许的客户端连接数
MAX_CLIENTS = 5# 存储当前活跃客户端连接的列表
active_clients = []# 线程锁用于保护 active_clients 列表
count_lock = threading.Lock()# 全局计数器用于跟踪已连接的客户端数量
connected_clients = 0# 处理客户端连接的函数
def handle_client(client_socket, client_address):global connected_clientstry:with count_lock:connected_clients += 1#print(f"connected_clients= {connected_clients} ")if connected_clients > MAX_CLIENTS:# 如果连接数超过最大限制,向客户端发送拒绝通知print("服务器连接已满,已拒绝一个新的连接")response = "服务器连接已满,拒绝连接。"client_socket.send(response.encode('utf-8'))returnelse:active_clients.append(client_socket)while True:data = client_socket.recv(1024)if not data:breakprint(f"客户端 {client_address[0]}:{client_address[1]} 发来数据: {data.decode('utf-8')}")# 服务器处理数据逻辑(范例置空)# 向客户端发送响应数据response = "服务器已收到您的消息"client_socket.send(response.encode('utf-8'))except Exception as e:print(f"发生异常: {str(e)}")finally:with count_lock:connected_clients -= 1active_clients.remove(client_socket)client_socket.close()def main():server_host = '192.168.1.110'  # 监听所有网络接口server_port = 15009  # 选择一个未被占用的端口号server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((server_host, server_port))server.listen(MAX_CLIENTS+1)  # 最多允许5个客户端排队等待连接print(f"等待客户端连接在 {server_host}:{server_port}...")# 创建一个线程池with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CLIENTS+1) as executor:while True:client_socket, client_address = server.accept()print(f"客户端已连接: {client_address[0]}:{client_address[1]}")# 使用线程池处理客户端连接executor.submit(handle_client, client_socket, client_address)if __name__ == "__main__":main()
2.4写一个简单的chat程序,并能互传文件。

要求写一个简单的chat程序,能够实现互相通信与文件传输。实现这个需要一个服务端和若干客户端,客户端之间可以通过服务端互相联系。

①程序思想

服务端需要能够接受客户端发送的请求,并且对请求做出判断。这个请求是发送的消息还是请求文件操作。如果是请求文件操作,是要上传文件还是要下载文件,文件如果存在就传输这个文件给客户端下载,如果文件不存在就向用户端发送错误报告。

客户端需要能够处理用户的终端输入,并且根据约定好的解析方式解析出用户的意图,使用与服务端约定好的方式去表示用户的意图,再发送给服务端。从服务端接收反馈,再通过用户能够看懂的方式反馈给客户。

传输文件的时候要对文件进行分段传输,因为文件通常都大于我们一次可以传输的范围。

②实现效果

用户视角可用操作:

  • 输入用户名并加入聊天室
  • 直接输入并回车 #发送普通消息
  • 使用/exit #退出客户端
  • 使用/file upload +文件路径 #上传文件(若文件存在,即上传;若文件不存在,本地报错)
  • 使用/file download +文件名 #下载文件(若文件存在,即下载;若文件不存在,即服务器返回报错)
  • 使用/file list #查看服务器可供下载的文件列表(★这是在开发过程中额外实现的功能)
  • 接收其他任意用户加入聊天室的消息
  • 接收其他任意用户发送的消息
  • 接收其他任意用户上传文件成功的消息
  • 接收其他任意用户退出聊天室的消息

服务器视角可用操作:

  • 监听所有用户发送的普通消息
  • 接收所有用户发送的文件
  • 向请求文件的用户发送文件
  • 显示用户加入以及退出的消息

显示系统时(获取发生事件时的服务器时间与用户时间)

③程序设计
chatServer

主进程:

开启服务端,在地址、端口上等待客户端,

维护一个客户端列表,记录每个客户端的名字

开启永真循环,若当前用户数量未达到上限,接收到用户请求接入时通过线程池分配线程与用户进行连接。

子线程handle_client(client_socket):

在永真循环内接收消息

  • 若消息为"/exit":从客户端列表中删除该用户,调用broadcast函数向其他用户广播该用户离开聊天室的消息
  • 若消息以"FILE:“开头:解析客户端发送的【“FILE:”+”|“+file_name+”|"+str(file_size)】,获得文件名和文件大小,调用receive_file(client_socket, file_name, file_size)函数接收文件
  • 若消息以"DOWN:"开头:解析客户端发送的【“DOWN:” + “|” + file_name】,获得文件名,在服务端储存文件夹内查找该文件是否存在。若不存在,返回报错;若存在,调用send_requested_file(client_socket, file_name, file_path)向客户端发送该文件
  • 若消息为"/file list":调用list_files_in_directory()函数遍历服务端储存文件夹内存在的所有文件名,并使用pickle包编码(因为列表形式的结果无法直接发送)。先向客户端发送【“LIST:”】提示客户端做好准备,然后发送结果
  • 否则:表示普通消息。向所有用户转发该普通消息

函数receive_file(client_socket, file_name, file_size):

  • 接收来自客户端的【“FILE:” + “|” + file_name + “|” + str(file_size)】并分离出文件名和文件大小
  • 分段分次从服务端接收文件并保存到指定路径

函数send_requested_file(client_socket, file_name, file_path):

  • 从文件路径获取文件大小
  • 向客户端发送【“FILE:” + “|” + file_name + “|” + str(file_size)】提醒做好准备
  • 分段分次向客户端发送文件

函数broadcast(message):

  • 使用for循环向列表内所有在线的客户端发送消息

函数list_files_in_directory(directory):

  • 使用for循环遍历文件夹获取存在的文件名
chatClient

主进程:

获取用户名,连接服务器。

之后创建子线程,开启永真循环,充当接收端,负责从服务器接收消息:

  • 若消息为空:表示与服务器断开了连接
  • 若消息以"FILE:"开头:表示文件传递协议,调用download(message)下载文件
  • 若消息为"LIST:":表示文件列表请求,直接处理解序列化并向终端输出服务器可供下载文件列表

子线程send_message():

充当类似于shell的工作,读入用户终端输入的内容,进行解析处理后向服务器发送

  • 若输入为"/exit":向服务器发送退出请求,并退出
  • 若输入为"/file upload "开头:先验证文件是否存在,若存在调用send_file(file_path)发送文件,否则本地报错
  • 若输入为"/file download "开头:调用download_query(file_name)向服务器确认该文件是否存在
  • 若输入为"/file list “:向服务器发送”/file list "
  • 否则:为普通消息,直接向服务器发送

函数send_file(file_path):

  • 通过文件路径获得文件名和文件大小
  • 向服务器发送【“FILE:”+“|”+file_name+“|”+str(file_size)】引导服务端接受文件
  • 分段分次向服务端发送文件

函数download_query(file_name):

  • 向服务器发送【“DOWN:” + “|” + file_name】查询文件是否可以下载

函数download(message):

  • 接收来自服务器的【“FILE:” + “|” + file_name + “|” + str(file_size)】并分离出文件名和文件大小
  • 分段分次从服务端接收文件并保存到指定路径
④技术亮点
Ⅰ 显示时间

使用这个包与这个方式获取时间,一般人对时间的毫秒不感兴趣,因此规范化格式。

import datetime #获取系统时间
current_time = datetime.datetime.now()
time = current_time.strftime("%Y-%m-%d %H:%M:%S")
Ⅱ 显示服务器可供下载文件列表

善用os.path包,这个包提供了关于系统路径的很有用的工具

另外传输时没办法直接传输表格,因此需要使用pidkle包进行序列化与解序列化

import pickle #序列化列表为字节数据#服务端:序列化并发送
file_names_bytes = pickle.dumps(file_names)
client_socket.send(file_names_bytes)#客户端:接收并解序列化
file_names_bytes = client_socket.recv(1024)
file_names = pickle.loads(file_names_bytes)
Ⅲ 客户端采用线程

客户端采用主进程接收,线程发送的模式,收发更加清晰。

⑤程序源码
chatServer
import socket
import threading
import os
import concurrent.futures
import pickle #序列化列表为字节数据
import datetime #获取系统时间current_time = datetime.datetime.now()
time = current_time.strftime("%Y-%m-%d %H:%M:%S")
# 建立服务器并等待客户端
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建服务器套接字
server_host = '192.168.1.110'  # 服务器主机
server_port = 15004 # 服务器端口
server_socket.bind((server_host, server_port))
server_socket.listen(6)
# 用于存储已连接的客户端套接字和用户名的字典
client_sockets = {}
client_usernames = {}
MAX_CONNECTIONS = 5 # 设置最大连接数
executor = concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CONNECTIONS) # 创建线程池
print(f"{time} server>>等待客户端连接在 {server_host}:{server_port}...")# 处理客户端消息的函数
def handle_client(client_socket):base_path = "E:\python_files\计网-实验2\服务端保存文件"while True:try:# 接收消息message = client_socket.recv(1024).decode("utf-8")current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")if message == "/exit":  #客户端退出# 客户端断开连接del client_sockets[client_socket]username = client_usernames[client_socket]del client_usernames[client_socket]broadcast(f"{time} server>>>{username} 离开了聊天室")print(f"{time} server>>{username} 离开了聊天室")breakelif message.startswith("FILE:"):   #客户端上传文件# 客户端要上传文件recv = message.split("|")file_name,file_size = recv[1],recv[2]receive_file(client_socket, file_name, file_size)elif message.startswith("DOWN:"):   #客户端请求下载# 处理文件下载请求recv = message.split("|")file_name = recv[1]file_path = os.path.join(base_path, file_name)# 请求的文件是否在本地存在if os.path.isfile(file_path) != True:client_socket.send(f"{time} server>>错误:{file_path}:文件不存在".encode("utf-8"))print(f"{time} server>>错误:{file_path}:文件不存在")continue# 请求的文件在本地存在,开始发送send_requested_file(client_socket, file_name, file_path)elif message == "/file list":   #客户端请求查看文件列表file_names = list_files_in_directory(os.path.normpath(base_path))file_names_bytes = pickle.dumps(file_names)inform = "LIST:"client_socket.send(inform.encode("utf-8"))client_socket.send(file_names_bytes)else:# 否则,将消息广播给其他客户端username = client_usernames[client_socket]broadcast(f"{time} {username}: {message}")print(f"{time} {username}: {message}")except:# 处理异常,例如客户端断开连接break# 用于接收文件的函数
def receive_file(client_socket, file_name, file_size):current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")base_path = "E:\python_files\计网-实验2\服务端保存文件"save_path = os.path.join(base_path,file_name)try:recv_size = 0file = open(save_path, 'wb')flag = Truewhile flag:if int(file_size) > recv_size:data = client_socket.recv(1024)recv_size += len(data)file.write(data)else:flag = Falsefile.close()print(f"{time} server>>文件上传成功")broadcast(f"{time} server>>>{client_usernames[client_socket]} 上传了文件: {file_name}")except Exception as e:print(f"{time} server>>>{client_usernames[client_socket]}文件传输失败: {e}")client_socket.send(f"{time} server>>>{client_usernames[client_socket]}文件传输失败: {e}".encode("utf-8"))  # 发送错误消息给客户端# 处理客户端下载文件请求
def send_requested_file(client_socket, file_name, file_path):current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")file_size = os.stat(file_path).st_sizetry:inform = ("FILE:" + "|" + file_name + "|" + str(file_size))client_socket.send(inform.encode("utf-8"))send_size = 0file = open(file_path, 'rb')flag = Truewhile flag:if send_size + 1024 > file_size:data = file.read(file_size - send_size)flag = Falseelse:data = file.read(1024)send_size += 1024client_socket.send(data)file.close()print(f"{time} server>>向 {client_usernames[client_socket]} 发送文件成功")except Exception as e:print(f"{time} server>>向 {client_usernames[client_socket]} 发送文件失败: {e}")client_socket.send(f"{time} server>>文件发送失败".encode("utf-8"))  # 发送错误消息给客户端# 广播消息给所有客户端
def broadcast(message):for client_socket in client_sockets:client_socket.send(message.encode("utf-8"))# 获取当前文件列表
def list_files_in_directory(directory):file_list = []for filename in os.listdir(directory):if os.path.isfile(os.path.join(directory, filename)):file_list.append(filename)return file_list# 主循环,等待客户端连接
while True:current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")if len(client_sockets) >= MAX_CONNECTIONS:# 达到最大连接数时,拒绝新连接client_socket, _ = server_socket.accept()client_socket.send(f"{time} server>>>连接已满,请稍后再试。".encode("utf-8"))client_socket.close()else:client_socket, client_address = server_socket.accept()username = client_socket.recv(1024).decode("utf-8")client_sockets[client_socket] = client_addressclient_usernames[client_socket] = usernameprint(f"{time} server>>>连接来自 {client_address}, 用户名: {username}<<<")client_socket.send(f"{time} server>>>连接成功!开始聊天吧".encode("utf-8"))broadcast(f"{time} server>>>{username} 加入了聊天室")# 使用线程池处理客户端消息executor.submit(handle_client, client_socket)
chatClient
import socket
import threading
import os
import pickle #序列化列表为字节数据
import datetime #获取系统时间# 创建客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服务器地址和端口
server_host = '192.168.1.110'  # 服务器主机
server_port = 15004  # 服务器端口
client_socket.connect((server_host, server_port))# 输入用户名
username = input("请输入您的用户名: ")
client_socket.send(username.encode("utf-8"))# 处理本地输入
def send_message():while True:current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")message = input()if message.lower() == "/exit":  # 退出客户端client_socket.send(message.lower().encode("utf-8"))client_socket.close()breakelif message.startswith("/file upload "):   # 上传文件file_path = os.path.normpath(message[13:])if os.path.isfile(file_path) != True:print(f"{time} local>>错误:{file_path}:文件不存在")continuesend_file(file_path)elif message.startswith("/file download "): # 下载文件file_name = message[15:]download_query(file_name)elif message.lower() == "/file list ":  # 查看文件列表client_socket.send(message.lower().encode("utf-8"))else:   # 发送普通消息client_socket.send(message.encode("utf-8"))# 上传文件
def send_file(file_path):current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")try:file_name = os.path.basename(file_path)file_size = os.stat(file_path).st_sizeinform = ("FILE:"+"|"+file_name+"|"+str(file_size))client_socket.send(inform.encode("utf-8"))send_size = 0file = open(file_path,'rb')flag = Truewhile flag:if send_size + 1024 > file_size:data = file.read(file_size-send_size)flag = Falseelse:data = file.read(1024)send_size += 1024client_socket.send(data)file.close()print(f"{time} local>>文件上传成功")except:print(f"{time} local>>文件传输失败")# 发送下载文件请求
def download_query(file_name):current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")try:inform = ("DOWN:" + "|" + file_name)client_socket.send(inform.encode("utf-8"))except:print(f"{time} local>>下载文件请求发送失败")def download(message):current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")recv = message.split("|")# 格式("FILE:" + "|" + file_name + "|" + str(file_size))file_name,file_size = recv[1],recv[2]base_path = "E:\python_files\计网-实验2\客户端保存文件"save_path = os.path.join(base_path, file_name)recv_size = 0file = open(save_path, 'wb')flag = Truewhile flag:if int(file_size) > recv_size:data = client_socket.recv(1024)recv_size += len(data)file.write(data)else:flag = Falseprint(f"{time} local>>文件 {file_name} 下载成功")file.close()# 创建一个线程来处理用户输入的消息
message_thread = threading.Thread(target=send_message)
message_thread.daemon = True  # 将线程设置为守护线程
message_thread.start()# 接收并显示服务器发送的消息
while True:current_time = datetime.datetime.now()time = current_time.strftime("%Y-%m-%d %H:%M:%S")message = client_socket.recv(1024).decode("utf-8")if not message:# 如果消息为空,表示与服务器断开连接print(f"{time} local>>与服务器断开连接")breakif message.startswith("FILE:"):download(message)elif message == "LIST:":file_names_bytes = client_socket.recv(1024)file_names = pickle.loads(file_names_bytes)if not file_names:print("当前服务器:无文件")else:print("当前服务器文件列表:")for file_name in file_names:print(file_name)else:print(message)
⑥程序测试
Ⅰ 使用正确的IP地址与端口号

首先使用get_ip.py获得本机ip

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\get_ip.py 
本机IP地址是: 192.168.56.103进程已结束,退出代码0

将此ip地址替换客户端/服务端程序段中的ip地址

注意端口号要用较高的,以避免需要管理员权限。若开机第一次运行,无需考虑更换端口号。

Ⅱ 测试普通消息

运行chatServer.py,启动服务端

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatServer.py 
2023-10-27 22:16:44 server>>等待客户端连接在 192.168.56.103:15004...

运行chatClient.py,启动一个客户端,用户名为wolf,服务端返回连接成功消息。

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatClient.py 
请输入您的用户名: wolf
2023-10-27 22:16:44 server>>>连接成功!开始聊天吧2023-10-27 22:16:44 server>>>wolf 加入了聊天室

使用客户端wolf,发送消息“hello,I am wolf.”

# 服务端
E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatServer.py 
2023-10-27 22:16:44 server>>等待客户端连接在 192.168.56.103:15004...
2023-10-27 22:16:44 server>>>连接来自 ('192.168.56.103', 59606), 用户名: wolf<<<
2023-10-27 22:19:50 wolf: hello,I am wolf.#客户端
E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatClient.py 
请输入您的用户名: wolf
2023-10-27 22:16:44 server>>>连接成功!开始聊天吧2023-10-27 22:16:44 server>>>wolf 加入了聊天室
hello,I am wolf.
2023-10-27 22:19:50 wolf: hello,I am wolf.

消息发送成功

Ⅲ 测试多用户状态下普通消息

运行chatClient.py,启动一个客户端,用户名为void,服务端返回连接成功消息。

使用void发送消息“hello,I am void.”

从wolf端可看到void进入聊天室以及发送消息

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatClient.py 
请输入您的用户名: wolf
2023-10-27 22:16:44 server>>>连接成功!开始聊天吧2023-10-27 22:16:44 server>>>wolf 加入了聊天室
hello,I am wolf.
2023-10-27 22:19:50 wolf: hello,I am wolf.
2023-10-27 22:18:38 server>>>void 加入了聊天室
2023-10-27 22:22:26 void: hello,I am void.
Ⅳ 测试文件列表与文件传输

在目录下有如下文件

  • 文件夹:服务端保存文件
  • 文件夹:客户端保存文件
  • 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
  • A甘晴void.txt
  • chatClient.py
  • chatServer.py

测试步骤如下:

  • 使用 void 端请求查看服务器可供下载的文件列表
  • 使用 void 端先上传一个不存在的文件“M甘晴void.txt”
  • 使用 void 端上传一个存在的文件“A甘晴void.txt”
  • 使用 void 端请求查看服务器可供下载的文件列表
  • 使用 wolf 端请求查看服务器可供下载的文件列表
  • 使用 wolf 端上传一个存在的文件“8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf”
  • 使用 wolf 端请求查看服务器可供下载的文件列表
  • 使用 wolf 端请求下载一个不存在的文件“M甘晴void.txt”
  • 使用 wolf 端请求下载一个存在的文件“A甘晴void.txt”
  • 使用 void 端请求下载一个存在的文件“8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf”

测试结果及截图如下:(截图更好看一些)

服务端

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatServer.py 
2023-10-27 22:16:44 server>>等待客户端连接在 192.168.56.103:15004...
2023-10-27 22:16:44 server>>>连接来自 ('192.168.56.103', 59606), 用户名: wolf<<<
2023-10-27 22:19:50 wolf: hello,I am wolf.
2023-10-27 22:18:38 server>>>连接来自 ('192.168.56.103', 59902), 用户名: void<<<
2023-10-27 22:22:26 void: hello,I am void.
2023-10-27 22:33:12 server>>文件上传成功
2023-10-27 22:34:25 server>>文件上传成功
2023-10-27 22:35:01 server>>错误:E:\python_files\计网-实验2\服务端保存文件\M甘晴void.txt:文件不存在
2023-10-27 22:35:13 server>>向 wolf 发送文件成功
2023-10-27 22:35:39 server>>向 void 发送文件成功

在这里插入图片描述

客户端wolf

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatClient.py 
请输入您的用户名: wolf
2023-10-27 22:16:44 server>>>连接成功!开始聊天吧2023-10-27 22:16:44 server>>>wolf 加入了聊天室
hello,I am wolf.
2023-10-27 22:19:50 wolf: hello,I am wolf.
2023-10-27 22:18:38 server>>>void 加入了聊天室
2023-10-27 22:22:26 void: hello,I am void.
2023-10-27 22:33:12 server>>>void 上传了文件: A甘晴void.txt
/file list
当前服务器文件列表:
A甘晴void.txt
/file upload E:\python_files\计网-实验2\8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
2023-10-27 22:34:25 local>>文件上传成功
2023-10-27 22:34:25 server>>>wolf 上传了文件: 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
/file list
当前服务器文件列表:
8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
A甘晴void.txt
/file download M甘晴void.txt
2023-10-27 22:35:01 server>>错误:E:\python_files\计网-实验2\服务端保存文件\M甘晴void.txt:文件不存在
/file download A甘晴void.txt
2023-10-27 22:35:13 local>>文件 A甘晴void.txt 下载成功

在这里插入图片描述

客户端void

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatClient.py 
请输入您的用户名: void
2023-10-27 22:18:38 server>>>连接成功!开始聊天吧2023-10-27 22:18:38 server>>>void 加入了聊天室
hello,I am void.
2023-10-27 22:22:26 void: hello,I am void.
/file list
当前服务器:无文件
/file upload E:\python_files\计网-实验2\M甘晴void.txt
2023-10-27 22:32:24 local>>错误:E:\python_files\计网-实验2\M甘晴void.txt:文件不存在
/file upload E:\python_files\计网-实验2\A甘晴void.txt
2023-10-27 22:33:12 local>>文件上传成功
2023-10-27 22:33:12 server>>>void 上传了文件: A甘晴void.txt
/file list
当前服务器文件列表:
A甘晴void.txt
2023-10-27 22:34:25 server>>>wolf 上传了文件: 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
/file download 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
2023-10-27 22:35:39 local>>文件 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf 下载成功

在这里插入图片描述

再看看两个文件夹内的情况,可以发现文件的上传与下载确实成功完成了。

在这里插入图片描述

可以发现测试是成功的,每个细节都完美地回应了设想。

Ⅴ 测试客户端退出

在这之前可以让wolf和void再说几句话

之后让客户端void使用“/exit”命令主动退出

客户端void使用/exit主动退出之后,wolf端和服务端都在终端显示了void退出的消息。(具体可以看整体数据和截图)

之后再让wolf端退出,最后结束服务器。

Ⅵ 全部测试过程终端命令行与截图

服务端

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatServer.py 
2023-10-27 22:16:44 server>>等待客户端连接在 192.168.56.103:15004...
2023-10-27 22:16:44 server>>>连接来自 ('192.168.56.103', 59606), 用户名: wolf<<<
2023-10-27 22:19:50 wolf: hello,I am wolf.
2023-10-27 22:18:38 server>>>连接来自 ('192.168.56.103', 59902), 用户名: void<<<
2023-10-27 22:22:26 void: hello,I am void.
2023-10-27 22:33:12 server>>文件上传成功
2023-10-27 22:34:25 server>>文件上传成功
2023-10-27 22:35:01 server>>错误:E:\python_files\计网-实验2\服务端保存文件\M甘晴void.txt:文件不存在
2023-10-27 22:35:13 server>>向 wolf 发送文件成功
2023-10-27 22:35:39 server>>向 void 发送文件成功
2023-10-27 22:45:22 void: OK,well computer network is really fun.
2023-10-27 22:46:20 wolf: You get it. Now I manifest dense interest in computer network.
2023-10-27 22:46:51 void: wish you get a good mark and achieve your score, good bye.
2023-10-27 22:47:03 wolf: good bye.
2023-10-27 22:47:08 server>>void 离开了聊天室
2023-10-27 22:48:40 server>>wolf 离开了聊天室进程已结束,退出代码-1

在这里插入图片描述

客户端wolf

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatClient.py 
请输入您的用户名: wolf
2023-10-27 22:16:44 server>>>连接成功!开始聊天吧2023-10-27 22:16:44 server>>>wolf 加入了聊天室
hello,I am wolf.
2023-10-27 22:19:50 wolf: hello,I am wolf.
2023-10-27 22:18:38 server>>>void 加入了聊天室
2023-10-27 22:22:26 void: hello,I am void.
2023-10-27 22:33:12 server>>>void 上传了文件: A甘晴void.txt
/file list
当前服务器文件列表:
A甘晴void.txt
/file upload E:\python_files\计网-实验2\8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
2023-10-27 22:34:25 local>>文件上传成功
2023-10-27 22:34:25 server>>>wolf 上传了文件: 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
/file list
当前服务器文件列表:
8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
A甘晴void.txt
/file download M甘晴void.txt
2023-10-27 22:35:01 server>>错误:E:\python_files\计网-实验2\服务端保存文件\M甘晴void.txt:文件不存在
/file download A甘晴void.txt
2023-10-27 22:35:13 local>>文件 A甘晴void.txt 下载成功
2023-10-27 22:45:22 void: OK,well computer network is really fun.
You get it. Now I manifest dense interest in computer network.
2023-10-27 22:46:20 wolf: You get it. Now I manifest dense interest in computer network.
2023-10-27 22:46:51 void: wish you get a good mark and achieve your score, good bye.
good bye.
2023-10-27 22:47:03 wolf: good bye.
2023-10-27 22:47:08 server>>>void 离开了聊天室
/exit
Traceback (most recent call last):File "E:\python_files\计网-实验2\chatClient.py", line 108, in <module>message = client_socket.recv(1024).decode("utf-8")^^^^^^^^^^^^^^^^^^^^^^^^
ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。进程已结束,退出代码1

在这里插入图片描述

客户端void

E:\anaconda\envs\python3-11\python.exe E:\python_files\计网-实验2\chatClient.py 
请输入您的用户名: void
2023-10-27 22:18:38 server>>>连接成功!开始聊天吧2023-10-27 22:18:38 server>>>void 加入了聊天室
hello,I am void.
2023-10-27 22:22:26 void: hello,I am void.
/file list
当前服务器:无文件
/file upload E:\python_files\计网-实验2\M甘晴void.txt
2023-10-27 22:32:24 local>>错误:E:\python_files\计网-实验2\M甘晴void.txt:文件不存在
/file upload E:\python_files\计网-实验2\A甘晴void.txt
2023-10-27 22:33:12 local>>文件上传成功
2023-10-27 22:33:12 server>>>void 上传了文件: A甘晴void.txt
/file list
当前服务器文件列表:
A甘晴void.txt
2023-10-27 22:34:25 server>>>wolf 上传了文件: 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
/file download 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf
2023-10-27 22:35:39 local>>文件 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION.pdf 下载成功
OK,well computer network is really fun.
2023-10-27 22:45:22 void: OK,well computer network is really fun.
2023-10-27 22:46:20 wolf: You get it. Now I manifest dense interest in computer network.
wish you get a good mark and achieve your score, good bye.
2023-10-27 22:46:51 void: wish you get a good mark and achieve your score, good bye.
2023-10-27 22:47:03 wolf: good bye.
/exit
Traceback (most recent call last):File "E:\python_files\计网-实验2\chatClient.py", line 108, in <module>message = client_socket.recv(1024).decode("utf-8")^^^^^^^^^^^^^^^^^^^^^^^^
ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。进程已结束,退出代码1

在这里插入图片描述

⑦程序总结与展望

感觉还是有必要给我花了很多时间做的这个chat小程序做一个总结的。

从程序功能上来看,应该是比较好地回应了需求,甚至新增了新的功能,比如列表的查看等,但这些功能也是在编写现有功能的时候觉得有必要加上去。确实是越编写,就越觉得有必要新增功能,换句话说,只有在写的时候才知道有什么新的功能是可以去优化用户体验的。

在程序实现上,发送与接收文件的分段的部分参考了A橙_学长,因为我原来怎么也想不到发送文件要考虑大多数情况下,尤其是文件很大的情况下,它一次是难以完全发送完毕的。这个时候如果不分段,就不能够正常发送。

我的程序仍然有很多没有实现,比如

  • 线程的锁
  • 并发处理
  • 可视化(使用pyqt或者tk)
  • 在服务端已满状态下向新用户发送拒绝(这个在之前2.3线程池实现了,但这里原理有点不一样,故未作处理)
  • 验证用户身份(数据库介入,涉及到注册、登录等验证)
  • 其他更加复杂的功能

其实可以发现这很像暑期CS课程组教我们的非常简单的全栈实现,在计网的基础上与别的学科有了交叉的融合。实际上我跟同学开玩笑说,这个chat小程序的最终终极进化对象应该是QIQC,也就是QQ,甚至实现更多的功能。

但是显然作为一门核心课的其中一个实验,做到这个程度我认为无论是从花的时间还是精力来讲,都已经足够了。因此我主要是从计网的角度去实现套接字的编程,并未涉及到其他融合的角度。我认为如果有足够的时间,我能做到,这也是很有趣的,然而现在我还有其他更多的内容需要学习。

三、实验感悟

能自己实现简单的套接字编程。

最初实现第一个TCP传输的时候,看到接收到的结果真的很开心!我感觉这个真的非常有趣,同时及也对于计算机网络的 学习更加感兴趣了。

实现简单的chat程序的时候遇到了瓶颈,特别是在传输文件的部分,真的搞不懂文件的传输还需要分段,但幸运的是向A橙_学长学习到了经验,解决了这个问题。另外,这个chat程序确实倾注了很多心血,感觉最后能实现预期的结果真的很开心。

最后感谢老师与助教学姐。

感谢A橙学长。

2023.10.27晚 于天马学生公寓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/203128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu中显卡驱动,cuda,cudnn安装

1. 在ubuntu中安装显卡驱动 参考&#xff1a;https://blog.csdn.net/m0_37605642/article/details/119651996 2.在ubuntu中安装cuda 参考&#xff1a;https://blog.csdn.net/m0_61431544/article/details/127007300 2.1 安装cuda cuda官网&#xff1a; https://developer.n…

前端“量子纠缠”:multipleWindow3dScene 来了

最近前端实现的量子纠缠在网络上火了起来&#xff0c;作者bgstaal的推文&#xff1a;效果如下&#xff1a; 量子纠缠 那我们一起来看下什么是量子纠缠&#xff0c;以及前端是如何实现的。 什么是量子纠缠&#xff1f; 在量子力学里&#xff0c;当几个粒子在彼此相互作用后&…

大数据Doris(三十三):Doris高级设置

文章目录 Doris高级设置 一、增大内存

【华为数据之道学习笔记】2-建立企业级数据综合治理体系

数据作为一种新的生产要素&#xff0c;在企业构筑竞争优势的过程中起着重要作用&#xff0c;企业应将数据作为一种战略资产进行管理。数据从业务中产生&#xff0c;在IT系统中承载&#xff0c;要对数据进行有效治理&#xff0c;需要业务充分参与&#xff0c;IT系统确保遵从&…

AWS Remote Control ( Wi-Fi ) on i.MX RT1060 EVK - 2 “架构 AWS”

接续上一章节&#xff0c;我们把开发环境架设好之后&#xff0c;此章节叙述如何建立 AWS IoT 环境&#xff0c;请务必已经有 AWS Account&#xff0c;申请 AWS Account 之流程将不在此说明。 III-1. 登入AWS IoT&#xff0c; 在“管理”>“所有装置”>“实物”下点击“建…

IDEA切换Python虚拟环境

前言 因为之前一直使用的IDEA开发&#xff0c;换到VSCODE之后各种不习惯&#xff0c;特别是DEBUG的操作&#xff0c;特别难受&#xff0c;因此决心换回IDEA 环境配置 已有项目调整 进入Project 选择SDKs&#xff0c;新建Python 配置Conda以及虚拟环境 有就选择一个虚拟环境…

LeetCode-周赛-思维训练-中等难度

第一题 1798. 你能构造出连续值的最大数目 解题思路 我们先抛开原题不看&#xff0c;可以先完成一道简单的题目&#xff0c;假设现在就给你一个目标值X&#xff0c;问你能够构造出从【1~X】的连续整数&#xff0c;最小需要几个数&#xff1f; 贪心假设期望&#xff1a;我们要…

Path Finder for Mac:超越系统的文件管理利器

Path Finder for Mac是一款卓越的文件管理器&#xff0c;它不仅具备基本的文件浏览、打开、复制和移动等操作功能&#xff0c;还引入了一系列强大的特性&#xff0c;使得用户可以更高效地管理和处理文件。 一、强大的预览功能 Path Finder for Mac支持多种文件格式的预览&…

题目分析,高度理解一维二维数组的申请和[]是什么运算符

第0题: 动态申请二维数组并输出非负数和 和负数出现次数 思路:输入数组大小,然后申请内存并不对其初始化,提高速度,传入数据到申请的数组中,判断如果数组中有元素小于0对其进行计数,否则加上非0数最后输出答案,释放内存 第一题: 解答: 运行结果: 思路分析: 创建长度为20的…

RobotFramework编写用例,在Jenkins上如何实现用例的并发运行?

我们了解RobotFramework编写自动化测试用例的方法&#xff0c;了解如何将用例在Jenkins上运行。 但是&#xff0c;随着用例的增多&#xff0c;传统的pybot/robot命令运行测试用例会耗费大量的时间&#xff0c;这就慢慢成为了一个苦恼的问题。 那么&#xff0c;在Jenkins上如何…

JFrog Artifactory二进制文件管理工具部署使用

1.简介 JFrog Artifactory二进制文件管理工具&#xff0c;目前已经在使用的公司有很多&#xff0c;足见他的方便好用。 2.下载安装包 点击下载地址 这里我下载的是7.9.2版本 3. 安装 &#xff08;1&#xff09;在安装JFrog Artifactory之前需要安装好jdk&#xff08;需…

9_企业架构队列缓存中间件分布式Redis

企业架构队列缓存中间件分布式Redis 学习目标和内容 1、能够描述Redis作用及其业务适用场景 2、能够安装配置启动Redis 3、能够使用命令行客户端简单操作Redis 4、能够实现操作基本数据类型 5、能够理解描述Redis数据持久化机制 6、能够操作安装php的Redis扩展 7、能够操作实现…

AWS 日志分析工具

当您的网络资源托管在 AWS 中时&#xff0c;需要定期监控您的 AWS CloudTrail 日志、Amazon S3 服务器日志和 AWS ELB 日志等云日志&#xff0c;以降低任何潜在的安全风险、识别严重错误并确保满足所有合规性法规。 什么是 Amazon S3 Amazon Simple Storage Service&#xff…

苹果ios的系统app应用WebClip免签应用开源及方式原理

在移动设备上&#xff0c;为了方便访问我们经常使用的网站或服务&#xff0c;我们经常会希望将其添加到主屏幕上&#xff0c;以便快速启动。虽然我们可以通过使用浏览器书签实现这一目标&#xff0c;但添加一个图标到主屏幕上&#xff0c;使得它看起来与原生App无异&#xff0c…

为何开展数据清洗、特征工程和数据可视化、数据挖掘与建模?

1.2为何开展数据清洗、特征工程和数据可视化、数据挖掘与建模 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解1.2节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。内容涵盖数据科学…

一个最新国内可用的免费GPT4,Midjourney绘画网站+使用教程

一、前言 ChatGPT GPT4.0&#xff0c;Midjourney绘画&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和用户进行创作交流。 然而&#xff0c;GPT-4对普…

MAC 系统在vs code中,如何实现自动换行

目录 问题描述&#xff1a; 问题解决&#xff1a; 问题描述&#xff1a; 在vscode中&#xff0c;有些时候&#xff0c;一行内容过多&#xff0c;如果不能自动换行&#xff0c;就需要拖动页面&#xff0c;才能看到完整的内容。如下图两行所示&#xff1a; 问题解决&#xff1a…

基于opencv和tensorflow实现人脸识别项目源码+可执行文件,采用python中的tkinter库做可视化

项目名称: 基于OpenCv和tensorflow的人脸识别 完整代码下载地址&#xff1a;基于OpenCv和tensorflow的人脸识别 环境配置: Pythontensorflow2OpenCv categories: 人工智能 description: Opencv是一个开源的的跨平台计算机视觉库&#xff0c;内部实现了图像处理和计算机视觉方…

2023 IoTDB 用户大会成功举办,深入洞察工业互联网数据价值

2023 年 12 月 3 日&#xff0c;中国通信学会作为指导单位&#xff0c;Apache IoTDB Community、清华大学软件学院、中国通信学会开源技术委员会联合主办&#xff0c;“科创中国”开源产业科技服务团和天谋科技&#xff08;北京&#xff09;有限公司承办的 2023 IoTDB 用户大会…

基于 Stereo R-CNN 的自动驾驶 3D 目标检测

论文地址&#xff1a;https://openaccess.thecvf.com/content_CVPR_2019/papers/Li_Stereo_R-CNN_Based_3D_Object_Detection_for_Autonomous_Driving_CVPR_2019_paper.pdf 论文代码&#xff1a;https://github.com/HKUST-Aerial-Robotics/Stereo-RCNN 论文背景 大多数 3D 物…