Python中UDP和TCP编程

UDP和TCP区别:

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
  2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3. TCP面向字节流实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文, 且UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
  5. TCP首部开销20字节,开销较大;而UDP的首部开销小,只有8个字节。
  6. TCP的逻辑通信信道全双工的可靠信道,UDP则是不可靠信道

UDP编程

  • UDP属于无连接协议,在UDP编程是不需要首先建立连接,而是直接向接收方发送信息

UDP编程常用方法:

  • UDP编程常用到的socket模块方法有3个:
  • socket模块是对Socket模块进行了二次封装,支持Socket接口的访问,大幅度简化了程序开发步骤,提高开发效率。
  1. socket([family[,type[,proto]]]):
  • 创建一个Socket对象,即创建UDP套接字
  • 其中fimily为:
  • socket.AF_INTE 表示 IPV4
  • socket.AF_INTE6 表示 IPV6
  • type为:
  • SOCK_DGRAM 表示 UDP
  • SOCK_STREAM 表示 TCP
# 创建套接字,使用IPV4协议,使用UDP协议传输数据
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  1. sendto(date:bytes, address):
  • 用来发送数据
  • 把date指定的内容发送给address指定的地址
  • 其中address地址是包含接受方主机IP地址应用程序端口号的元组
  • 其中参数date的类型为bytes类型,不能为str类型,否则报错
  • 两种方式将str转换成bytes类型:
  1. string类型前面加一个英文字母b
  • 使用前提:参数string不能为一个变量名,而是字符串
  • 例如
sendto(==b=="你好啊", ("xxx.xxx.xxx.xxx", 8080))
  1. 调用encode()方法:
  • 将str类型转换成为bytes类型。
  • str类型变量名.encode(“utf-8”)或者"你好".encode(“utf-8”)
  • 例如
sendto(==str_msg.encode("utf-8")==, ("xxx.xxx.xxx.xxx", 8080))
  1. recv(bufsize[,flags]):
  • 从套接字接受数据
  • recv()返回接收到的数据
  • 参数bufsize表示本次接收的最大字节数
  • 参数若为1024,则表示1K
  • 参数若为1024*1024,则表示1M
  1. recvfrom(bufsize[,flags]):
  • 从套接字接受数据
  • recvfrom()返回的是(数据, 客户端地址),可以用来接收对端的地址信息,这个对于udp这种无连接的,可以很方便地进行回复
  • 参数bufsize表示本次接收的最大字节数
  • 参数若为1024,则表示1K
  • 参数若为1024*1024,则表示1M
# 1014表示本次接收的最大字节数
data, addr = udp_socket.recvfrom(1024)
  • 注意
  • recv和recvfrom是可以替换使用的。
  • 而换过来如果你在udp当中也使用recv,那么就不知道该回复给谁了。
  • 如果你不需要回复的话,也是可以使用的。
  • 另外就是对于tcp是已经知道对端的,就没必要每次接收还多收一个地址,没有意义,要取地址信息,在accept当中取得就可以加以记录了。
  1. bind(address)方法
  • 参数address为一个元组(‘本地IP’, 端口号)
  • 绑定本地信息
  • '本地IP参数’若为空字符串,表示本机任何可用IP
# 绑定本地信息(端口和端口号,空字符串表示本机任何可用IP地址)
udp_socket.bind(('',5000))
  1. gethostname()方法
  • 该方法可以用来获得当前的主机名
  • 返回值为str类型
import socket

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
name = socket.gethostname()
print(name)
print(type(name))

结果如下:
    LAPTOP-2B15DENO
    <class 'str'>
  1. gethostbyname(name)方法
  • 参数name为主机名(str类型)
  • 该方法可以获取IP地址
  • 返回值为str类型
import socket

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 获取主机名
name = socket.gethostname()
# 参数name表示本主机名
name_ip = socket.gethostbyname(name)

print(name)
print(type(name))
print(name_ip)
print(type(name_ip))

结果如下:
    LAPTOP-2B15DENO
    <class 'str'>
    192.168.43.120
    <class 'str'>

UDP发送数据:

  1. 创建套接字
  2. 发送数据
  3. 关闭
import socket

def main():
   # 创建套接字,使用IPV4协议,使用UDP协议传输数据
   udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
   
   # 输入发送者信息
   receiver_ip = input("请输入接受者IP:")
   receiver_port = int(input("请输入接受者端口号:"))
   
   # 发送数据
   while True:
       udp_msg = input("请输入您要发送的信息(输入exit退出):")
       if udp_msg == "exit":
           break
       udp_socket.sendto(udp_msg.encode("gbk"), (receiver_ip, receiver_port))
   
   # 关闭套接字
   udp_socket.close()
   
   
if __name__ == "__main__":
   main()

UDP接收数据

  1. 创建套接字
  2. 绑定本地信息(IP+端口号)
  3. 接收数据
  4. 关闭
import socket

def main():
    # 创建套接字,使用IPV4协议,使用UDP传输协议
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # 绑定本地接收信息(IP+端口号)
    name = socket.gethostname()
    print("本机名:" + name)
    ip_addr = socket.gethostbyname(name)
    print("本机IP" + ip_addr)
    receive_port = int(input("请输入接收数据的端口号:"))
    print("正在接收数据……")
    # 空字符串表示本机任何可用IP
    udp_socket.bind((ip_addr, receive_port))

    # 接收数据
    while True:
        # 2019表示本次接收数据的最大字节数
        udp_msg, udp_addr = udp_socket.recvfrom(2019)
        print("%s : %s" % (udp_addr, udp_msg.decode("gbk")))

    # 关闭套接字
    udp_socket.close()


if __name__ == "__main__":
main()

TCP编程

  • TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
  • 应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( MTU)的限制)。
  • 之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体 的TCP层。
  • TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收
  • 然后接收端实体对已成功收到的包发回一个相应的确认(ACK)
  • 如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传
  • TCP用一个校验函数检验数据是否有错误;在发送接收都要计算校验和

服务器端和客户端的区别:

  • 服务器端:就是提供服务的一方
  • 客服端:就是被服务的一方

TCP编程常用方法:

  1. socket([family[,type[,proto]]]):
  • 创建一个Socket对象,即创建UDP套接字
  • 其中fimily为:
  • socket.AF_INTE 表示 IPV4
  • socket.AF_INTE6 表示 IPV6
  • type为:
  • SOCK_DGRAM 表示 UDP
  • SOCK_STREAM 表示 TCP
# 创建套接字,使用IPV4协议,使用TCP协议传输数据
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  1. connect(address)方法:
  • 参数address为一个元组(‘本地IP’, 端口号)
  • 连接服务器
# 连接服务器
server_ip = input("请输入您要连接的服务器IP:")
server_port = int(input("请输入服务器port:"))
tcp_socket.connect((server_ip, server_port))

# 或者:
tcp_socket.connect(("192.168.43.120", 8080))
  1. send(data:bytes)方法:
  • 用来发送数据
  • 其中参数date的类型为bytes类型,不能为str类型,否则报错
tcp_msg = input("请输入您要发送的数据:")
tcp_socket.send(tcp_msg.encode("gbk"))
  1. recv(bufsize[,flags]):
  • 从套接字接受数据
  • recv()只返回接收到的数据
  • 参数bufsize表示本次接收的最大字节数。
  • 参数若为1024,则表示1K
  • 参数若为1024*1024,则表示1M
  1. bind(address)方法
  • 参数address为一个元组(‘本地IP’, 端口号)
  • 绑定本地信息
  • '本地IP参数’若为空字符串,表示本机任何可用IP
# 绑定本地信息(端口和端口号,空字符串表示本机任何可用IP地址)  
tcp_socket.bind(('',5000))
  1. listen(backlog)方法
  • backlog指定最多允许多少个客户连接到服务器
  • 它的值至少为1。
  • 收到连接请求后,这些请求需要排队。
  • 如果队列满,就拒绝请求
  • backlog应该理解为阻塞队列的长度,总共与服务器连接的客户端一共有 backlog + 1 个。
  • 阻塞队列FIFO,当连接客户端结束后阻塞队列里的第一个客服端与服务器连接成功。
  1. accept()方法
  • accept()接受一个客户端的连接请求
  • 并返回一个元组,里面包含一个新的套接字和链接则信息的元组(IP+端口号)
  • 返回值为(新的套接字,(IP, 端口号))
  • 新的套接字,不同于以上socket()返回的用于监听和接受客户端的连接请求的套接字;与此客户端通信是通过这个新的套接字发送和接收数据来完成的。

TCP客户端构建流程:

  1. 创建套接字
  2. 链接服务器
  3. 发送数据
  4. 关闭套接字
import socket

def main():
    # 创建套接字,使用IPV4协议,使用TCP传输协议
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 连接服务器
    server_ip = input("请输入您要连接的服务器IP:")
    server_port = int(input("请输入服务器port:"))
    tcp_socket.connect((server_ip, server_port))

    # 发送信息
    tcp_msg = input("请输入您要发送的数据:")
    tcp_socket.send(tcp_msg.encode("gbk"))

    # 接收数据:
    receive_msg = tcp_socket.recv(1024)
    print("接收到的数据为:" + receive_msg.decode("gbk"))

    # 关闭套接字
    tcp_socket.close()


if __name__ == "__main__":
    main()

TCP服务器端构建流程

  1. 创建套接字
  2. bind绑定本地信息
  3. listen让默认的套接字由主动变为可以被动连接
  4. accept等待客户端的链接
  5. recv/send接收、发送数据
import socket

def main():
    # 创建套接字,使用IPV4协议,使用TCP传输协议
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 绑定本地信息
    tcp_socket.bind(("192.168.43.120", 6666))

    # 让默认套接字由主动变成被动
    # 这里的参数表示服务器阻塞队列的长度!
    tcp_socket.listen(128)

    # 调用多次accept,从而为多个客户端服务
    while True:
        # 等待客户端的链接
        print("等待客户端链接……")
        new_tcp_socket, client_addr = tcp_socket.accept()
        print("客户端链接成功!")
        print("客户端信息:" + str(client_addr))

        # 循环带刺为同一个客户端服务多次
        while True:

            # 接收客户端发送的数据
            recv_msg = new_tcp_socket.recv(1024)
            print(recv_msg.decode("gbk"))

            '''
            如果recv解堵塞,那么有两种方式:
            1. 客户端发送过来了数据
            2. 客户端调用了close导致了 recv解堵塞
            '''
            if recv_msg:
                # 会送一部分数据给客服端
                new_tcp_socket.send("----ok----".encode("gbk"))
            else:
                break

        # 关闭新的套接字
        '''
        关闭accept()返回的套接字
        则意味着 为该客户端服务结束
        '''
        new_tcp_socket.close()

    # 关闭监听套接字
    '''
    如果将监听套接字关闭了
    那么会导致不能在等待新的客户端的到来
    即tcp_socket.accept()会出现异常
    '''
    tcp_socket.close()

if __name__ == "__main__":
    main()