本文作于2022.1.25,供本人加深记忆
我们将使用下列简单的客户-服务器应用程序来演示对于UDP和TCP的套接字编程:
①客户从其键盘读取一行字符(数据)并将该数据向服务器发送。
②服务器接收该数据(sentence)并将这些字符转换为大写。
③服务器将修改的数据(modified sentence)发送给客户。
④客户接收修改的数据并在其显示器(monitor)上将该行显示出来。
图2-26着重显示了客户和服务器的主要与套接字相关的活动,两者通过UDP运输服务进行通信。 客户程序将被称为UDPClient. py,服务器程序被称为UDPServer. pyo为了强调关键问题,我们有意提供最少的代码。“好代码”无疑将具有更多辅助性的代码行,特别是用于处理岀现差错的情况。对于本应用程序,我们任意选择了114514作为服务器的端口号。
from socket import *
serverName = "hostname"
serverPort = 114514
clientsocket = socket(AF_INET, SOCK_DGRAM)
message = raw_input("Input lowercase sentence:")
clientSocket.sendto(message.encode(),(serverName,serverPort))
modifiedMessage, serverAddress = clientsocket.recvfrom(2048)
print (modifiedMessage.decode())
clientSocket.close ()
UDPCIient.py完整代码
逐行分析
from socket import *
该socket模块形成了在Python中所有网络通信的基础。包括了这行,我们将能够在程序中创建套接字。
serverName = ‘hostname’
在这里我们可以提供包含服务器IP地址或者主机名的字符串,如果是主机名的话将自动执行DNS lookup从而获得IP地址
serverPort = 114514
将整数变量serverPort,即服务器端口设置为114514
clientsocket = socket(AF_INET, SOCK_DGRAM)
该行创建了客户的套接字,称为clientSocket第一个参数指示了地址簇;第二个参数指示了该套接字是SOCK.DGRAM类型(数据报datagram)的,这意味着它是一个UDP套接字
message = raw_input('Input lowercase sentence:')
输入内容待转换为大写的字符串
clientSocket.sendto(message.encode(),(serverName,serverPort))
在这行中,我们首先将报文由字符串类型转换为字节类型,因为我们需要向套接字中发送字节;这将使用encode ()方法完成。方法sendto ()为报文附上目的地址 (serverName, serverPort)并且向进程的套接字clientSocket发送结果分组。
modifiedMessage, serverAddress = clientsocket.recvfrom(2048)
在发送分组之后,客户等待接 收来自服务器的数据。对于上述这行,当一个来自因特网的分组到达该客户套接字时,该分组的数据被放置到变量modified Message中,其源地址被放置到变量serverAddress中。变量serverAd-dress包含了服务器的IP地址和服务器的端口号。程序UDPClienl实际上并不需要服务器的地址信息,因为它从起始就已经知道了该服务器地址;而这行Python代码仍然提供了服务器的地址。方法recvfrom也取缓存长度2048作为输入。(该缓存长度用于多种目的。)
print (modifiedMessage.decode () )
这行将报文从字节转化为字符串后,在用户显示器上打印出modifiedMessage0它应当 是用户键入的原始行,但现在变为大写的了。
clientSocket.close ()
该行关闭了套接字。然后关闭了该进程。
from socket import *
serverPort = 114514
serverSocket = socket(AF_INET, SOCK_DGRAM)
serversocket.bind(('',serverPort))
print ("The server is ready to receive")
while True:
message, clientAddress = serverSocket.recvfrom(2048)
modifiedMessage = message.decode().upper()
serverSocket.sendto(modifiedMessage.encode(), clientAddress)
UDPServer.py完整代码
逐行分析
注意到UDPServer的开始部分与UDPClient类似。同上略过不谈。
serversocket.bind(('',serverPort))
上面行将端口号114514与该服务器的套接字绑定在一起。因此在UDPServer中,代码显式地为该套接字分配一个端口号。以这种方式,当任何人向位于该服务器的IP地址的端口114514发送一个分组,该分组将导向该套接字。
while True:
该while循环将允许UDPServer无限期地接收并处理来自客户的分组
message, clientAddress = serverSocket.recvfrom(2048)
这行代码类似于我们在UDPClient中看到的。当某分组到达该服务器的套接字时,该分组的数据被放置到变量message中,其源地址被放置到变量clientAddress中。变量clientAddress包含了客户的IP地址和客户的端口号。这里,UDPServer将利用该地址信息,因为它提供了返回地址,类似于普通邮政邮件的返回地址。使用该源地址信息,服务器此时知道了它应当将回答发向何处。
modifiedMessage = message.decode().upper()
它在将报文转化为字符串后,获取由客户发送的行并使用方法upper。将其转换为大写。
serverSocket.sendto(modifiedMessage.encode(), clientAddress)
最后一行将该客户的地址(IP地址和端口号)附到大写的报文上(在将字符串转化为字节后),并将所得的分组发送到服务器的套接字中。然后因特网将分组交付到该客户地址。在服务器发送该分组后,它仍维持在while循环中,等待(从运行在任一台主机上的任何客户发送的)另一个UDP分组到达。
与UDP不同,TCP是一个面向连接的协议。这意味着在客户和服务器能够开始互相发送数据之前,它们先要握手和创建一个TCP连接。TCP连接的一端与客户套接字相联系,另一端与服务器套接字相联系。当创建该TCP连接时,我们将其与客户套接字地址 ( IP地址和端口号)和服务器套接字地址(IP地址和端口号)关联起来。使用创建的TCP 连接,当一侧要向另一侧发送数据时,它只需经过其套接字将数据丢进TCP连接。这与 UDP不同,UDP服务器在将分组丢进套接字之前必须为其附上一个目的地地址。
注意:初次遇到TCP套接字有时会混淆欢迎套接字(welcome socket or server socket)(这是所有要与服务器通信的客户的起始接触点)和每个新生成的服务器侧的连接套接字(connection socket)(这是随后为与每个客户通信而生成的套接字)。
图2.28着重显示了客户和服务器的主要与套接字相关的活动,两者通过TCP运输服务进行通信。
from socket import *
serverName = 'servername'
serverPort = 114514
clientSocket = socket(AF_INET,SOCK_STREAM)
#有客户端套接字隐式捆绑到本地port
clientsocket.connect((serverName,serverPort))
sentence = raw_input('Input lowercase sentence:')
clientsocket.send(sentence.encode())
modifiedSentence = clientsocket.recv(1024)
print('From Server:' , modifiedSentence.decode())
clientSocket.close ()
TCPClient.py完整代码
逐行分析(与之前类似的忽略)
clientSocket = socket(AF_INET,SOCK_STREAM)
该行创建了客户的套接字,称为clientSocket,第一个参数仍指示底层网络使用的是 IPv4。第二个参数指示该套接字是SOCK_STREAM类型。这表明它是一个TCP套接字。
clientsocket.connect((serverName,serverPort))
前面讲过在客户能够使用一个TCP套接字向服务器发送数据之前(反之亦然),必须在客户与服务器之间创建一个TCP连接。上面这行就发起了客户和服务器之间的这条TCP连接。connect()方法的参数是这条连接中服务器端的地址。这行代码执行完后,执行三次握手,并在客户和服务器之间创建起一条TCP连接。
clientsocket.send(sentence.encode())
通过该客户的套接字并进入TCP连接发送字符串sentence,值得注意的是,该程序并未显式地创建一个分组并为该分组附上目的地址,而使用UDP套接字却要那样做。 相反,该客户程序只是将字符串sentence中的字节放入该TCP连接中去。
modifiedSentence = clientsocket.recv(1024)
当字符到达客户端(书上此处为服务器,我认为是笔误加以改正)时,它们被放置在字符串modifiedSentence中。字符继续积累在 modifiedSentence中,直到该行以回车符结束为止。
clientSocket.close ()
最后一行关闭了套接字,因此关闭了客户和服务器之间的TCP连接。
from socket import *
serverPort = 114514
serversocket = socket(AF_INET,SOCK_STREAM)
serverSocket.bind(('',serverPort))
serverSocket.listen(1)
print('The server is ready to receive')
while True:
connectionSocket,addr = serverSocket.accept()
sentence = connectionsocket.recv(1024).decode()
capitalizedSentence = sentence.upper()
connectionsocket.send(capitalizedSentence.encode())
connectionSocket.close ()
TCPServer. py完整代码
逐行分析
serverSocket.bind(('',serverPort))
与UDPServer类似,我们将服务器的端口号serverPort与该套接字关联起来。
serverSocket.listen(1)
但对TCP而言,serverSocket是我们的欢迎套接字。在创建这扇欢迎之门后,我们将等待并聆听某个客户敲门(即聆听某个客户端发出TCP连接请求)。其中参数定义了请求连接的最大数 (至少为1)。
connectionSocket,addr = serverSocket.accept()
当客户敲该门时,程序为serverSocket调用accept()方法,这在服务器中创建了一个称为connectionSocket的新套接字,由这个特定的客户专用。客户和服务器则完成了握手,在客户的clientSocket和服务器的serverSocket之间创建了一个TCP连接。借助于创建的TCP连接,客户与服务器现在能够通过该连接相互发送字节。使用TCP,从一侧发送的所有字节不仅确保到达另一侧,而且确保按序到达。
connectionSocket.close ()
在此程序中,在向客户发送修改的句子后,我们关闭了该连接套接字。但由于serverSocket保持打开,所以另一个客户此时能够敲门并向该服务器发送一个句子要求修改。
我的一点看法:
①UDP、TCP的发送字节的函数名称的不同,暗示了其参数要求的不同
令letter为待发送字节,
UDP:sendto(letter,clientAddress) 需要地址(to这个单词很关键)
TCP:send(letter) 不需要地址
②UDP中服务器套接字在创建时与一个端口相捆绑以实现当任何人向位于该服务器的IP地址的端口发送一个分组,该分组将导向该服务器套接字。而TCP中客户端本地套接字和服务器欢迎套接字均在创建时与本地端口捆绑(前者为隐式捆绑代码中可以不体现)。
图源自中科大郑烇老师2020年秋自动化系本科课程课件