用UDP来做一个聊天器

在做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!

猜你喜欢

转载自blog.csdn.net/qq_45807032/article/details/104937087