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))
- string类型前面加一个英文字母b
- 两种方式将str转换成bytes类型:
- 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()