• Python网络编程之Socket(套接字)


    一、Socket概念

    Socket套接字,一种独立于协议的网络编程接口,就是对网络中不同主机上的应用进程之间进行双向通信的端点。
    TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种端点我们叫做套接字(Socket)。

    IP层的IP地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用IP地址+协议+端口号唯一标示网络中的一个进程。能够唯一标示网络中的进程后,它们就可以利用socket进行通信了

    python中,我们利用Socket套接字来实现网络通信,可以说套接字是实现网络编程进行数据传输的一种技术手段。
    Socket用于描述IP地址和端口,应用程序通常通过’套接字’相网络发出请求或者应答网络请求。
    Socket主要是基于应用层和传输层之间,是一个中间的抽象层,功能是将复杂的TCP/IP协议族隐藏在Socket接口后面。
    应用程序通过套接字发送或接收数据,socket模块针对服务器端和客户端Socket进行像对文件一样的打开、读写和关闭等操作。
    套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

    在这里插入图片描述

    二、套接字的发展史及分类

    套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。

    • 基于文件类型的套接字家族:
      套接字家族的名字:AF_UNIX

      unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

    • 基于网络类型的套接字家族:
      套接字家族的名字:AF_INET

      (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

    三、Socket的使用

    通过指定地址簇和socket类型来进行创建。

    地址簇描述
    Socket.AF_UNIX只能够用于单一的Unix系统进程间通信
    Socket.AF_INET服务器之间网络通信IPv4
    Socket.AF_INET6IPv6
    Socket类型描述
    Socket.SOCK_STREAM流式socket,for TCP
    Socket.SOCK_DGRAM数据报式socket,for UDP
    Socket.SOCK_RAW原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。

    服务器与客户端交互过程:
    在这里插入图片描述

    语法格式(基于TCP协议)

    									'''服务端'''
    
    import socket
    '''基于TCP协议的套接字(Socket)编程'''
    '''以后养成一个查看源码编写代码的思路!!!!'''
    
    '''服务端比客户端先运行,服务端先运行然后等待客户端连接'''
    
    # 1.产生一个socket对象并指定采用的通信版本和协议(括号内不写默认就是TCP协议)
    server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    '''
    AF_INET, type=SOCK_STREAM   AF_INET, type=SOCK_DGRAM
    基于TCP协议                  基于UDP协议
    '''
    
    # 2.绑定IP地址(ip)和端口号(port)(需要用元组的形式)
    '''def bind(self, __address: _Address | bytes) -> None: ...'''
    # 绑定的IP地址可以省略,代表绑定本机,第一个参数为ip地址,第二个参数为端口号
    server.bind(('127.0.0.1', 8000))
    '''127.0.0.1为本地回环地址,只有自己电脑可以访问'''
    
    # 3.服务器做监听,也称为半连接池(需要用int类型,服务器能够同时等待客户端的数量)(排队数量不包含正在执行的)
    '''    def listen(self, __backlog: int = ...) -> None: ...'''
    # listen有一个参数,是一个数值,参数越大,以后可以连接的客户端越多,参数越小链接的客户端越小
    server.listen(3)
    print('form Listen')
    
    
    # 4.等待接收客户端的连接请求
    '三次握手'
    sock, addr = server.accept()  # return sock,addr
    # 运行到这里会等待客户端发送信息才继续下面的运行
    '''sock就是双向通道,addr就是客户端地址'''
    print('等待完成!')
    
    # 5.接收客户端发送过来的消息,(接收到的类型是bytes类型,二进制的)
    '''def recv(self, __bufsize: int, __flags: int = ...) -> bytes: ...'''
    data = sock.recv(1024)  # 括号内代表的是一次接收最多1024个字节
    '''recv()接收     send()发送'''
    print('接收到了客户端的数据:',data)
    
    # 6.给客户端发送消息,注意消息必须是bytes类型的
    sock.send(data.upper())  # 我这里就只是给客户端发送的消息转成大写
    
    # 7.关闭双向通道
    '四次挥手'
    sock.close()
    
    # 8.关闭服务器
    server.close()
    
    
    print('==============================')
    
    									'''客户端'''
    import socket
    '''运行程序时一定要确保先运行了服务器,之后才是客户端'''
    # 1.生成socket对象指定类型和协议
    client1 = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    
    # 2.通过IP地址和端口号连接服务端
    '''def connect(self, __address: _Address | bytes) -> None: ...'''
    client1.connect(('127.0.0.1', 8000))
    
    # 3.直接给服务端发送消息
    client1.send('hello world'.encode('utf-8'))
    
    # 4.接收服务端发送过来的消息
    data = client1.recv(1024)
    print('收到服务端发来的消息:', data.decode('utf-8'))
    
    # 5.断开与服务端的链接
    client1.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    1.基于TCP协议的套接字(socket)编程

    TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

    服务端:

    	import socket
    
    # 生成一个socket对象,设定类型和协议
    server1 = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    
    # 为socket设置IP地址,端口号
    server1.bind(('127.0.0.1', 8080))
    
    # 给服务器添加同时监听几个客户端,
    server1.listen(3)
    
    while True:
        # 等待客户端发来消息
        sock, addr = server1.accept()
        # sock双向通道,addr客户端地址
        while True:
            try:
                # 避免用户断开后,直接报错,自动捕获异常
                # 接收客户端发来的消息 二进制
                data = sock.recv(2048)
                print('接收到了客户端发来的消息:', data.decode('utf-8'))
                # 给客户端回复消息
                mag = input('请输入回复的消息>>>:').strip()
                sock.send(mag.encode('utf-8'))
    
            except Exception as f:
                print(f)
                break
    
    # 关闭双向通道
    sock.close()
    
    # 断开服务器链接
    server1.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    客户端:

    	import socket
    
    # 生成一个socket对象,设置类型和协议
    client4 = socket.socket(family = socket.AF_INET,type=socket.SOCK_STREAM)
    
    # 连接到服务器
    client4.connect(('127.0.0.1',8080))
    
    while True:
        # 向服务器发送消息
        mag = input('请输入发送的消息>>>:').strip()
    
    	if len(mag) == 0:
    		print('请输入有效信息!')
    		continue
    
        if mag == 'yes':
            print('已断开与服务器连接!')
            break
    
        client4.send(mag.encode('utf-8'))  # 转码
    
        # 接收服务器回应的消息 字节
        data = client4.recv(2048)
        print('接收到了服务器发来的消息:',data.decode('utf-8'))  # 解码
    
        print('是否不在回复,断开连接?,请输入yes即可!')
    
    # 断开连接
    client4.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    有时候我们在重启服务端时,会出现端口被占用的情况。这是因为虽然连接已经关闭,但是端口还没来得及释放。
    在这里插入图片描述
    两种解决方案:
    * 更换端口。
    * bind绑定IP之前添加一个参数,来达到端口复用的目的。

    import os
    
    server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
    
    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    
    # 绑定ip地址和端口,127.0.0.1代表回环地址,只能当前计算机访问
    server.bind(('127.0.0.1',8080))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    半连接池
    	半连接池server.listen(5)
    	意思就是同时可以运行五个客户端 如果多于五个则会报错
    	需要等待前面五个客户端其中一个结束 则会有多余的位置给到第六个
    
    • 1
    • 2
    • 3

    2.基于UDP协议的套接字(socket)编程

    UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

    udp是无连接的,所以发送数据前不需要先建立连接。
    我们可以使用多个客户端与服务端进行数据发送,服务端可以逐个回复
    udp发送数据采用的是数据报模式,数据会一次性全部发过去,如果未接收到也不会保存,安全性没有保障,但传输速率较快。

    服务端:

    	import socket
    '''基于UDP协议的套接字(socket)编程'''
    
    # 生成一个socket对象 设置类型和协议
    server = socket.socket(family = socket.AF_INET,type = socket.SOCK_DGRAM)
    '''这里因为要设置成UDP协议,所以type = socket.SOCK_DGRAM'''
    
    # 设置服务器IP地址及端口号
    server.bind(('127.0.0.1',8848))
    
    while True:
        # 接受客户端发送来的信息,以及发送者的ip以及端口(UDP不需要建立连接)
        data,addr = server.recvfrom(2048)
        print(f"收到客户端{addr}发来的消息:",data.decode('utf-8'))
    
        # 向发送者回复信息
        mag = input('请输入回复的消息>>>>:').strip()
        server.sendto(mag.encode('utf-8'), addr)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    客户端:

    	import socket
    
    # 生成一个socket对象 设置类型和协议
    client = socket.socket(family = socket.AF_INET,type = socket.SOCK_DGRAM)
    
    server_ip = ('127.0.0.1',8848)  # 写好的ip地址和端口
    # 无需和服务端建立连接
    
    while True:
        mag = input('请输入发送的消息>>>:').strip()
        # 无需判断是否为空,因为UDP每次发送都不只是单纯的数据,还有ip和端口信息
    
        # 直接向写好的ip和端口发送数据
        client.sendto(mag.encode('utf-8'),server_ip)
    
        # 接收它的返回值
        data,addr = client.recvfrom(2048)
        print(f"接收到了{addr}的消息:",data.decode('utf-8'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    也可以使用服务端只接收客户端消息

    服务端:

    	import socket
    server = socket.socket(family = socket.AF_INET,type = socket.SOCK_DGRAM)
    
    server.bind(('127.0.0.1',8888))
    
    # 无需建立连接
    
    while True:
        data,addr = server.recvfrom(2048)
        print(f"接收到了发送者{addr}发来的消息:",data.decode('utf-8'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    客户端:

    	import socket
    
    client = socket.socket(family = socket.AF_INET,type = socket.SOCK_DGRAM)
    
    # 无需建立连接
    
    while True:
        # 直接给对应的ip地址和端口号发送信息
        mag = input('请输入发送的消息>>>:').strip()
        client.sendto(mag.encode('utf-8'),('127.0.0.1',8888))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    黏包现象

    	1.服务端连续执行三次recv
    	2.客户端连续执行三次send
    	问题:服务端一次性接收到了客户端三次的消息 该现象称为"黏包现象"
      
    黏包现象产生的原因
    	1.不知道每次的数据到底多大
    	2.TCP也称为流式协议:数据像水流一样绵绵不绝没有间隔(TCP会针对数据量较小且发送间隔较短的
    	多条数据一次性合并打包发送)
     
    避免黏包现象的核心思路\关键点
    		如何明确即将接收的数据具体有多大
    	
    ps:如何将长度变化的数据全部制作成固定长度的数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    Day 62 单向循环链表 双链表
    在九天服务器平台上使用自己上传的数据集文件
    【考研】操作系统——处理机调度算法1
    数据结构与算法知识点总结(3)树、图与并查集
    【模型提分tricks】Adversarial Weight Perturbation(AWP)对抗训练
    01背包 P1507 NASA的食物计划
    MySQL:引擎知识点盘点
    Linux服务器上下载安装Nginx
    21、嵌套路由实战操作
    Enzo丨Enzo SAVIEW PLUS AP试剂解决方案
  • 原文地址:https://blog.csdn.net/achen_m/article/details/133859994