1、IP/TCP/UDP简介

1.1、IP协议

互联网上每个计算机的唯一标识就是 IP 地址。IP 地址实际上是一个32位整数(称为IPv4),它是以字符串表示的 IP 地址,如:172.16.254.1,实际上是把32位整数按8位分组后得到的。如 图1.1.1所示:

                                                                                                 图1.1.1 IPv4示例

IP 协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块。类似于将一个大包裹拆分成几个小包裹,然后通过 IP 包发送出去。

IP 包的特点是按块发送,途径多个路由,但不保证都能到达,也不能保证顺序到达。

1.2、TCP协议

TCP 协议是建立在 IP 协议之上的。TCP 协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP 协议会通过3次握手建立可靠连接。如 图1.2所示:

   图 1.2.1 TCP 三次握手

传输时需要对每个 IP 包进行编号,确保对方按顺序收到,如果包丢掉了,就自动重发。如 图1.2.2所示:

图 1.2.2 传输数据包

一个 TCP 报文除了要包含传输的数据,还要包含源 IP 地址和目标 IP 地址、源端口和目标端口。每个网络程序都向操作系统申请唯一的端口号,这样两个进程在两台计算机之间建立网络连接就需要各自的 IP 地址和各自的端口号。

一个进程也可能同时与多个计算机建立连接,因此他会申请很多端口。端口号不是随意使用的,二十按照一定的规矩进行分配。例如:80端口分配给HTTP服务,21端口分配给FTP服务。

1.3、UDP协议

UDP 协议时面向无连接的协议。使用 UDP 协议时,不需要建立连接,子需要自动对方的 IP 地址和端口号,就可以直接发送数据包。但是无法保证数据一定到达。

虽然用 UDP 传输数据不可靠,但是它的有点是比 TCP 协议熟读快。对于不要求可靠到达的数据而言,就可以使用 UDP 协议。

TCP 协议和 UDP 协议的区别,如 图1.3.1所示:

 图1.3.1 TCP协议和UDP协议的区别

2、socket简介

为了让两个程序通过网络进行通信,二者均必须使用 Socket 套接字。Socket 的英文原义是“孔”或“插座“,通常业称作”套接字“,用于描述 IP 地址和端口,它是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。如 图2.1所示: 

 图2.1 使用Socket实现通信 

3、关键函数

s = socket.socket(AddressFamily,Type)

 AddressFamily:可以选择 AF_INET(用于Internet进程间通信)或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用 AF_INET

Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或则SOCK_DGRAM(数据包套接字,主要用于 UDP 协议)

# 创建TCP/IP套接字
tcpSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 创建UDP/IP套接字
udpSocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

 socket 对象的内置方法:

    函数                                      描述
s.bind()     绑定地址(host,port)到套接字,在 AF_INET 下,以元组(host,port)的形式表示地址
s.listen()开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分要用程序设为5即可i
s.accept()被动接受 TCP 客户端连接(阻塞式),等待连接的到来
s.connect() 主动初始化 TCP 服务器连接,一般 address 的格式为元组(host,port),如果连接出错,返回 socket.error 错误
s.recv() 接受 TCP 数据,数据以字符串 形式返回,bufsize 指定要接收的最大数据量,flag 提供有关消息的其他信息,通常可以忽略
s.send()发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小
s.sendall()完整发送 TCP 数据,将 string 中的数据发送到 连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常
s.recvfrom()接收 UDP 数据,与 recv() 类似,单返回值是(data,address)。其中 data 是包含接收数据的字符串,addrress 是发送数据的套接字
s.sendto()发送 UDP 数据,将数据发送到套接字,address 是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数
s.close()关闭套接字

4、发送文字

4.1、单次发送接收

客户端:

def SingleSendText():
    """
    单次发送接收文字
    """
    # host = socket.gethostname()  # 获取主机地址  socket.gethostname()
    host = "192.168.10.1"  # 设置IP
    port = 6666  # 设置端口号
    tcpclient = socket.socket()  # 创建TCP/IP套接字
    tcpclient.connect((host, port))  # 主动初始化TCP服务器连接
    print("已连接服务端")
    send_data = input("请输入要发送的内容:")
    tcpclient.send(send_data.encode())  # 发送TCP数据
    info = tcpclient.recv(1024).decode()
    print("接收到的内容:", info)
    tcpclient.close()

服务端:

def SingleReceiveText():
    """
    单次接收发送文字
    """
    # host = socket.gethostname()  # 获取主机地址  socket.gethostname()
    host = "192.168.10.1"  # 设置IP
    port = 6666  # 设置端口号
    tcpserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建TCP/IP套接字
    tcpserver.bind((host, port))  # 绑定地址(host, port)到套接字
    tcpserver.listen(5)  # 设置最多连接数量
    print("等待客户端连接...")
    tcpclient, addr = tcpserver.accept()  # 被动接收TCP客户端连接
    print("客户端已经连接")
    info = tcpclient.recv(1024).decode()  # 接收客户端数据
    print("接收到的内容:", info)
    send_data = input("请输入要发送的内容:")
    tcpclient.send(send_data.encode())  # 发送TCP数据
    tcpclient.close()
    tcpserver.close()

效果:

客户端          服务端

4..2、循环发送接收

客户端:

def CycleSendText():
    """
    循环发送接收文字
    """
    # host = socket.gethostname()  # 获取主机地址  socket.gethostname()
    host = "192.168.10.1"  # 设置IP
    port = 6666  # 设置端口号
    tcpclient = socket.socket()  # 创建TCP/IP套接字
    tcpclient.connect((host, port))  # 主动初始化TCP服务器连接
    print("已连接服务端")
    while True:   # 判断是否退出
        send_data = input("请输入要发送的内容:")
        tcpclient.send(send_data.encode())  # 发送TCP数据
        if send_data == "byebye":
            break
        info = tcpclient.recv(1024).decode()
        if info == "byebye":
            break
        else:
            print("接收到的内容:", info)
    tcpclient.close()

服务器:

def ReceiveTextCircularly():
    """
    循环接收发送文字
    """
    # host = socket.gethostname()  # 获取主机地址  socket.gethostname()
    host = "192.168.10.1"  # 设置IP
    port = 6666  # 设置端口号
    tcpserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建TCP/IP套接字
    tcpserver.bind((host, port))  # 绑定地址(host, port)到套接字
    tcpserver.listen(5)  # 设置最多连接数量
    print("等待客户端连接...")
    tcpclient, addr = tcpserver.accept()  # 被动接收TCP客户端连接
    print("客户端已经连接")
    while True:   # 判断是否退出
        info = tcpclient.recv(1024).decode()  # 接收客户端数据
        if info == "byebye":
            break
        print("接收到的内容:", info)
        send_data = input("请输入要发送的内容:")
        tcpclient.send(send_data.encode())  # 发送TCP数据
        if send_data == "byebye":
            break
    tcpclient.close()
    tcpserver.close()

效果:

             

5、发送图片

5.1、单次发送接收

客户端:

def SingleSendPicture():
    """
    单次发送图片
    """
    host = "192.168.10.1"  # 设置IP
    port = 6666  # 设置端口号
    tcpclient = socket.socket()  # 创建TCP/IP套接字
    tcpclient.connect((host, port))  # 主动初始化TCP服务器连接
    print("已连接服务端")
    imgPath = "people.jpg"
    sdata = picture2base(imgPath)
    print(f"开始发送图片 {imgPath}")
    tcpclient.send(sdata.encode())
    tcpclient.close()
    print("发送完成")

 服务端:

def SingleReceivePicture():
    """
    单次接收图片
    """
    host = "192.168.10.1"  # 设置IP
    port = 6666  # 设置端口号
    tcpserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建TCP/IP套接字
    tcpserver.bind((host, port))  # 绑定地址(host, port)到套接字
    tcpserver.listen(5)  # 设置最多连接数量
    print("等待客户端连接...")
    tcpclient, addr = tcpserver.accept()  # 被动接收TCP客户端连接
    print("客户端已经连接")
    print("开始接收")
    base64_data = ""
    while True:
        rdata = tcpclient.recv(1024)
        base64_data += str(rdata, 'utf-8')
        if not rdata:
            break
    base2picture(base64_data)
    tcpclient.close()
    tcpserver.close()
    print("接收完成")

5.2、循环接收

客户端:

def CycleSendPictures():
    """
    发送图片
    """
    host = "192.168.10.1" 
    port = 6666
    tcpclient = socket.socket() 
    tcpclient.connect((host, port)) 
    print("已连接服务端")
    imgPath = "people.jpg"
    sdata = picture2base(imgPath)
    print(f"开始发送图片 {imgPath}")
    tcpclient.send(sdata.encode())
    tcpclient.close()
    print("发送完成")

服务端:

def ReceivePicturesCircularly():
    """
    循环接收图片
    """
    host = "192.168.10.1"
    port = 6666
    tcpserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcpserver.bind((host, port))
    tcpserver.listen(5)
    print("等待客户端连接...")

    while True:
        tcpclient, addr = tcpserver.accept()
        print("-"*5 + "开始接收" + "-"*5)
        base64_data = ""
        while True:
            rdata = tcpclient.recv(1024)
            base64_data += str(rdata, 'utf-8')
            if not rdata:
                break
        base2picture(base64_data)
        tcpclient.close()
        print("-"*5 + "接收完成" + "-"*5)

    tcpserver.close()

6、发送文件

客户端:

def SendFile():
    """发送文件"""
    host = "192.168.10.1"
    port = 6666
    tcpclient = socket.socket()
    try:
        tcpclient.connect((host, port))
        print('服务器已连接')
    except:
        print('服务器连接失败,请修改后重新运行!!')
        exit(0)

    while True:
        print("-" * 5 + "开始发送" + "-" * 5)
        filename = "qq.txt"
        print(f"发送的文件为:{filename}")
        with open(filename, "r", encoding="utf-8") as f:
            rdata = f.read()
        tcpclient.send("qq.txt".encode("utf-8"))
        if tcpclient.recv(1024).decode("utf-8") == "ok":
            while True:
                tcpclient.send(rdata.encode('utf-8'))
                break
        print("-" * 5 + "发送完成" + "-" * 5)
        break

    tcpclient.close()

服务端:

def ReceivingFile():
    """
    接收文件
    """
    host = "192.168.10.1"
    port = 6666
    tcpserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcpserver.bind((host, port))
    tcpserver.listen(5)
    print("等待客户端连接...")

    while True:
        tcpclient, addr = tcpserver.accept()
        print('client addr:', addr)
        while True:
            print("-" * 5 + "开始接收" + "-" * 5)
            fileNameData = tcpclient.recv(1024)  # 接收文件名字
            filename = fileNameData.decode('utf-8')
            new_filename = "new_" + filename
            print(f"文件保存为:{new_filename}")
            if os.path.exists(new_filename):
                os.remove(new_filename)
            new_file = open(new_filename, "a", encoding="utf-8")
            tcpclient.send("ok".encode("utf-8"))
            while True:
                rdata = tcpclient.recv(1024)  # 接收文件内容
                if not rdata:
                    break
                new_file.write(rdata.decode("utf-8"))
            new_file.close()
            print("-" * 5 + "接收完成" + "-" * 5)
            break
        tcpclient.close()
        # break  # 注释则可以循环接收
    tcpserver.close()

7、源码获取

Socket通信(文字、图片、文件)

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐