Python全栈(四)高级编程技巧之5.Socket编程-基本概念、UDP发送与接收数据 - Go语言中文社区

Python全栈(四)高级编程技巧之5.Socket编程-基本概念、UDP发送与接收数据



Socket编程又称为网络编程。

一、IP地址介绍和分类

1.IP介绍

目的:
用来标记网络上的一台电脑或其他设备。
IP传输数据(局域网)的流程如下:
数据传输流程
查看IP地址:

  • Windows:
ipconfig

打印

Windows IP 配置


无线局域网适配器 本地连接* 1:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :

无线局域网适配器 本地连接* 12:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :

以太网适配器 VMware Network Adapter VMnet1:

   连接特定的 DNS 后缀 . . . . . . . :
   本地链接 IPv6 地址. . . . . . . . : fe80::694f:1a98:xxxx:xxxx%x
   自动配置 IPv4 地址  . . . . . . . : 169.254.xxx.xxx
   子网掩码  . . . . . . . . . . . . : 255.255.0.0
   默认网关. . . . . . . . . . . . . :

以太网适配器 VMware Network Adapter VMnet8:

   连接特定的 DNS 后缀 . . . . . . . :
   本地链接 IPv6 地址. . . . . . . . : fe80::d146:2496:xxxx:xxxx%xx
   自动配置 IPv4 地址  . . . . . . . : 169.254.xxx.xxx
   子网掩码  . . . . . . . . . . . . : 255.255.0.0
   默认网关. . . . . . . . . . . . . :

无线局域网适配器 WLAN:

   连接特定的 DNS 后缀 . . . . . . . :
   本地链接 IPv6 地址. . . . . . . . : fe80::f55a:3b0f:xxx:xxx%xx
   IPv4 地址 . . . . . . . . . . . . : 192.168.xxx.xxx
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 192.168.0.1

其中,
无线局域网适配器是网卡;
无线局域网适配器 WLAN是本机IP(内网IP),与外网IP如222.215.xxx.xxx不一致;
以太网适配器 VMware Network Adapter VMnet是虚拟机网卡。

  • linux(以Ubuntu为例):
ifconfig

打印

ens33: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether 00:0c:29:69:xx:xx  txqueuelen 1000  (以太网)
        RX packets 6  bytes 552 (552.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens34: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether 00:0c:29:69:xx:xx  txqueuelen 1000  (以太网)
        RX packets 6  bytes 552 (552.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (本地环回)
        RX packets 343  bytes 25689 (25.6 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 343  bytes 25689 (25.6 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

在Ubuntu中关闭网卡的命令是

ifconfig ens33 down

开启的命令是

ifconfig ens33 up

2.IP地址分类

(1)根据版本分类
如图
IP根据版本分类
IPv4有256256256*256 = 232 = 4294967296个不同可能,现已耗尽;
IPv6有2128个不同可能。
(2)根据根据网络号和主机号分类
举例:
192.168.0.161中,
C类:192.168.0作为网络号时,相同表示在同一网络中,161是主机号,范围是0-255,同一网络号下的主机号是唯一的;
B类:192.168作为网络号;
A类:192作为网络号。
如图
IP地址分类

二、端口介绍

1.端口简介

端口是为了识别不同的应用程序而分配给不同的应用的。
举例演示QQ、微信发消息的过程如下
Port

2.端口分类

  • 知名端口:
    范围是从0到1023。
    • 80端口分配给HTTP服务
    • 21端口分配给FTP服务
  • 动态端口:
    范围是从1024-65535。

三、Socket的简单使用

1.TCP/IP协议

TCP/IP协议是Transmission Control Protocol/Internet Protocol的简写,即传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。
TCP/IP定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
TCP/IP网络模型四层模型从根本上和OSI七层网络模型是一样的,只是合并了其中的某些层:
TCP/IP网络模型四层模型

2.Socket

socket又称套接字,应用程序通常通过套接字向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
白话说,socket就是两个节点为了互相通信,而在各自家里装的一部电话。
TCP/IP各层之间传输数据的过程:
如图
TCP/IP各层之间传输数据
增加Socket(在应用层和传输层之间)之后传输数据的过程:
如图
Socket传输数据
Python中,socket模块的源码:

def __init__(self, family=-1, type=-1, proto=-1, fileno=None):
    # For user code address family and type values are IntEnum members, but
    # for the underlying _socket.socket they're just integers. The
    # constructor of _socket.socket converts the given argument to an
    # integer automatically.

参数中
family是协议族,默认AF_INET是IPv4,AF_INET6是IPv6;
type是套接字类型,包括TCP和UDP两种,默认SOCK_STREAM是流式套接字,属于TCP,SOCK_DGRAM是数据报套接字,属于UDP。

socket的简单使用:

socket的使用过程:

  • 创建套接字
  • 使用套接字收/发数据
  • 关闭套接字
import socket

#创建套接字
s = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
#使用套接字收发数据
pass
#关闭套接字
s.close()

过程可类比文件处理。

四、UDP发送与接收

1.UDP发送数据

import socket

def main():
    #创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #发送数据
    udp_socket.sendto('hello',('192.168.0.100',8080))
    #关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

打印

Traceback (most recent call last):
  File "xxx/demo.py", line 21, in <module>
    main()
  File "xxx/demo.py", line 16, in main
    udp_socket.sendto('hello',('192.168.0.161',8080))
TypeError: a bytes-like object is required, not 'str'

报错,sendto()函数参数需要字节型数据,需要对原代码进行改动:

import socket

def main():
    #创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #发送数据
    udp_socket.sendto(b'hello',('192.168.0.100',8080))
    #关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

此时无打印输出,需要用可视化工具对发送的数据进行显示,这里选择NetAssist网络调试助手,可点击https://download.csdn.net/download/CUFEECR/12131323下载。
NetAssist进行配置(协议类型、主机地址和端口号与代码中一致)
NetAssist配置
点击打开按钮,运行代码,即可看到
结果1
代码中参数更改后,要在NetAssist中同步

import socket

def main():
    #创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #发送数据
    udp_socket.sendto(b'hello world',('127.0.0.1',8080))
    udp_socket.sendto(b'hello Corley', ('127.0.0.1', 8080))
    #关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

显示
结果2
但是发生内容只能是ASCII字符,否则会报错

import socket

def main():
    #创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #发送数据
    udp_socket.sendto(b'你好',('localhost',8080))
    #关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

打印

  File "xxx/demo.py", line 16
    udp_socket.sendto(b'你好',('localhost',8080))
                     ^
SyntaxError: bytes can only contain ASCII literal characters.

需要进行编码

import socket

def main():
    #创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #发送数据
    send_data = '你好'
    udp_socket.sendto(send_data.encode('gbk'),('localhost',8080))
    #关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

显示
结果3
代码进行优化–不间断发送

import socket

def main():
    # 创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 循环发送数据
    while True:
        send_data = input('Please enter data to send:')
        if send_data == 'exit':
            break
        send_data = send_data.encode('gbk')
        udp_socket.sendto(send_data,('localhost',8080))
    # 关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

显示
结果4
发送数据时还可以绑定端口,使发送数据的端口保持不变。

import socket

def main():
    # 创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 绑定本地信息,不绑定则随机分配
    bind_addr = ('', 7890)  # IP字符串为空时代表本机地址中的任何一个
    udp_socket.bind(bind_addr)
    # 循环发送数据
    while True:
        send_data = input('Please enter data to send:')
        if send_data == 'exit':
            break
        send_data = send_data.encode('gbk')
        udp_socket.sendto(send_data,('192.168.0.100',8080))
    # 关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

结果如下
发送指定端口
端口为指定端口。

2.UDP接收数据

UDP接收数据的过程:

  • 创建套接字
  • 绑定本地信息(IP和端口)
  • 接收数据
  • 显示或处理数据
  • 关闭套接字

发送数据前,需要对NetAssist进行配置如下
NetAssist发送配置
接收数据尝试

import socket

def main():
    # 创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #接收数据
    recv_data = udp_socket.recvfrom(1024) #参数1024表示接收的最大字节数
    print(recv_data)
    # 关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

打印

Traceback (most recent call last):
  File "xxx/demo.py", line 23, in <module>
    main()
  File "xxx/demo.py", line 17, in main
    recv_data = udp_socket.recvfrom(1024) #参数1024表示接收的最大字节数
OSError: [WinError 10022] 提供了一个无效的参数。

报错,需要绑定本地信息。
端口绑定问题:
如果程序运行时,没有绑定端口,那么操作系统会自动分配一个端口给程序;
而且同一端口,不能用两次。

import socket

def main():
    # 创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #绑定本地信息
    bind_addr = ('',7789) #IP字符串为空时代表本机地址中的任何一个
    udp_socket.bind(bind_addr)
    #接收数据
    recv_data = udp_socket.recvfrom(1024) #参数1024表示接收的最大字节数
    print(recv_data)
    # 关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

运行,然后在NetAssist中发送数据,可得

(b'1', ('192.168.0.100', 8080))

易知,接收到的数据为元组形式,其中,
第一个值为接收到的数据;
第二个值为(发送方的IP,端口)。
进一步优化代码–实时接收

import socket

def main():
    # 创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #绑定本地信息
    bind_addr = ('',7789) #IP字符串为空时代表本机地址中的任何一个
    udp_socket.bind(bind_addr)
    while True:
        #接收数据
        recv_data = udp_socket.recvfrom(1024) #参数1024表示接收的最大字节数
        data = recv_data[0]
        send_addr = recv_data[1]
        print('%s:%s'%(str(send_addr),data.decode('gbk')))
    # 关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

显示
结果6
如果recvfrom()的参数指定过小,超过发送的数据的大小,则会报错

import socket

def main():
    # 创建UDP套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #接收数据
    recv_data = udp_socket.recvfrom(1024) #参数1024表示接收的最大字节数
    print(recv_data)
    # 关闭套接字
    udp_socket.close()

if __name__ == '__main__':
    main()

打印

Traceback (most recent call last):
  File "xxx/demo.py", line 46, in <module>
    main()
  File "xxx/demo.py", line 40, in main
    recv_data = udp_socket.recvfrom(10) #参数10表示接收的最大字节数
OSError: [WinError 10040] 一个在数据报套接字上发送的消息大于内部消息缓冲区或其他一些网络限制,或该用户用于接收数据报的缓冲区比数据报小。

3.应用–生成UDP聊天器

步骤:

  • 创建套接字 套接字是可以同时收发数据的
  • 发送数据
  • 接收数据
import socket


def send_data(udp_socket):
    '''发送数据'''
    send_data = input('Please enter data to send:')
    send_data = send_data.encode('gbk')
    dest_ip = input('Please enter destination IP:')
    dest_port = int(input('Please enter destination PORT:'))
    udp_socket.sendto(send_data,(dest_ip,dest_port))

def recv_data(udp_socket):
    '''接收数据'''
    recv_data = udp_socket.recvfrom(1024)
    data = recv_data[0]
    send_addr = recv_data[1]
    print('%s:%s' % (str(send_addr), data.decode('gbk')))

def main():
    # 创建UDP套接字,同时接收、发送数据
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 绑定本地信息
    bind_addr = ('', 7788)  # IP字符串为空时代表本机地址中的任何一个
    udp_socket.bind(bind_addr)
    # 循环发送、接收数据
    while True:
        #发送
        send_data(udp_socket)
        #接收
        recv_data(udp_socket)
    # 关闭套接字
    udp_socket.close()


if __name__ == '__main__':
    main()

显示
结果7
提示:
此时要关闭NetAssist,否则可能会因占用端口而发生程序不能正常向下执行的情况。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/CUFEECR/article/details/104147766
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢