Python中UDP和TCP编程
UDP和TCP区别:
- TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的, 且UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
- TCP首部开销20字节,开销较大;而UDP的首部开销小,只有8个字节。
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
UDP编程
- UDP属于无连接协议,在UDP编程是不需要首先建立连接,而是直接向接收方发送信息。
UDP编程常用方法:
- UDP编程常用到的socket模块方法有3个:
- socket模块是对Socket模块进行了二次封装,支持Socket接口的访问,大幅度简化了程序开发步骤,提高开发效率。
- socket([family[,type[,proto]]]):
- 创建一个Socket对象,即创建UDP套接字
- 其中fimily为:
- socket.AF_INTE 表示 IPV4
- socket.AF_INTE6 表示 IPV6
- type为:
- SOCK_DGRAM 表示 UDP
- SOCK_STREAM 表示 TCP
# 创建套接字,使用IPV4协议,使用UDP协议传输数据
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sendto(date:bytes, address):
- 用来发送数据
- 把date指定的内容发送给address指定的地址
- 其中address地址是包含接受方主机IP地址和应用程序端口号的元组:
- IPV4格式为(‘xxx.xxx.xxx.xxx’, 端口号)
- 其中参数date的类型为bytes类型,不能为str类型,否则报错。
- 两种方式将str转换成bytes类型:
- string类型前面加一个英文字母b
- 使用前提:参数string不能为一个变量名,而是字符串
- 例如:
sendto(==b=="你好啊", ("xxx.xxx.xxx.xxx", 8080))
- 调用encode()方法:
- 将str类型转换成为bytes类型。
- str类型变量名.encode(“utf-8”)或者"你好".encode(“utf-8”)
- 例如:
sendto(==str_msg.encode("utf-8")==, ("xxx.xxx.xxx.xxx", 8080))
- recv(bufsize[,flags]):
- 从套接字接受数据
- recv()只返回接收到的数据
- 参数bufsize表示本次接收的最大字节数。
- 参数若为1024,则表示1K
- 参数若为1024*1024,则表示1M
- recvfrom(bufsize[,flags]):
- 从套接字接受数据
- recvfrom()返回的是(数据, 客户端地址),可以用来接收对端的地址信息,这个对于udp这种无连接的,可以很方便地进行回复。
- 参数bufsize表示本次接收的最大字节数。
- 参数若为1024,则表示1K
- 参数若为1024*1024,则表示1M
# 1014表示本次接收的最大字节数
data, addr = udp_socket.recvfrom(1024)
- 注意:
- recv和recvfrom是可以替换使用的。
- 而换过来如果你在udp当中也使用recv,那么就不知道该回复给谁了。
- 如果你不需要回复的话,也是可以使用的。
- 另外就是对于tcp是已经知道对端的,就没必要每次接收还多收一个地址,没有意义,要取地址信息,在accept当中取得就可以加以记录了。
- bind(address)方法
- 参数address为一个元组(‘本地IP’, 端口号)
- 绑定本地信息
- '本地IP参数’若为空字符串,表示本机任何可用IP
# 绑定本地信息(端口和端口号,空字符串表示本机任何可用IP地址)
udp_socket.bind(('',5000))
- gethostname()方法
- 该方法可以用来获得当前的主机名
- 返回值为str类型
import socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
name = socket.gethostname()
print(name)
print(type(name))
结果如下:
LAPTOP-2B15DENO
<class 'str'>
- gethostbyname(name)方法
- 参数name为主机名(str类型)
- 该方法可以获取IP地址
- 返回值为str类型
import socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 获取主机名
name = socket.gethostname()
# 参数name表示本主机名
name_ip = socket.gethostbyname(name)
print(name)
print(type(name))
print(name_ip)
print(type(name_ip))
结果如下:
LAPTOP-2B15DENO
<class 'str'>
192.168.43.120
<class 'str'>
UDP发送数据:
- 创建套接字
- 发送数据
- 关闭
import socket
def main():
# 创建套接字,使用IPV4协议,使用UDP协议传输数据
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 输入发送者信息
receiver_ip = input("请输入接受者IP:")
receiver_port = int(input("请输入接受者端口号:"))
# 发送数据
while True:
udp_msg = input("请输入您要发送的信息(输入exit退出):")
if udp_msg == "exit":
break
udp_socket.sendto(udp_msg.encode("gbk"), (receiver_ip, receiver_port))
# 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
UDP接收数据
- 创建套接字
- 绑定本地信息(IP+端口号)
- 接收数据
- 关闭
import socket
def main():
# 创建套接字,使用IPV4协议,使用UDP传输协议
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定本地接收信息(IP+端口号)
name = socket.gethostname()
print("本机名:" + name)
ip_addr = socket.gethostbyname(name)
print("本机IP" + ip_addr)
receive_port = int(input("请输入接收数据的端口号:"))
print("正在接收数据……")
# 空字符串表示本机任何可用IP
udp_socket.bind((ip_addr, receive_port))
# 接收数据
while True:
# 2019表示本次接收数据的最大字节数
udp_msg, udp_addr = udp_socket.recvfrom(2019)
print("%s : %s" % (udp_addr, udp_msg.decode("gbk")))
# 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
TCP编程
- TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
- 应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( MTU)的限制)。
- 之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体 的TCP层。
- TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。
- 然后接收端实体对已成功收到的包发回一个相应的确认(ACK);
- 如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。
- TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
服务器端和客户端的区别:
- 服务器端:就是提供服务的一方
- 客服端:就是被服务的一方
TCP编程常用方法:
- socket([family[,type[,proto]]]):
- 创建一个Socket对象,即创建UDP套接字
- 其中fimily为:
- socket.AF_INTE 表示 IPV4
- socket.AF_INTE6 表示 IPV6
- type为:
- SOCK_DGRAM 表示 UDP
- SOCK_STREAM 表示 TCP
# 创建套接字,使用IPV4协议,使用TCP协议传输数据
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- connect(address)方法:
- 参数address为一个元组(‘本地IP’, 端口号)
- 连接服务器
# 连接服务器
server_ip = input("请输入您要连接的服务器IP:")
server_port = int(input("请输入服务器port:"))
tcp_socket.connect((server_ip, server_port))
# 或者:
tcp_socket.connect(("192.168.43.120", 8080))
- send(data:bytes)方法:
- 用来发送数据
- 其中参数date的类型为bytes类型,不能为str类型,否则报错。
tcp_msg = input("请输入您要发送的数据:")
tcp_socket.send(tcp_msg.encode("gbk"))
- recv(bufsize[,flags]):
- 从套接字接受数据
- recv()只返回接收到的数据
- 参数bufsize表示本次接收的最大字节数。
- 参数若为1024,则表示1K
- 参数若为1024*1024,则表示1M
- bind(address)方法
- 参数address为一个元组(‘本地IP’, 端口号)
- 绑定本地信息
- '本地IP参数’若为空字符串,表示本机任何可用IP
# 绑定本地信息(端口和端口号,空字符串表示本机任何可用IP地址)
tcp_socket.bind(('',5000))
- listen(backlog)方法
- backlog指定最多允许多少个客户连接到服务器。
- 它的值至少为1。
- 收到连接请求后,这些请求需要排队。
- 如果队列满,就拒绝请求。
- backlog应该理解为阻塞队列的长度,总共与服务器连接的客户端一共有 backlog + 1 个。
- 阻塞队列FIFO,当连接客户端结束后阻塞队列里的第一个客服端与服务器连接成功。
- accept()方法
- accept()接受一个客户端的连接请求。
- 并返回一个元组,里面包含一个新的套接字和链接则信息的元组(IP+端口号)
- 即返回值为(新的套接字,(IP, 端口号))。
- 新的套接字,不同于以上socket()返回的用于监听和接受客户端的连接请求的套接字;与此客户端通信是通过这个新的套接字上发送和接收数据来完成的。
TCP客户端构建流程:
- 创建套接字
- 链接服务器
- 发送数据
- 关闭套接字
import socket
def main():
# 创建套接字,使用IPV4协议,使用TCP传输协议
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入您要连接的服务器IP:")
server_port = int(input("请输入服务器port:"))
tcp_socket.connect((server_ip, server_port))
# 发送信息
tcp_msg = input("请输入您要发送的数据:")
tcp_socket.send(tcp_msg.encode("gbk"))
# 接收数据:
receive_msg = tcp_socket.recv(1024)
print("接收到的数据为:" + receive_msg.decode("gbk"))
# 关闭套接字
tcp_socket.close()
if __name__ == "__main__":
main()
TCP服务器端构建流程
- 创建套接字
- bind绑定本地信息
- listen让默认的套接字由主动变为可以被动连接
- accept等待客户端的链接
- recv/send接收、发送数据
import socket
def main():
# 创建套接字,使用IPV4协议,使用TCP传输协议
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地信息
tcp_socket.bind(("192.168.43.120", 6666))
# 让默认套接字由主动变成被动
# 这里的参数表示服务器阻塞队列的长度!
tcp_socket.listen(128)
# 调用多次accept,从而为多个客户端服务
while True:
# 等待客户端的链接
print("等待客户端链接……")
new_tcp_socket, client_addr = tcp_socket.accept()
print("客户端链接成功!")
print("客户端信息:" + str(client_addr))
# 循环带刺为同一个客户端服务多次
while True:
# 接收客户端发送的数据
recv_msg = new_tcp_socket.recv(1024)
print(recv_msg.decode("gbk"))
'''
如果recv解堵塞,那么有两种方式:
1. 客户端发送过来了数据
2. 客户端调用了close导致了 recv解堵塞
'''
if recv_msg:
# 会送一部分数据给客服端
new_tcp_socket.send("----ok----".encode("gbk"))
else:
break
# 关闭新的套接字
'''
关闭accept()返回的套接字
则意味着 为该客户端服务结束
'''
new_tcp_socket.close()
# 关闭监听套接字
'''
如果将监听套接字关闭了
那么会导致不能在等待新的客户端的到来
即tcp_socket.accept()会出现异常
'''
tcp_socket.close()
if __name__ == "__main__":
main()