网络传输方式

  • 面向无连接两台计算机通信的时候,不需要建立连接(逻辑)就可以进行数据的收发,数据可能会丢
    • 传输协议: UDP
  • 面向有连接两台计算机通信的时候,需要先建立连接,再能通信
    • 传输协议:TCP

Python 提供了两个级别访问的网络服务:

  • 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统 Socket 接口的全部方法。
  • 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。

    什么是 Socket?

    Socket又称”套接字”,应用程序通常通过”套接字”向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

    socket()函数

    Python 中,我们用 socket()函数来创建套接字,语法格式如下:

    1. socket.socket([family[, type[, proto]]])

    参数

  • family: 套接字家族可以使 AF_UNIX 或者 AF_INET(使用IPv4) AF_INET6 使用IPv6。

  • type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREA(使用TCP的传输方式(有连接))M 或 SOCK_DGRAM(使用UDP的传输方式(无连接)。
  • protocol: 一般不填默认为 0。

    Socket 对象(内建)方法

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

UDP网络程序

image.png
s.sendto()

1.发送数据

"""
1、导入模块
2、创建套接字
3、设置广播权限
4、发送数据
5、关闭套接字
"""
# 1、导入模块
import socket

# 2、创建套接字
#                   IPv4                 UDP
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3、设置广播权限(套接字默认不允许发送广播,需要开启相关权限)
# PermissionError: [Errno 13] Permission denied
# udp_socket.setsockopt(套接字,属性,属性值)
# socket.SOL_SOCKET 当前的套接字
# socket.SO_BROADCAST 广播属性
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
# 4、发送数据
udp_socket.sendto("哈哈,打不过我吧~".encode(), ("192.168.150.255", 8080))
# 5、关闭套接字
udp_socket.close()

image.png

2.接收数据

s.recvfrom()、
image.png
接收 UDP 数据,与 recv() 类似,但返回值是(data,address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址。

# 1、导入模块
import socket

# 2、创建套接字
#                   IPv4                 UDP
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3、设置广播权限(套接字默认不允许发送广播,需要开启相关权限)
# PermissionError: [Errno 13] Permission denied
# udp_socket.setsockopt(套接字,属性,属性值)
# socket.SOL_SOCKET 当前的套接字
# socket.SO_BROADCAST 广播属性
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
# 4、发送数据
udp_socket.sendto("发送消息~".encode(), ("192.168.56.1 ", 8080))

# 5、每次接收1024个字节 会造成程序阻塞 等待接收发来的数据
recv_data = udp_socket.recvfrom(1024)
# 收到的数据是个元组
result = recv_data[0].decode("GBK")

print("接收来自:", recv_data[1], "消息:", result)
# 6、关闭套接字
udp_socket.close()
  • 编码字符串.encode() 默认UTF-8字符串
  • 解码二进制.decode() 默认UTF-8字符集
  • 解码失败的处理decode(encoding=”字符集”, errors=”错误处理方式”)错误处理方式有两种:ignore 忽略, strict 严格

image.png
上面每次发送端口都是在变化,为了方便可以绑定端口。

s.bind() 绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址。
# address ---> ("ip地址", 端口号) 端口号范围:0-65535
udp_socket.bind(("192.168.56.1", 8088))

UDP广播

广播地址:xxx.xxx.xxx.255 或者 255.255.255.255
思路:

# 1、导入模块
import socket
#创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#开启权限
# PermissionError: [Errno 13] Permission denied
# udp_socket.setsockopt(套接字,属性,属性值)
# socket.SOL_SOCKET 当前的套接字
# socket.SO_BROADCAST 广播属性
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,True)
#绑定端口
udp_socket.bind(("192.168.56.1", 8088))
#发送数据
udp_socket.sendto("哈哈,打不过我吧~".encode(), ("255.255.255.255", 8080))
#udp_socket.sendto("哈哈,打不过我吧~".encode(), ("192.168.150.255", 8080))

udp_socket.close()

udp聊天器

"""
一、功能
1、发送信息
2、接收信息
3、退出系统

二、框架的设计
1、发送信息 send_msg()
2、接收信息 recv_msg()
3、程序的主入口 main()
4、当程序独立运行的时候,才启动聊天器

三、实现步骤
1、发送信息 send_msg()
1) 定义变量接收用户与输入的接收方的IP地址
2)定义变量接收用户与输入的接收方的端口号
3)定义变量接收用户与输入的接收方的内容
4)使用socket的sendto() 发送信息

2、接收信息 recv_msg()
1) 使用socket 接收数据
2)解码数据
3)输出显示

3、主入口main()
1)创建套接字
2)绑定端口
3)打印菜单(循环)
4)接收用户输入的选项
5)判断用户的选择,并且调用对应的函数
6)关闭套接字

"""
import socket


def send_msg(udp_socket):
    """发送信息的函数"""
    # 1) 定义变量接收用户与输入的接收方的IP地址
    ipaddr = input("请输入接收方的IP地址:\n")
    # 判断是否需要默认
    if len(ipaddr) == 0:
        ipaddr = "192.168.150.93"
        print("当前接收方默认IP设置为[%s]" % ipaddr)
    # 2)定义变量接收用户与输入的接收方的端口号
    port = input("请输入接收方的端口号:\n")
    if len(port) == 0:
        port = "8080"
        print("当前接收方默认端口设置为[%s]" % port)
    # 3)定义变量接收用户与输入的接收方的内容
    content = input("请输入要发送的内容:\n")
    # 4)使用socket的sendto()发送信息
    udp_socket.sendto(content.encode(), (ipaddr, int(port)))


def recv_msg(udp_socket):
    """接收信息的函数"""
    # 1) 使用socket接收数据
    # (b'\xe4\xbc\x91\xe6\x81\xaf\xe4\xbc\x91\xe6\x81\xaf\xe6\x89\x80', ('192.168.150.93', 8080))
    recv_data, ip_port = udp_socket.recvfrom(1024)
    # 2)解码数据
    recv_text = recv_data.decode()
    # 3)输出显示
    print("接收到[%s]的消息:%s" % (str(ip_port), recv_text))


def main():
    """程序的主入口"""
    # 1)创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2)绑定端口
    # address ---> ("ip地址", 端口号)
    # udp_socket.bind(address)
    udp_socket.bind(("", 8080))

    while True:
        # 3)打印菜单(循环)
        print("\n\n***************************")
        print("******  1、发送信息  *******")
        print("******  2、接收信息  *******")
        print("******  3、退出系统  *******")
        print("***************************")
        # 4)接收用户输入的选项
        sel_num = int(input("请输入选项:\n"))
        # 5)判断用户的选择,并且调用对应的函数
        if sel_num == 1:
            # print("您选择的是发送信息")
            # 调用发送信息的函数
            send_msg(udp_socket)
        elif sel_num == 2:
            # print("您选择的是接收信息")
            recv_msg(udp_socket)
        elif sel_num == 3:
            print("系统正在退出中...")
            print("系统退出完成!")
            break
    # 6)关闭套接字
    udp_socket.close()


if __name__ == '__main__':
    # 程序独立运行的时候,才去启动聊天器
    main()

TCP网络程序

TCP 面向连接、可靠的、基于字节流的传输控制协议

  • CP的特点
    • 面向连接
    • 可靠传输
      • 应答机制
      • 超时重传
      • 错误校验
      • 流量管控
  • TCP通信模型TCP严格区分客户端、服务端

介绍

客户端

image.png

  • 实现步骤
    • 导入模块
    • 创建套接字socket.SOCK_STREAM
    • 建立连接tcp_client_socket.connect( (“服务端ip”, 服务端端口) )
    • 发送数据tcp_client_socket.send(“内容”.encode())
    • 接收数据recv_data = tcp_client_socket.recv(1024)recv_data 是接收到的数据的二进制
    • 关闭连接 ```python “”” 1、导入模块 2、创建套接字 TCP 3、建立连接 connect() 4、发送数据 5、关闭套接字

“””

1、导入模块

import socket

2、创建套接字 TCP

socket.SOCK_STREAM TCP传输方式

socket.SOCK_DGRAM UDP传输方式

tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

3、建立连接 connect()

tcp_client_socket.connect(address)

address —> (“ip”, 端口)

tcp_client_socket.connect((“192.168.56.1”, 8080))

4、发送数据

tcp_client_socket.send(“约吗?”.encode())

接收数据

recv_data = tcp_client_socket.recv(1024)

recv_data 保存的是服务端回复的信息的二进制

b’\xd4\xbc\xd4\xbc\xd4\xbc’

解码

recv_text = recv_data.decode(“utf-8”) print(“收到数据:”, recv_text)

5、关闭套接字

tcp_client_socket.close()

<a name="FygxU"></a>
### 服务器
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1867021/1636888543044-878ef6e9-9efb-4b4b-b4e9-62547379fb77.png#clientId=u3725c61a-a65e-4&from=paste&height=519&id=u1528d2b4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=519&originWidth=369&originalType=binary&ratio=1&size=47339&status=done&style=none&taskId=uab8c54a3-db5d-4c33-aa3e-b868dd8bd96&width=369)

- 实现步骤
   - 导入模块
   - 创建套接字
   - 绑定端口
   - 开启监听(把套接字由主动设置为被动模式)tcp_server_socket.listen(128) 最大允许128个连接
   - 等待客户端连接new_client_socket,client_ip_port = tcp_server_socket.accept()new_client_socket 新的套接字,只是服务当前的客户端client_ip_port 客户端端口和ip地址
   - 使用新的套接字接收客户端发送的信息new_client_socket.recv(1024)
   - 关闭新的套接字new_client_socket.close() 关闭和当前客户端的连接
   - 关闭服务器套接字tcp_server_socket.close() 服务器不再接收新的客户端,老客户端可以继续服务
```python
"""
1、导入模块
2、创建套接字
3、绑定端口和ip
4、开启监听(设置套接字为被动模式)
5、等待客户端连接
6、收发数据
7、关闭连接

"""

# 1、导入模块
import socket
# 2、创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3、绑定端口和ip
tcp_server_socket.bind(("", 8080))
# 4、开启监听(设置套接字为被动模式)
# listen() 作用设置 tcp_server_socket 套接字为被动监听模式,不能在主动发送数据
# 128 允许接受的最大的连接数,在windows 128 有效,但是在linux 此数字无效
tcp_server_socket.listen(128)
# 5、等待客户端连接
# accept() 开始接受客户端连接,程序会默认进入阻塞状态(等待客户端连接),如果由客户端连接后,程序自动
# 自动解除阻塞
# recv_data 数据含有两部分
# 1)返回了一个新的套接字socket 对象
# 2) 客户端的ip地址和端口号 元组
new_client_socket, client_ip_port = tcp_server_socket.accept()
print("新客户端来了:%s" % str(client_ip_port))
# 6、收发数据
# recv() 会让程序再次阻塞,收到信息后再接阻塞
recv_data = new_client_socket.recv(1024)
recv_text = recv_data.decode("utf-8")
print("接收到[%s]的信息:%s" % (str(client_ip_port), recv_text))
# new_client_socket.close() 表示不能再和当前的客户端通信了
new_client_socket.close()
# 7、关闭连接
# tcp_server_socket.close() 表示程序不再接受新的客户端连接,已经连接的可以继续服务
tcp_server_socket.close()

服务器增强

循环接收多条信息

"""
1、导入模块
2、创建套接字
3、绑定端口和ip
4、开启监听(设置套接字为被动模式)
5、等待客户端连接
6、收发数据
7、关闭连接

"""

# 1、导入模块
import socket

# 2、创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3、绑定端口和ip
tcp_server_socket.bind(("", 8080))
# 4、开启监听(设置套接字为被动模式)
# listen() 作用设置 tcp_server_socket 套接字为被动监听模式,不能在主动发送数据
# 128 允许接受的最大的连接数,在windows 128 有效,但是在linux 此数字无效
tcp_server_socket.listen(128)
# 5、等待客户端连接
# accept() 开始接受客户端连接,程序会默认进入阻塞状态(等待客户端连接),如果由客户端连接后,程序自动
# 自动解除阻塞
# recv_data 数据含有两部分
# 1)返回了一个新的套接字socket 对象
# 2) 客户端的ip地址和端口号 元组
new_client_socket, client_ip_port = tcp_server_socket.accept()
print("新客户端来了:%s" % str(client_ip_port))
while True:
    # 6、收发数据
    # recv() 会让程序再次阻塞,收到信息后再接阻塞
    recv_data = new_client_socket.recv(1024)
    recv_text = recv_data.decode("utf-8")
    print("接收到[%s]的信息:%s" % (str(client_ip_port), recv_text))
# new_client_socket.close() 表示不能再和当前的客户端通信了
new_client_socket.close()
# 7、关闭连接
# tcp_server_socket.close() 表示程序不再接受新的客户端连接,已经连接的可以继续服务
tcp_server_socket.close()

循环接受多个客户端连接

"""
1、导入模块
2、创建套接字
3、绑定端口和ip
4、开启监听(设置套接字为被动模式)
5、等待客户端连接
6、收发数据
7、关闭连接

"""

# 1、导入模块
import socket
# 2、创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3、绑定端口和ip
tcp_server_socket.bind(("", 8080))
# 4、开启监听(设置套接字为被动模式)
# listen() 作用设置 tcp_server_socket 套接字为被动监听模式,不能在主动发送数据
# 128 允许接受的最大的连接数,在windows 128 有效,但是在linux 此数字无效
tcp_server_socket.listen(128)
# 5、等待客户端连接
# accept() 开始接受客户端连接,程序会默认进入阻塞状态(等待客户端连接),如果由客户端连接后,程序自动
# 自动解除阻塞
# recv_data 数据含有两部分
# 1)返回了一个新的套接字socket 对象
# 2) 客户端的ip地址和端口号 元组
while True:
    new_client_socket, client_ip_port = tcp_server_socket.accept()
    print("新客户端来了:%s" % str(client_ip_port))
    # 6、收发数据
    while True:
        # recv() 会让程序再次阻塞,收到信息后再接阻塞
        recv_data = new_client_socket.recv(1024)
        # 当接受到数据为 空 的时候,表示客户端已经断开连接了,服务端也要断开
        # if len(recv_data)!= 0:
        # b'xxxx'
        # 如果recv_data 非空即为真,否则为假
        if recv_data:
            recv_text = recv_data.decode("utf-8")
            print("接收到[%s]的信息:%s" % (str(client_ip_port), recv_text))
        else:
            print("客户端已经断开连接!")
            break
    # new_client_socket.close() 表示不能再和当前的客户端通信了
    new_client_socket.close()
# 7、关闭连接
# tcp_server_socket.close() 表示程序不再接受新的客户端连接,已经连接的可以继续服务
tcp_server_socket.close()

image.png

TCP网络应用程序的注意点介绍

  1. 当 TCP 客户端程序想要和 TCP 服务端程序进行通信的时候必须要先建立连接
  2. TCP 客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的。
  3. TCP 服务端程序必须绑定端口号,否则客户端找不到这个 TCP 服务端程序。
  4. listen 后的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发消息。
  5. 当 TCP 客户端程序和 TCP 服务端程序连接成功后, TCP 服务器端程序会产生一个新的套接字,收发客户端消息使用该套接字。
  6. 关闭 accept 返回的套接字意味着和这个客户端已经通信完毕
  7. 关闭 listen 后的套接字意味着服务端的套接字关闭了,会导致新的客户端不能连接服务端,但是之前已经接成功的客户端还能正常通信。
  8. 当客户端的套接字调用 close 后,服务器端的 recv 会解阻塞,返回的数据长度为0,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的 recv 也会解阻塞,返回的数据长度也为0

案例:

案例-多任务版TCP服务端程序开发

1. 需求

目前我们开发的TCP服务端程序只能服务于一个客户端,如何开发一个多任务版的TCP服务端程序能够服务于多个客户端呢?
完成多任务,可以使用线程,比进程更加节省内存资源。

2. 具体实现步骤

  1. 编写一个TCP服务端程序,循环等待接受客户端的连接请求
  2. 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞
  3. 把创建的子线程设置成为守护主线程,防止主线程无法退出。

    3. 多任务版TCP服务端程序的示例代码:

    ```python import socket import threading

处理客户端的请求操作

def handle_client_request(service_client_socket, ip_port):

# 循环接收客户端发送的数据
while True:
    # 接收客户端发送的数据
    recv_data = service_client_socket.recv(1024)
    # 容器类型判断是否有数据可以直接使用if语句进行判断,如果容器类型里面有数据表示条件成立,否则条件失败
    # 容器类型: 列表、字典、元组、字符串、set、range、二进制数据
    if recv_data:
        print(recv_data.decode("gbk"), ip_port)
        # 回复
        service_client_socket.send("ok,问题正在处理中...".encode("gbk"))

    else:
        print("客户端下线了:", ip_port)
        break
# 终止和客户端进行通信
service_client_socket.close()

if name == ‘main‘:

# 创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口号复用,让程序退出端口号立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定端口号
tcp_server_socket.bind(("", 9090))
# 设置监听, listen后的套接字是被动套接字,只负责接收客户端的连接请求
tcp_server_socket.listen(128)
# 循环等待接收客户端的连接请求
while True:
    # 等待接收客户端的连接请求
    service_client_socket, ip_port = tcp_server_socket.accept()
    print("客户端连接成功:", ip_port)
    # 当客户端和服务端建立连接成功以后,需要创建一个子线程,不同子线程负责接收不同客户端的消息
    sub_thread = threading.Thread(target=handle_client_request, args=(service_client_socket, ip_port))
    # 设置守护主线程
    sub_thread.setDaemon(True)
    # 启动子线程
    sub_thread.start()


# tcp服务端套接字可以不需要关闭,因为服务端程序需要一直运行
# tcp_server_socket.close()
**执行结果:**
```python
客户端连接成功: ('172.16.47.209', 51528)
客户端连接成功: ('172.16.47.209', 51714)
hello1 ('172.16.47.209', 51528)
hello2 ('172.16.47.209', 51714)

小结

  1. 编写一个TCP服务端程序,循环等待接受客户端的连接请求
    while True:
      service_client_socket, ip_port = tcp_server_socket.accept()
    
    2.当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞
    while True:
      service_client_socket, ip_port = tcp_server_socket.accept() 
      sub_thread = threading.Thread(target=handle_client_request, args=(service_client_socket, ip_port))
      sub_thread.start()
    
    3.把创建的子线程设置成为守护主线程,防止主线程无法退出。
    while True:
      service_client_socket, ip_port = tcp_server_socket.accept() 
      sub_thread = threading.Thread(target=handle_client_request, args=(service_client_socket, ip_port))
      sub_thread.setDaemon(True) 
      sub_thread.start()
    

    案例-文件的上传和下载:

    服务端 ```python “”” 1、导入模块 2、创建套接字 3、绑定端口 4、设置监听,设置套接字由主动为被动 5、接受客户端连接 6、接收客户端发送的文件名 7、根据文件名读取文件内容 8、把读取的内容发送给客户端(循环) 9、关闭和当前客户端的连接 10、关闭服务器

“””

1、导入模块

import socket

2、创建套接字

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

设置套接字地址可以重用

tcp_server_socket.setsockopt(当前套接字, 属性名, 属性值)

socket.SO_REUSEADDR 地址是否可以重用 True可以重用 False 不可以重用

tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

3、绑定端口

tcp_server_socket.bind((“”, 8081))

4、设置监听,设置套接字由主动为被动

tcp_server_socket.listen(128)

5、接受客户端连接

while True: new_client_socket, ip_port = tcp_server_socket.accept() print(“欢迎新客户端:”, ip_port)

# 6、接收客户端发送的文件名
recv_data = new_client_socket.recv(1024)
file_name = recv_data.decode()
print(file_name)

try:
    # 7、根据文件名读取文件内容
    with open(file_name, "rb") as file:
        # 8、把读取的内容发送给客户端(循环)
        while True:
            file_data = file.read(1024)
            # 判断是否读取到了文件的末尾
            if file_data:
                # 发送文件
                new_client_socket.send(file_data)
            else:
                break
except Exception as e:
    print("文件%s下载失败!" % file_name)
else:
    print("文件%s下载成功" % file_name)
# 9、关闭和当前客户端的连接
new_client_socket.close()

10、关闭服务器

tcp_server_socket.close()

客户端
```python
"""

1、导入模块
2、创建套接字
3、建立连接
4、接收用户输入的文件名
5、发送文件名到服务端
6、创建文件,并且准备保存
7、接收服务端发送的数据,保存到本地(循环)
8、关闭套接字

"""

# 1、导入模块
import socket
# 2、创建套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3、建立连接
tcp_client_socket.connect(("192.168.150.111", 8081))
# 4、接收用户输入的文件名
file_name = input("请输入要下载的文件名:\n")
# 5、发送文件名到服务端
tcp_client_socket.send(file_name.encode())
# 6、创建文件,并且准备保存
with open("/home/demo/Desktop/"+file_name, "wb") as file:
    # 7、接收服务端发送的数据,保存到本地(循环)
    while True:
        file_data = tcp_client_socket.recv(1024)
        # 判断数据是否传送完毕
        if file_data:
            file.write(file_data)
        else:
            break
# 8、关闭套接字
tcp_client_socket.close()

socket之send和recv原理剖析

1. 认识TCP socket的发送和接收缓冲区

当创建一个TCP socket对象的时候会有一个发送缓冲区和一个接收缓冲区,这个发送和接收缓冲区指的就是内存中的一片空间。

2. send原理剖析

send是不是直接把数据发给服务端?
不是,要想发数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的,它需要调用操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区(内存中的一片空间),再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡

3. recv原理剖析

recv是不是直接从客户端接收数据?
不是,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓存区获取客户端发送的数据

4. send和recv原理剖析图

image.png
说明:

  • 发送数据是发送到发送缓冲区
  • 接收数据是从接收缓冲区 获取

    小结

    不管是recv还是send都不是直接接收到对方的数据和发送数据到对方,发送数据会写入到发送缓冲区,接收数据是从接收缓冲区来读取,发送数据和接收数据最终是由操作系统控制网卡来完成。