3. Socket编程

socket编程相关的模块是socket,所以在使用socket编程的时候,需要导入socket模块

import socket

3.1. socket编程概述

socket编程属于传输层编程,主要研究的是两台机器如何发送基本字符或者字节流信息 ,或者可以理解成,socket编程研究的是如何一个字节一个字节的把信息传输给对方, 而此时双方并不关心每一个字节具体的含义,反正我发过去就完了。
而一旦发送完毕,你发送的内容到底是啥意思,发送者的意图等,属于HTTP协议的范围,HTTP协议属于应用层协议,默认两台机器能顺利发送完整有序的字符信息。

一个不太恰当的例子,socket编程相当于快递公司,它只负责把货物安全按时发到制定人员 ,如果你发送的是一堆货物,则货物可能分不同的批次车次运输到对方,而具体如何运输, 发货方其实没必要知道。 但一旦发送到对方手中,货物的使用方式,作用和价值等等,属于 取决于收货方,跟快递公司无关,此处收货方发货收货双方可以认为是HTTP协议。

  • TCP/IP协议族

    • TCP(传输控制协议,TransmissionControlProtocal)

      • 基于链接的,每次传输数据都要建立链接

      • 建立链接需要消耗资源,带宽等,需要消耗时间

      • 传输数据安全,可靠,数据传输有序

    • UDP(用户数据报协议,UserDatagramProtocal)

      • 无连接

      • 安全性差,数据接受无顺序

      • 传输消耗带宽小,没有专门链接需要消耗资源

    • 可以把TCP理解成挂号信,我们的信用卡法律文书等重要文件,如果邮寄需要发送挂号信, 此时信件的邮寄过程中实时信息更新,可查可追溯,所以安全。

    • UDP则相当于普通信件,我们邮寄平信的时候,只要把信件交给快递员就好了,至于信件能不能送到,何时送到, 没有保障,也不能查看。

  • socket:套接字,在网络里边,特指通过ip地址和端口进行通信的一种机制

  • IP地址: 用来唯一标示网络中的设备地址,通过IP地址可以找到网络中任何一台设备

  • 端口:

    • 进行应用程序身份鉴定的一个数字,一台机器可能有很多程序,每台程序进行网络 通信都要有自己的特定的端口号

    • 理论上数值是0-65535之间

    • 一般1000以下不推荐开发者自行使用

3.2. UDP编程

在开始编程之前我们需要先看几个概念:

  • 无连接:
    UDP是没有链接的编程,即发送数据只要有IP和端口号就发送,至于对方能不能接受, 端口和IP是否正确不在考虑范围之内。

  • 客户端(Client) 和 服务器端(Server):
    我们以前的编程都是一个程序,因为我们需要模拟通信过程,也就是需要有 至少两方参与,我们习惯把发起通信的一方称为客户端(Client),把另一方 称为服务器端(Server)。
    在我们进行socket编程的时候,要想让程序跑起来,一般我们需要写两个程序, 然后分别运行。

  • Server端流程

    1. 建立socket,socket是负责具体通信的一个实例a

       # 建立socket,负责跟对方进行通信
       # socket初始化需要两个参数
       # 1. AF_INET:指定socket编程使用IPv4协议族
       # 2. SOCK_DGRAM:指定通信方式为UDP方式
       sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      
    2. 绑定,为创建的socket指派固定的端口和ip地址

       # 绑定ip和端口
       # 127.0.0.1 这个地址代表的是机器本身
       # 7777:端口,负责指定服务器上的对应的应用
       # 端口随机指定,一般大于1000小于65535
       # 所谓端口地址,是一个有字符串和整数组成的元组
       # 注意bind函数的参数只有一个tuple
       sock.bind(("127.0.0.1", 7777))
      
    3. 接受对方发送内容

       # 接收数据
       # 如果对方不发送或者还没发送数据,则服务器端只能等待
       # recvfrom接收的返回值是一个元组形式,前一个选项是数据,后一个选项是对方的地址
       # 参数的含义是缓冲区的大小
       data, addr = sock.recvfrom(500)
      
       # 发送的数据信息只能是bytes格式
       # 要想让信息显示出来,一般需要进行解码操作
       text = data.decode()
       print(type(text))
      
    4. 给对方发送反馈,此步骤为非必须步骤

       # 需要给client发送反馈信息,此步骤为非必须项
       rsp = "I have received your msg"
       # 发送的数据格式要求是一个字节串
       # 由字符串得到字节串需要进行编码操作
       data = rsp.encode()
       # 发送数据给对方
       sock.sendto(data, addr)
      
  • Client端流程

    1. 建立通信的socket实例

       sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      
    2. 发送内容到指定服务器

       text = "I love wangxiaojing"
       # 发送的数据只能是bytes格式
       # str格式数据要想转换成bytes格式需要编码
       data = text.encode()
       # 特别注意地址信息的格式
       sock.sendto(data, ("127.0.0.1", 7777))
      
    3. 接受服务器给定的反馈内容,如果期待对方回复的话。

       # 如果对方没有反馈,此函数会一直等待
       # data是一个bytes格式的内容
       data, addr = sock.recvfrom(1000)
      
       # bytes格式内容转换成字符串需要进行解码
       text = data.decode()
      
       print(text)
      

整个通信过程如下图所示: UDP通信过程

关于上述代码,有几个说明需要再提一下:

  • 源代码可以刘大拿个人博客下载

  • 源代码案例01是服务器,02是客户端

  • 启动的时候需要先启动服务端程序,这样客户端发送数据才不会报错

  • 传输的数据是bytes格式,发送端需要先编码,由str转换成bytes,接收端 需要接收后解码,由bytes格式转换成str

  • 原则上服务器程序要求永久运行,需要用死循环实现,具体实现参考源代码案例03

3.3. TCP编程

TCP编程是面向链接的传输,即每次传输之前需要先建立一个链接。
同样在编写程序的时候,客户端和服务器端两个程序需要编写。大致过程和方法 跟UDP编程相似,只不过:

  • 增加了传输的时候建立/关闭链接的过程

  • 建立socket实例的时候使用参数不同

  • 接收/发送函数名称不同

  • Server端的编写流程

    1. 建立socket负责具体通信,这个socket其实只负责接受对方的请求,真正通信的是链接后从新建立的socket

       # 1. 建立socket负责具体通信,这个socket其实只负责接受对方的请求,真正通信的是链接后从新建立的socket
       # 需要用到两个参数
       # AF_INET: 含义同udp一致
       # SOCK_STREAM: 表明是使用的tcp进行通信
       sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
    2. 绑定端口和地址

       # 2. 绑定端口和地址
       # 此地址信息是一个元祖类型内容,元祖分两部分,第一部分为字符串,代表ip,第二部分为端口,是一个整数,推荐大于10000
       addr = ("127.0.0.1", 8998)
       sock.bind(addr)
      
    3. 监听接入的访问socket,此过程可以理解成是等待客户端发起连接

       # 3. 监听接入的访问socket
       sock.listen()
      
    4. 接受访问的socket,可以理解接受访问即建立了一个通讯的链接通路

       # 4. 接受访问的socket,可以理解接受访问即建立了一个通讯的链接通路
       # accept返回的元祖第一个元素赋值给skt,第二个赋值给addr
       skt,addr = sock.accept()
      
    5. 接受对方的发送内容,利用接收到的socket接收内容,同UDP一样,发送的信息 只能是bytes格式,所以需要进行解码编码操作。

       # 5. 接受对方的发送内容,利用接收到的socket接收内容
       # 500代表接收使用的buffersize
       #msg = skt.receive(500)
       msg = skt.recv(500)
       # 接受到的是bytes格式内容
       # 想得到str格式的,需要进行解码
       msg = msg.decode()
      
    6. 如果有必要,给对方发送反馈信息。需要注意的是,此时的发送反馈不需要填写 客户端地址,因为发送反馈是按照已经建立好的链接发送的,即已经知道对方的地址。

       # 6. 如果有必要,给对方发送反馈信息
       skt.send(rst.encode())
      
    7. 关闭链接通路

       # 7. 关闭链接通路
       skt.close()
      
    8. 参看案例代码04

  • Client端流程

    1. 建立通信socket

       # 1. 建立通信socket
       sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
    2. 链接对方,请求跟对方建立通路。实际使用当中,需要确保连接建立 成功后才能发送数据。

       # 2. 链接对方,请求跟对方建立通路
       addr = ("127.0.0.1", 8998)
       sock.connect(addr)
      
    3. 发送内容到对方服务器

       # 3. 发送内容到对方服务器
       msg = "I love wangxiaojing"
       sock.send(msg.encode())
      
    4. 接受对方的反馈,当然前提是确保对方有反馈发回,否则 会无限期等待。

       # 4. 接受对方的反馈
       rst =  sock.recv(500)
       print(rst.decode())
      
    5. 关闭链接通路

       # 5. 关闭链接通路
       sock.close()
      
    6. 参看案例代码05

整个通信过程如下图所示: UDP通信过程

socket编程相对比较简单,在我们很多不太需要表示层参与的地方,比如物联网中,经常 对一些简单消息直接用socket发送即可,因为信息数量少,有时候可能并不是太重要,直接采用 UDP发送节省资源。 UDP编程还有一个常用的应用场景就是即时通信,即时通信需要长时间 高频炉发送数据,但相对来讲信息重要性相对较低,此时采用UDP比较合适。