本实验为《计算机网络 自顶向下方法》中的编程练习,通过编程实现才能更好的理解计算机如何实现连接和数据发送。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
运行在不同机器上的进行彼此通过向套接字发送报文来进行通信。当使用UDP时,必须先将目的地址附在该分组之上。目的地址包含什么?–>目的主机的IP地址和目的地套接字的端口号组成。
实现如下客户–服务器应用程序:
(1)客户从其键盘读取一行字符(数据)并将该数据向服务器发送。
(2)服务器接收该数据并将这些字符转换为大写。
(3)服务器将修改的数据发送给客户。
(4)客户接收修改的数据并在其监视器上将该行显示出来。
由于代码进行了详细的注释,窗口较小不便观察。可以复制出来进行理解分析。
客户端程序UDPClient.py
创建一个UDP套接字,并在用户输入一段小写字母组成的字符串后,发送到指定服务器地址和对应端口,等待服务器返回消息后,将消息显示出来。
服务端程序TCPServer.py
一直保持一个可连接的UDP套接字,在接收到字符串后,将其改为大写,然后向客户端返回修改后的字符串。
UDPClient.py
from socket import * # 导入实现socket套接字的包
serverName = '191.101.232.165' # 服务器地址,可以使用远程服务器。也可以自己选择另一台电脑作为服务器,此处需要输入另一台电脑的IP地址
serverPort = 12000 # 服务器指定的端口
clientSocket = socket(AF_INET, SOCK_DGRAM) # 创建UDP套接字,第一个参数指示了地址簇,AF_INET指示了底层网络使用IPv4协议;第二个参数指示为UDP套接字。值得注意的是没有指定客户套接字的端口号,操作系统会做这件事。因为使用时会创建操作进程
message = input('Input lowercase sentence:').encode() # 内置函数用户输入信息,并编码为bytes以便发送
clientSocket.sendto(message, (serverName, serverPort)) # 打包信息,发送到目的主机(服务器)。目的地址:(serverName, serverPort),如上所述需要目的IP地址和端口号;而且源地址也会附到分组上,这是自动完成的,不需要显式由代码完成。发送分组后,等待接收来自服务端的数据
modifiedMessage, serverAddress = clientSocket.recvfrom(2048) # 从服务器接收信息,同时也得到服务器地址。分组数据放到modifiedMessage,源地址放在变量serverAddress中(同样包含IP和端口号)
print(modifiedMessage.decode()) # 显示服务器返回的信息
clientSocket.close() # 关闭套接字
UDPServer .py
from socket import *
serverPort = 12000 # 服务器指定的端口
serverSocket = socket(AF_INET, SOCK_DGRAM) # 创建UDP套接字,使用IPv4协议
serverSocket.bind(('',serverPort)) # 将套接字绑定到之前指定的端口
print("The server in ready to receive")
while True: # 服务器将一直等待一个分组(UDP报文)到达
message, clientAddress = serverSocket.recvfrom(2048) # 接收客户端信息,获得地址
modifiedMessage = message.upper() # 将客户端发来的字符串变为大写
serverSocket.sendto(modifiedMessage, clientAddress) # 通过已经获得的客户端地址,将修改后的字符串发回客户端
先在一台机器上启动服务器程序,然后在另一台机器上启动客户端程序。运行效果如下:
服务器端:
客户端:
与UDP不同,TCP为面向连接的协议。提供端到端的可靠传输。这就意味着在客户端和服务器端能发送数据之前先要握手(也就是大家熟知的三次握手)和创建一个TCP连接。因此创建TCP连接时,需要将其与客户套接字地址和服务器套接字地址关联起来。当发送数据时,只需要经过套接字将数据放入TCP连接。与UDP不同,UDP在将分组放入套接字之前必须为其附上一个目的地址。
在三次握手期间,客户进程先敲服务器进程的欢迎之门。当服务器收到信息后会生成一个新的套接字,专门用于特定的客户。然后对应专门的客户进行连接会生成新的套接字,称为连接套接字。
欢迎套接字:所有要与服务器通信的客户的起始接触点。
连接套接字:随后为了每个客户通信而生成的套接字。
实现如下客户–服务器应用程序的流程图:
客户端程序TCPClient.py
创建一个TCP套接字,然后向指定服务器地址和端口发起连接,等待服务器连接后,再将用户输入的字符串通过套接字发送,其后将服务器返回的消息显示出来。
服务端程序TCPServer.py
一直保持一个TCP欢迎套接字,可接收任何客户端的连接请求。在接收到客户端的连接请求后,创建一个新的TCP连接套接字用于单独与该客户通信,同时显示客户端地址和端口。在接收到客户端发来的字符串后,将其改为大写,然后向客户端返回修改后的字符串。 最后,关闭TCP连接套接字。
from socket import *
serverName = '191.101.232.165' # 指定服务器地址
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM)
# 建立TCP套接字,使用IPv4协议,第二参数为类型声明,和上面UDP套接字不同;
# 创建客户套接字时没有指定 端口号,操作系统进行该操作
clientSocket.connect((serverName,serverPort)) # 向服务器发起连接,先进行三次握手,然后建立TCP连接;
# 注意和UDP方式对比:UDP直接分组然后发送(sendto)
sentence = input('Input lowercase sentence:').encode() # 用户输入信息,并编码以便发送
clientSocket.send(sentence) # 将信息发送到服务器
modifiedSentence = clientSocket.recvfrom(1024) # 从服务器接收信息
print(modifiedSentence[0].decode()) # 显示信息
clientSocket.close() # 关闭套接字
TCPServer .py
from socket import *
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM) # 创建TCP欢迎套接字,使用IPv4协议
serverSocket.bind(('',serverPort)) # 将TCP欢迎套接字绑定到指定端口,等待并聆听客户敲门
serverSocket.listen(1) # 定义最大连接数,至少为1
print("The server in ready to receive")
while True:
connectionSocket, addr = serverSocket.accept() # 接收到客户连接请求后,调用accept()方法,创建新的TCP连接套接字,由这个客户专用;进行握手后建立一个TCP连接
print('Accept new connection from %s:%s...' % addr)
sentence = connectionSocket.recv(1024) # 获取客户发送的字符串
capitalizedSentence = sentence.upper() # 将字符串改为大写
connectionSocket.send(capitalizedSentence) # 向用户发送修改后的字符串
connectionSocket.close() # 关闭TCP连接套接字
先在一台机器上启动服务器程序,然后在另一台机器上启动客户端程序。运行效果如下:
服务器端:
客户端: