实验二 网络基础编程实验(JAVA/Python3)

实验目的

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

实验内容

  1. 采用TCP进行数据发送的简单程序(java/python3.5);
  2. 采用UDP进行数据发送的简单程序(java/python3.5);
  3. 多线程/线程池对比(java/python3.5);
  4. 写一个简单的chat程序,并能互传文件,编程语言不限。

实验原理

1.Socket-套接字

​ ​ ​ ​Socket(套接字)是一种抽象层,应用程序通过它来发送和接收数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。一个Socket允许应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。一台计算机上的应用程序向Socket写入的信息能够被另一台计算机上的另一个应用程序读取,反之亦然。

​ ​ ​ ​不同类型的Socket与不同类型的底层协议族以及同一协议族中的不同协议栈相关联。现在TCP/IP协议族中的主要Socket类型为流套接字和数据报套接字。流套接字将TCP作为其端对端协议(底层使用IP协议),提供了一个可信赖的字节流服务。一个TCP/IP流套接字代表了TCP连接的一端。数据报套接字使用UDP协议(底层同样使用IP协议),提供了一个"尽力而为"的数据报服务,应用程序可以通过它发送最长65500字节的个人信息。一个TCP/IP套接字由一个互联网地址,一个端对端协议(TCP或UDP协议)以及一个端口号唯一确定。

2.并行服务器

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

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

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

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

实验步骤

1.采用TCP进行数据发送的简单程序
TCP_Client:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: TCP_Client.py
@time: 2022/3/21 22:30
@desc:
"""
from socket import *serverName = 'Your IP Address'
serverPort = 12000while True:clientSocket = socket(AF_INET, SOCK_STREAM)clientSocket.connect((serverName, serverPort))print("Client has made a TCP connection with Server.")sentence = input("Input uppercase sentence:")clientSocket.send(sentence.encode())print("Client has delivered the message.")modifiedSentence = clientSocket.recv(1024).decode()print("Client has accepted the message.")print("The Message is:" + modifiedSentence)clientSocket.close()print("The TCP connection is closed.\n")
clientSocket = socket(AF_INET, SOCK_STREAM)

该行代码创建了客户的套接字,成为clientSocket。第一个参数指示底层网络使用的是IPv4。第二个参数表明这是一个TCP套接字。

clientSocket.connect((serverName, serverPort))

该行代码发起了客户端与服务器端的TCP连接,参数是这条连接中服务器端的地址(IP地址+端口号)。这条代码执行完后,执行三次握手,并在客户和服务器间建立了一条TCP连接。

sentence = input("Input uppercase sentence:")
clientSocket.send(sentence.encode())

用户通过上述代码获取一个句子,并通过该用户的套接字进入TCP连接,发送字符串sentence。由于已经和服务器建立了可靠的TCP连接,该程序不需要显式地创建一个分组并为该分组附上目的地址。

modifiedSentence = clientSocket.recv(1024).decode()

当字符到达客户端时,它们被放置在modifiedSentence中。在打印小写句子后,我们使用close()关闭该套接字。

注意:由于校园网的WIFI的IP地址总是在下次登陆时发生变化,因此,需要注意查看本地PC的IP地址。Windows操作系统下可以在CMD中使用ipconfig指令查看IPv4地址。

TCP_Server:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: TCP_Server.py
@time: 2022/3/21 22:24
@desc:
"""
from socket import *serverPort = 12000serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(128)
print("The SeverSocket is ready to receive.")
while True:connectionSocket, addr = serverSocket.accept()sentence = connectionSocket.recv(1024).decode()print("Server has accepted the message.")print("The Message is:" + sentence)sentence = sentence.lower()connectionSocket.send(sentence.encode())print("Server has delivered the message.")connectionSocket.close()print("The TCP connection is closed.\n")
serverSocket = socket(AF_INET, SOCK_STREAM)

与TCP_Client相同的是,服务器使用以上代码创建一个TCP套接字。

serverSocket.bind(('', serverPort))

使用bind()将服务器的端口号serverPort与该套接字关联起来。

对于TCP而言,serverSocket将是我们的欢迎套接字,在创建这扇欢迎之门后,服务器将等待并聆听某个客户敲门:

serverSocket.listen(128)
connectionSocket, addr = serverSocket.accept()
sentence = connectionSocket.recv(1024).decode()

当某客户敲门时,程序为serverSocket调用accept()方法,这在服务器中,创建了connectionSocket的新套接字,由这个特定的用户专用。客户和服务器则完成了握手,在客户的clientSocket和服务器的serverSocket之间创建了一个TCP连接。借助于创建的TCP连接,客户与服务器现在能够通过该链接相互发送字节。使用TCP,从一侧发送的所有字节不仅能确保到达另一端,而且能确保按序到达。

connectionSocket.close()

在此程序中,在向客户发送修改的句子后,我们关闭了该连接的套接字,但由于serverSocket依旧保持打开,所以另一个客户此时能够敲门并向该服务器发送一个句子要求修改。

2.采用UDP进行数据发送的简单程序
UDP_Client:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: UDP_Client.py
@time: 2022/3/22 11:27
@desc:
"""
from socket import *serverName = 'Your IP Address'
serverPort = 12000while True:clientSocket = socket(AF_INET, SOCK_DGRAM)message = input("Input uppercase sentence:")clientSocket.sendto(message.encode(), (serverName, serverPort))print("Client has delivered the message.")modifiedMessage, serverAddress = clientSocket.recvfrom(1024)modifiedMessage = modifiedMessage.decode()print("Client has accepted the message.")print("The Message is:" + modifiedMessage)clientSocket.close()print("The Socket is closed.\n")
serverName = 'Your IP Address'
serverPort = 12000

第一行将变量serverName置为字符串“Your IP Address”。这里,我们提供了或者包含服务器的IP地址或者包含服务器的主机名的字符串。如果使用主机名,则将自动执行DNS lookup从而获得IP地址。第二行将整数变量serverPort置为12000。

clientSocket = socket(AF_INET, SOCK_DGRAM)

此行创建客户的套接字,成为clientSocket。第一个参数指示了地址簇;特别是AF_INET指示了底层网络使用了IPv4。第二个参数指示了该套接字是一个UDP套接字。在这里,我们并没有指定客户套接字的端口号,客户套接字的端口号由操作系统帮我们自动分配。

clientSocket.sendto(message.encode(), (serverName, serverPort))

在上述这行中,我们首先将报文由字符串类型转换为字节类型,因为我们需要向套接字中发送字节,这将使用encode()方法完成。方法sendto()为报文附上目的地址,并且向进程的套接字clientSocket发送结果分组。

modifiedMessage, serverAddress = clientSocket.recvfrom(1024)

对于上述这行,当一个来自因特网的分组到达该用户套接字的时候,该分组的数据被放置到变量modifiedMessage中,其源地址被放置到变量serverAddress中。变量serverAddress包含了服务器的IP地址和服务器的端口号。

clientSocket.close()

该行关闭了clientSocket套接字。

UDP_Server:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: UDP_Server.py
@time: 2022/3/22 14:43
@desc:
"""
from socket import *serverPort = 12000while True:serverSocket = socket(AF_INET, SOCK_DGRAM)serverSocket.bind(('', serverPort))print("The Server is ready to receive.")message, clientAddress = serverSocket.recvfrom(1024)message = message.decode()print("Server has already received the message.")print("The Message is:" + message)modifiedMessage = message.lower().encode()serverSocket.sendto(modifiedMessage, clientAddress)print("Server has delivered the message.")serverSocket.close()print("The Socket is closed.\n")

注意到 UDPServer 的开始部分与 UDPClient 类似。它也是导入套接字模块,也将整数变量 serverPort 设置为 12000 ,并且也创建套接字类型 SOCK_DGRAM (一种 UDP 套接字)。与UDPClient 有很大不同的第一行代码是:

serverSocket.bind(('', serverPort))

上面行将端口号12000与个服务器的套接字绑定(即分配)在一起。因此在 UDPServer中, (由应用程序开发者编写的)代码显式地为该套接字分配一个端口号 。以这种方式,当任何人向位于该服务器的 IP 地址的端口 12000 发送一个分组,该分组将指向该套接字。UDPServer 然后进入一个 while 循环;该while 循环将允许UDPServer 无限期地接收并处理来自客户的分组。在该while循环中, UDPServer 等待一个分组的到达。

message, clientAddress = serverSocket.recvfrom(1024)

这行代码类似于我们在 UDPClient 中看到的。当某分组到达该服务器的套接字时,该分组的数据被放置到变量 message 中,其源地址被放置到变量 clien tAddress中。变量clientAddress 包含了客户的 IP 地址和客户的端口号。这里, UDPServer 将利用该地址信息,因为它提供了返回地址,类似于普通邮政邮件的返回地址。使用该源地址信息,服务器此时知道了它应当将回答发向何处。

serverSocket.sendto(modifiedMessage, clientAddress)

最后一行将该客户的地址 (IP 地址和端口号)附到大写报文上,并将所得的分组发送到服务器的套接字中 。(如前面所述,服务器地址也附在分组上,尽管这是自动而不是显式地由代码完成的 )。因特网则将分组交付到该客户地址。在服务器发送该分组后,它仍维持在 while 循环中,等待(从运行在任一台主机上的任何客户发送的)另一个 UDP分组到达。

3.多线程/线程池对比
多线程实现TCP客户端/服务器:

MultiThread_Client:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: MultiThread_Client.py
@time: 2022/3/22 19:15
@desc:
"""
from socket import *
import threading
import randomserverName = 'Your IP Address'
serverPort = 12000
sentencePool = ["HI,JIANJIAN", "NICE TO MEET YOU", "THANKS", "THAT'S FUNNY!"]def Worker(number):clientSocket = socket(AF_INET, SOCK_STREAM)clientSocket.connect((serverName, serverPort))print("Client " + str(number) + " has made a TCP connection with Server.")sentence = sentencePool[random.randint(0, 3)]clientSocket.send(sentence.encode())print("Client " + str(number) + " has delivered the message.")modifiedSentence = clientSocket.recv(1024).decode()print("Client " + str(number) + " has accepted the message.")print("The Message of Client " + str(number) + " is:" + modifiedSentence)clientSocket.close()print("The TCP connection of Client " + str(number) + " is closed.\n")for number in range(10):thread = threading.Thread(target=Worker, args=(number,))thread.start()

客户端的实现基于多线程。Worker为线程的工作函数,其核心功能为创建clientSocket,使该clientSocket与服务器建立TCP连接,向服务器的connectionSocket发送数据,接受connectionSocket传输的数据。客户端基于多线程实现数据发送可以有效地模拟真实生产环境下,不同PC主机向服务器发送数据的状态,从而对多线程/线程池的工作效果加以分析。

MultiThread_Server:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: MultiThread_Server.py
@time: 2022/3/22 19:46
@desc:
"""
from socket import *
import threadingserverPort = 12000def Worker(connSocket, addr):sentence = connSocket.recv(1024).decode()print("Server has accepted the message from: " + str(addr))print("The Message from " + str(addr) + " is:" + sentence)sentence = sentence.lower()connSocket.send(sentence.encode())print("Server has delivered the message to " + str(addr))connSocket.close()print("The TCP connection of " + str(addr) + " is closed.\n")serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(128)
print("The SeverSocket is ready to receive.")while True:connectionSocket, addr = serverSocket.accept()thread = threading.Thread(target=Worker, args=(connectionSocket, addr))thread.start()

服务器端的实现基于多线程。Worker为线程的工作函数。下面介绍服务器端的设计思路:

首先,服务器端建立serverSocket套接字,该套接字类似欢迎之门,用于等待和聆听用户的敲门。在while循环中,当serverSocket感知到用户敲门时,将调用accept()方法,创建一个新套接字connectionSocket,由这个特定的客户专用,此时服务器端将创建线程,调用工作函数。工作函数的核心作用为利用该用户专用的connectionSocket套接字完成数据接受、处理、回传操作,同时,由该线程主动关闭该connectionSocket。概括整体流程:该工作函数的核心作用为维护特定用户的connectionSocket。由于connectionSocket的管理交由线程负责,因此,主线程(即执行while循环的线程)可以在满足最大接受连接数约束条件的前提下,在主进程占用CPU的过程中,在任意时刻接受来自不同PC主机端的请求连接,从而实现了多线程维护服务器端的正常运作。

ThreadPool_Client:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: ThreadPool_Client.py
@time: 2022/3/22 20:59
@desc:
"""
from socket import *
import threading
import randomserverName = 'Your IP Address'
serverPort = 12000
sentencePool = ["HI,JIANJIAN", "NICE TO MEET YOU", "THANKS", "THAT'S FUNNY!"]def Worker(number):clientSocket = socket(AF_INET, SOCK_STREAM)clientSocket.connect((serverName, serverPort))print("Client " + str(number) + " has made a TCP connection with Server.")sentence = sentencePool[random.randint(0, 3)]clientSocket.send(sentence.encode())print("Client " + str(number) + " has delivered the message.")modifiedSentence = clientSocket.recv(1024).decode()print("Client " + str(number) + " has accepted the message.")print("The Message of Client " + str(number) + " is:" + modifiedSentence)clientSocket.close()print("The TCP connection of Client " + str(number) + " is closed.\n")for number in range(10):thread = threading.Thread(target=Worker, args=(number,))thread.start()

ThreadPool_Client的代码与Multithread_Client的代码相同,且目的一致。

ThreadPool_Server:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: ThreadPool_Server.py
@time: 2022/3/22 21:18
@desc:
"""
from concurrent.futures import ThreadPoolExecutor
from socket import *serverPort = 12000def Worker(connSocket, addr):sentence = connSocket.recv(1024).decode()print("Server has accepted the message from: " + str(addr))print("The Message from " + str(addr) + " is:" + sentence)sentence = sentence.lower()connSocket.send(sentence.encode())print("Server has delivered the message to " + str(addr))connSocket.close()print("The TCP connection of " + str(addr) + " is closed.\n")serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(128)
print("The SeverSocket is ready to receive.")pool = ThreadPoolExecutor(max_workers=10)while True:connectionSocket, addr = serverSocket.accept()pool.submit(Worker, connectionSocket, addr)

首先介绍有关线程池的定义:

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

从 Python3.2 开始,标准库为我们提供了 concurrent.futures 模块,它提供了 ThreadPoolExecutor 和 ProcessPoolExecutor两个类,实现了对 threading 和 multiprocessing 的进一步抽象,不仅可以自动调度线程,还可以做到:

(1)主线程可以获取某一个线程(或者任务的)的状态,以及返回值;

(2)当一个线程完成的时候,主线程能够立即知道;

(3)让多线程和多进程的编码接口一致。

我们可以使用 ThreadPoolExecutor 来实例化线程池对象。传入max_workers参数来设置线程池中最多能同时运行的线程数目。我们可以使用 submit 函数来提交线程需要执行的任务(函数名和参数)到线程池中,并返回该任务的抽象对象,注意 submit() 不是阻塞的,而是立即返回。通过 submit 函数返回的任务抽象对象,能够使用其 done() 方法判断任务是否结束。

在ThreadPool_Server的设计中,我们的核心工作函数Worker()与在MultiThread_Server中的Worker()保持一致。我们通过ThreadPoolExecutor()创建了一个最大工作线程数为10的线程池,并且当serverSocket感知到有新用户敲门时,

若将服务器端线程池最大工作线程数设置为比客户端请求线程总数小,由于线程池只会维护最大工作线程数的线程进行工作,因此,当线程池的线程已满时,后到的任务需要排队等待线程池对其进行工作调度

多线程/线程池的比较:

多线程并不会阻塞一个新连接的处理,对于多线程而言,它会一直消耗资源来进行连接处理,直到计算机资源的耗尽。随着线程数的增多,系统资源将被消耗地更多,这将导致系统花费大量的时间处理上下文切换和线程管理,主线程中与其它客户建立connectionSocket进行服务的时间将会相对应的减少。

线程池仅在有空闲线程的时候才会处理新的连接,否则将进行阻塞。当新的客户端连接请求传入服务器时,它将交给线程池中的一个线程处理。当该线程处理完这个客户端后,线程将返回线程池,为下一次请求处理做好准备。

4.写一个简单的chat程序,并能互传文件
Chat_Client:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: Chat_Client.py
@time: 2022/3/23 9:34
@desc:
"""
import os
import json
import struct
from socket import *download_dir = r'C:\Users\HUAWEI\Desktop'
buffsize = 1024
serverName = 'Your IP Address'
serverPort = 12000while True:clientSocket = socket(AF_INET, SOCK_STREAM)clientSocket.connect((serverName, serverPort))print("Client has made a TCP connection with Server.")choice = input("What do you want to do with the Server?Chat or File Trans\n")if choice == "Chat":command = "Chat"clientSocket.send(command.encode())sentence = input("Input sentence:")clientSocket.send(sentence.encode())print("Client has delivered the message.")reply = clientSocket.recv(1024).decode()print("Client has accepted the message.")print("The Message is:" + reply)clientSocket.close()print("The TCP connection is closed.\n")elif choice == "File Trans":mode = input("GET or DELIVER\n")if mode == "GET":command = "GET"clientSocket.send(command.encode())head_struct = clientSocket.recv(4)head_len = struct.unpack('i', head_struct)[0]data = clientSocket.recv(head_len)head_dir = json.loads(data.decode('utf-8'))filesize_b = head_dir['filesize_bytes']filename = head_dir['filename']recv_len = 0recv_mesg = b''f = open(download_dir + '\\' + filename, 'wb')while recv_len < filesize_b:if filesize_b - recv_len > buffsize:recv_mesg = clientSocket.recv(buffsize)f.write(recv_mesg)recv_len += len(recv_mesg)else:recv_mesg = clientSocket.recv(filesize_b - recv_len)recv_len += len(recv_mesg)f.write(recv_mesg)f.close()elif mode == "DELIVER":command = "DELIVER"clientSocket.send(command.encode())filemesg = input('Input the FilePath:').strip()filesize_bytes = os.path.getsize(filemesg)dict = {'filename': os.path.basename(filemesg),'filesize_bytes': filesize_bytes,}head_info = json.dumps(dict)head_info_len = struct.pack('i', len(head_info))clientSocket.send(head_info_len)clientSocket.send(head_info.encode('utf-8'))with open(filemesg, 'rb') as f:data = f.read()clientSocket.sendall(data)print("Deliver the File successfully.")

客户端一共有两种核心模式:Chat(聊天)和File Trans(文件传输),当处于Chat模式下时,客户端可以向服务器端发送聊天内容,并等待服务器返回的应答内容。当处于File Trans模式下时,又分为两个功能板块:GET(获取服务器文件)和DELIVER(发送本地文件)。GET模块的核心功能为获取服务器提供的相关文件,并保存在本地指定的地方,DELIVER模块的核心功能为发送本地指定的文件。

客户端的运行流程如下:

(1)告知服务器客户端的模式请求,即告知服务器,客户端当前想做的是Chat(聊天)还是Get(获取文件)还是Deliver(传输文件),该步骤由以下代码实现(例):

command = "GET"
clientSocket.send(command.encode())

(2)若选择聊天模式,则将输入的句子编码后发送给服务器端;

sentence = input("Input sentence:")
clientSocket.send(sentence.encode())
print("Client has delivered the message.")

(3)若选择获取文件模式,客户端首先接受服务器发来的文件信息(head_dir),该结构包含了文件名及文件的大小,之后利用while循环对文件进行下载,如果文件的大小大于缓冲区大小,则分批对文件内容进行读取、写入操作,反之,则一次性对文件进行读取、写入。

while recv_len < filesize_b:if filesize_b - recv_len > buffsize:recv_mesg = clientSocket.recv(buffsize)f.write(recv_mesg)recv_len += len(recv_mesg)else:recv_mesg = clientSocket.recv(filesize_b - recv_len)recv_len += len(recv_mesg)f.write(recv_mesg)

(4)若选择发送文件模式,客户端会编写文件信息结构体,其中包含了文件名及文件的大小,客户端首先告知服务器文件信息结构体的大小,接着发送文件信息结构体,最后向服务器发送文件数据,至此文件发送结束。

其中,服务器向客户端提供了TCP_Client.py文件,客户端向服务器提供了TCP_Server.py文件。由于客户端、服务器均将文件下载至本地桌面,从上图可知:我们成功完成了聊天及文件传输。

Chat_Server:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
"""
@author: 无名的英雄
@file: Chat_Server.py
@time: 2022/3/23 20:00
@desc:
"""
import os
import json
import struct
import threading
from socket import *buffsize = 1024
download_dir = r"C:\Users\HUAWEI\Desktop"
serverPort = 12000def Worker(connSocket, addr):command = connSocket.recv(1024).decode()if command == "Chat":print("Server has accepted the message.")request = connSocket.recv(1024).decode()print("The Message is:" + request)sentence = input("Input sentence:")connSocket.send(sentence.encode())print("Server has delivered the message.")elif command == "GET":filemesg = input('Input the FilePath:').strip()filesize_bytes = os.path.getsize(filemesg)dict = {'filename': os.path.basename(filemesg),'filesize_bytes': filesize_bytes,}head_info = json.dumps(dict)head_info_len = struct.pack('i', len(head_info))connSocket.send(head_info_len)connSocket.send(head_info.encode('utf-8'))with open(filemesg, 'rb') as f:data = f.read()connSocket.sendall(data)print("Deliver the File successfully.")elif command == "DELIVER":head_struct = connSocket.recv(4)head_len = struct.unpack('i', head_struct)[0]data = connSocket.recv(head_len)head_dir = json.loads(data.decode('utf-8'))filesize_b = head_dir['filesize_bytes']filename = head_dir['filename']recv_len = 0recv_mesg = b''f = open(download_dir + '\\' + filename, 'wb')while recv_len < filesize_b:if filesize_b - recv_len > buffsize:recv_mesg = connSocket.recv(buffsize)f.write(recv_mesg)recv_len += len(recv_mesg)else:recv_mesg = connSocket.recv(filesize_b - recv_len)recv_len += len(recv_mesg)f.write(recv_mesg)f.close()connSocket.close()print("The TCP connection of " + str(addr) + " is closed.\n")serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(128)
print("The SeverSocket is ready to receive.")while True:connectionSocket, addr = serverSocket.accept()thread = threading.Thread(target=Worker, args=(connectionSocket, addr))thread.start()

服务器端的整体架构与客户端类似,故仅介绍服务器端的工作流程:

(1)服务器端首先接受来自客户端的模式信息,及客户端目前想做的是聊天、获取文件还是文件发送。

command = connSocket.recv(1024).decode()

(2)若客户端想与服务器端聊天,服务器将自动接收客户端发送的字符串,管理员可在服务器端输入字符串发回给客户端。

(3)若客户端想获取文件,管理员可在服务器端指定发送给客户端的文件,服务器端首先会将文件名及文件大小封装在文件信息结构体中,服务器将发送文件信息结构体的大小,接着发送文件信息结构体,最后发送文件数据给客户端。

(4)若客户端想发送文件,服务器首先会接受客户端发送的文件信息结构体获取文件名及文件大小,之后服务器会利用while循环对文件进行下载,如果文件的大小大于缓冲区大小,则分批对文件内容进行读取、写入操作,反之,则一次性对文件进行读取、写入。

HNU--计算机网络实验2相关推荐

  1. 计算机网络实验八——聊天程序

    :比较费工夫的一次实验,因为不熟悉VS的MFC程序开发流程:以此简单记录一下. :仅为个人总结:如有错误,还望指教! 计算机网络实验八--聊天程序 HNU CS Computer Network La ...

  2. 计算机网络实验一——应用协议与数据包分析

    计算机网络实验一--应用协议与数据包分析 一.实验题目 二.实验内容 三.实验原理 1.HTTP 的工作原理 2.HTTP 报文格式 四.实验步骤 1.截获报文步骤 2.TCP连接分析 3.HTTP简 ...

  3. 湖南大学计算机网络实验二------水

    计算机网络实验二 网络基础编程实验(Python3) HNU 由于这一次实验2.1-2.3都是给了代码的,所以没有写了,只写了2.3的对比和2.4的代码,实现互通信 2.3 多线程/线程池对比(pyt ...

  4. 计算机网络实验五,计算机网络(实验五).docx

    计算机网络(实验五).docx 实验五一.实验内容在这个实验室里,我们将探讨ICMP 协议得几个方面由 Ping 项目产生得P 信息Tracer ute程序生成得C消息关于 CM 信息得格式与内容.在 ...

  5. 计算机网络实验ip数据报转发,计算机网络实验报告三网际协议IP.doc

    计算机网络实验报告三网际协议IP 计算机网络实验报告 实验题目:网际协议IP学号:201200301106日期:2014/11/20班级:2012级软工3班姓名: 李凯峰实验目的: 1.掌握IP数据报 ...

  6. 计算机虚拟网络毕业论文,计算机毕业论文——基于WEB的虚拟计算机网络实验平台.doc...

    PAGE Tianjin University of Technology and Education 毕 业 设 计 专 业: 计算机科学与技术 班级学号: 计0203班 – 11 学生姓名: 指导 ...

  7. 计算机网络协议教案,计算机网络实验教案(6)网络协议分析-IP协议3.pdf

    计算机网络实验教案(6)网络协议分析-IP协议3.pdf (2页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.9 积分 <计算机网络实验> ...

  8. 配置实验室计算机步骤,计算机网络实验指导书(new)

    计算机网络实验指导书 实验一 以太网的组建(2学时) 实验名称:以太网的组建 实验目的: 1.了解实验室布局:认识交换机与路由器的结构与连接方法: 2.掌握简单的局域网组网方法: 3.掌握简单的局域网 ...

  9. 计算机网络数据分析报告,贵州大学计算机网络实验报告-实验四-分析IP协议数据包格式...

    贵州大学计算机网络实验报告-实验四-分析IP协议数据包格式 (7页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.9 积分 贵州大学GUIZHOU UN ...

  10. 宁波大学计算机网络实验五,宁波大学计算机网络实验答案.doc

    宁波大学计算机网络实验答案 实验三 DNS协议分析 思考题: Q1.运行nslookup,查询并记载你的本地DNS 服务器名称及其IP 地址, 的 权威DNS 服务器名称及其IP 地址: A: 运行 ...

最新文章

  1. system-copy 和 ShellExecute 用法
  2. python学习笔记(二)— 集合
  3. 微软电脑适合什么人用_#微软surface pro使用心得# 大学生挑电脑参考/平板与电脑二合一到底买的是什么...
  4. JZTK项目 驾照题库项目servlet层得到的json字符串在浏览器中 汉字部分出现问号?无法正常显示的解决方法
  5. pythongui界面复选框数值选择并求和_如何使用Python从图像中分离复选框按钮和复......
  6. get方法报空指针_C++基础教程之指针拷贝详解
  7. php适配器设计模式,螃蟹学PHP设计模式之适配器模式
  8. python 解析pb文件_利用Python解析json文件
  9. 听说去了BAT的 Linuxers 都做过这套面试题!
  10. SVN自动定时更新方法
  11. Dockerfile 中的命令
  12. Boost-IO学习 异步数据处理Simple(转)
  13. 高校科研管理系统源代码_高校科研信息管理系统
  14. 乾颐堂现任明教教主(2014年课程)TCPIP协议详解卷一 第一节课笔记
  15. OpenGL学习笔记一
  16. estimating the Flood kirs——曼哈顿距离
  17. 干货:制造业中的机械智能(内附完整PPT)
  18. 【NOI2017模拟3.30】原谅(计算几何,期望)
  19. 叶君—国画大师笔下的“忠义千秋”
  20. 用Python爬取微信好友,原来他们是这样的人......

热门文章

  1. win10升级nvidia、cuda、cudnn,非常简单
  2. matlab怎么看输出电压纹波,Boost变换器的能量传输模式和输出纹波电压分析.pdf
  3. Jenkins修改端口号
  4. Android 第三方桌面,怎么请求Widget的android.permission.BIND_APPWIDGET
  5. yii2.0域名目录绑定(二级域名)以及url美化 url伪静态 Apache ,Ngnix和 IIS
  6. S型速度曲线_博图+变频器+三相异步电机(以堆垛机控制系统举例)
  7. 初始化一个java空数组_Java 数组的两种初始化方式
  8. 转载一些关于QQ空间的文章
  9. 程序员公司实行996,加班费却只有10块!网友:我们20块
  10. 有关window的history和location的使用