在做UDP聊天器之前,我们需要补充一些知识,首先我们先来了解一下什么是UDP:
什么是UDP呢?
UDP全称是User Datagram Protocol,中文就是用户数据报协议,是Internet协议集中的一种无连接传输层协议,提供面向事务简单不可靠传送服务。简单来说就是传输层的一种传输协议,传输之前不需要像TCP那样提前建立连接,如果将TCP比作“”“打电话模式”,那么UDP就可以比作“写信模式”。
我们大体的了解了一下UDP的定义,那么接下来我们来看一下“写信模式”有哪些特点:
1.不可靠性:
UDP使用尽最大努力交付,但不保证一定交付成功。也就是说,通过udp协议传输的数据不能保证其完整性和可到达性。因此主机 不需要维护复杂的连接关系。通俗点来讲,就是你邮寄出去的信封可能会在在路途中丢失。
2.无连接性:
udp是无连接的,即通信时是不需要创建连接的,目标主机的运输层收到之后不给出确认。举一个不是很恰当的例子:你写信时应该不会提前给对方打声招呼吧。
3.面向报文:
对应用层下来的报文和对ip网络层交上来的报文都不进行改动,直接添加/去除首部后进行下一步操作。报文过长则在ip层会被分片;报文过短则在ip层的数据报的首部会很长。时下有一些邮局提供全服务,即只要你在本局购买邮票,你写信时只需要提交要邮寄的内容其它的全都交给邮局来搞定。
4.没有拥塞机制:
网络出现拥塞后不会降低源主机的发送速率,对某些实时性应用很重要。如IP电话、实时视频等。同时允许在网络拥塞是丢失一些数据,但不允许数据有太大的时延。比如你已经收到很多很多的信封,但此时仍然会有人还需要给你寄信,他会照常去寄信,而并不会因为你已经收到了很多信而停止其活动。
5.没有明确意义上的客户端与服务器端
寄信人也可以收信,收信人也可以寄信。
6.支持一对一,一对多,多对一、多对多的交互通信
7.首部开销小,只有8个字节
是不是突然有一个疑问,首部是什么?别急,下面我们来看一下UDP的组成你就明白了!
UDP由两部分组成,一部分是首部字段,另一部分是数据字段。这里仅介绍一下首部字段:
首部字段仅有8字节,分为4个字段:
源端口: 源端口号。在需要对方回信时选用,不需要时可用全0。
目的端口: 目的端口号。在终点交付时使用。
长度: udp用户数据报的长度,其最小值为8(仅首部)。
检验和: 检测udp用户数据报在传输中是否有错,若有错就丢弃。
除此之外,我们还需要稍微再了解一下ip和端口:
IP地址
什么是ip呢?
ip就 相当于“手机号“,用来标记网络上的一台电脑,比如192.168.1.1。在本地局域网是唯一的。每一个IP地址都包括两部分:网络号(标记网络地址)和主机号(标记主机地址),共有ABCDE五种类型。
如何查看IP?
liunx 终端命令
ifconfig
Windows cmd命令
ipconfig
IP分类
IPv4和IP6
ipv4由四组数字组成,每组数字必须在0~255之间。也就是说共有256**3 个ipv4地址。
端口(port)
什么是端口?
端口就是用于标记主机上的应用程序,每一个应用程序的端口号都不一样
端口号有两种:
知名端口(well known ports):
即众所众知的端口号,范围从0~1023。如HTTP端口号为80,FTP端口号为21。
动态端口(Dynamic ports):
当程序需要网络通信时,主机会从可用端口中分配一个端口给程序,即动态分配。当这个程序结束时端口号同时释放。动态端口号范围是从1024~65535。
最后我们再了解一下socket:
什么是socket(套接字)?
socket是进程通信的一种方式,能够实现不同主机之间的进程通信,目前网络上大多数服务都是基于socket来完成通信的。
OK,接下来我们就要进入主题,开始编写聊天器了。
首先我们先来梳理一下流程:
1.创建套接字对象
2.绑定本地信息
4.发送数据
5.接收并处理数据
6.打印数据
7.关闭套接字对象
1.导入socket模块
import socket
2.创建套接字对象
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
注意:
其中创建套接字时socket需要两个参数:socket(AddressFamily,Type)
AdressFamily: 可以选择AF_INET(用于Internet进程间的通信)或者AF_UNIX(同一台机器进程间通信),实际中常用AF_INET
Type: SOCK_STREAM(流式套接字,主要用于TCP协议)或者SOCK_DGRAM(数据报套接字,主要用UDP协议)。这里我们需要网络通信,且使用udp协议,所以两个参数为AF_INET,SOCK_DGRAM。
3.绑定本地信息
local_adress = ("",1314)
udp_socket.bind(local_adress)
使用bind()方法来绑定套接字对象的本地信息,需要一个元组参数,元组内含两个元素,第一个元素为字符串类型的ip地址,第二个元素为整型的端口号(自己设定,但一定要大于1023,即使用动态端口中的一个)。
4.发送数据
dest_ip = input("请输入对方IP:")
dest_port = int(input("请输入对方端口号:"))
send_data = input("请输入内容:")
udp_socket.sendto(send_data.encode("ASCII"),(dest_ip,dest_port))
套接字对对象调用sendto()方法来发送数据,sendto()方法需要两个参数。第一个参数为要发送的数据(注意需要转码);第二个是一个含有两个元素的元组类型的参数:第一个元素是目标主机的ip,第二个元素是目标主机的端口号。注意到dest_port 用到了int转换,是因为端口号必须是整型。
5.接收并处理数据
recv_data = udp_socket.recvfrom(1024)
recv_msg =recv_data[0].decode("ASCII")
recv_adress = recv_data[1]
使用recvfrom()方法来接收数据,接收到的数据是一个含有两个元素的元组类型:第一个是接收到的内容,第二个是一个元组(ip和端口)。此时的recv_data是一个元组类型,可以使用索引来分别读取其内部内容,也可以使用拆包的方法分别接收来自recvfrom()的数据,这里使用的第一种方法。除此之外,还需对接收到的内容进行解码。
6.打印数据
print("来自【%s】的数据:\n%s" % (str(recv_adress),recv_msg))
%s 是字符类型,而recv_adress 是元组类型,因此需要将其进行类型转换。
7.关闭套接字对象
udp_socket.close()
完整代码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Mar 15 20:38:15 2020
@author: x
"""
import socket
def sending_data(udp_socket):
dest_ip = input("请输入对方IP:")
dest_port = int(input("请输入对方端口号:"))
send_data = input("请输入内容:")
udp_socket.sendto(send_data.encode("ASCII"),(dest_ip,dest_port))
def recving_data(udp_socket):
recv_data = udp_socket.recvfrom(1024)
recv_msg =recv_data[0].decode("ASCII")
recv_adress = recv_data[1]
return recv_msg,recv_adress
def main():
# 1.创建套接字对象
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 2.绑定本地信息
local_adress = ("",1314)
udp_socket.bind(local_adress)
while True:
# 3.发送数据
sending_data(udp_socket)
# 4.接收并处理数据
recv_msg,recv_adress = recving_data(udp_socket)
# 5.打印数据
print("来自【%s】的数据:\n%s" % (str(recv_adress),recv_msg))
# 6. 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
关注我,带你玩转python!