〖Web全栈开发②〗—网络编程基础(下)

  • (一)TCP 网络应用程序开发流程
      • 1. TCP 网络应用程序开发流程的介绍
      • 2. TCP 客户端程序开发流程的介绍
      • 3. TCP 服务端程序开发流程的介绍
      • 4. 小结
  • (二)socket之send和recv原理剖析
      • 1. 认识TCP socket的发送和接收缓冲区
      • 2. send原理剖析
      • 3. recv原理剖析
      • 4. send和recv原理剖析图
      • 5. 小结
  • (三)TCP服务端程序开发
      • 1. 开发 TCP 服务端程序开发步骤回顾
      • 2. socket 类的介绍
      • 3. TCP 服务端程序开发示例代码
      • 4. 小结
  • (四)TCP 客户端程序开发
      • 1. 开发 TCP 客户端程序开发步骤回顾
      • 2. socket 类的介绍
      • 3. TCP 客户端程序开发示例代码
      • 4. 小结
  • (五)TCP网络应用程序的注意点
      • 1. TCP网络应用程序的注意点介绍

️️个人简介:以山河作礼。
️️:Python领域新星创作者,CSDN实力新星认证,阿里云社区专家博主
:Web全栈开发专栏:《Web全栈开发》免费专栏,欢迎阅读!


(一)TCP 网络应用程序开发流程

学习目标

  • 能够知道TCP客户端程序的开发流程

1. TCP 网络应用程序开发流程的介绍

TCP 网络应用程序开发分为:

  • TCP 客户端程序开发
  • TCP 服务端程序开发

说明:

客户端程序是指运行在用户设备上的程序 服务端程序是指运行在服务器设备上的程序,专门为客户端提供数据服务。

2. TCP 客户端程序开发流程的介绍

步骤说明:

  1. 创建客户端套接字对象
  2. 和服务端套接字建立连接
  3. 发送数据
  4. 接收数据
  5. 关闭客户端套接字

3. TCP 服务端程序开发流程的介绍

步骤说明:

  1. 创建服务端端套接字对象
  2. 绑定端口号
  3. 设置监听
  4. 等待接受客户端的连接请求
  5. 接收数据
  6. 发送数据
  7. 关闭套接字

4. 小结

  1. TCP 网络应用程序开发分为客户端程序开发服务端程序开发
  2. 主动发起建立连接请求的是客户端程序
  3. 等待接受连接请求的是服务端程序

(二)socket之send和recv原理剖析

学习目标

  • 能够知道send和recv的底层工作原理

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

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

2. send原理剖析

send是不是直接把数据发给服务端” />3. recv原理剖析

recv是不是直接从客户端接收数据?

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

4. send和recv原理剖析图

说明:

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

5. 小结

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

(三)TCP服务端程序开发

学习目标

  • 能够写出TCP服务端应用程序接收和发送消息

1. 开发 TCP 服务端程序开发步骤回顾

  1. 创建服务端端套接字对象
  2. 绑定端口号
  3. 设置监听
  4. 等待接受客户端的连接请求
  5. 接收数据
  6. 发送数据
  7. 关闭套接字

2. socket 类的介绍

导入 socket 模块
import socket

创建服务端 socket 对象
socket.socket(AddressFamily, Type)

参数说明:

  • AddressFamily 表示IP地址类型, 分为TPv4和IPv6
  • Type 表示传输协议类型

方法说明:

  • bind((host, port)) 表示绑定端口号, host 是 ip 地址,port 是端口号,ip 地址一般不指定,表示本机的任何一个ip地址都可以。
  • listen (backlog) 表示设置监听,backlog参数表示最大等待建立连接的个数。
  • accept() 表示等待接受客户端的连接请求
  • send(data) 表示发送数据,data 是二进制数据
  • recv(buffersize) 表示接收数据, buffersize 是每次接收数据的长度

3. TCP 服务端程序开发示例代码

import socketif __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(("", 8989))# 设置监听# 128:最大等待建立连接的个数, 提示: 目前是单任务的服务端,同一时刻只能服务与一个客户端,后续使用多任务能够让服务端同时服务与多个客户端,# 不需要让客户端进行等待建立连接# listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字来完成tcp_server_socket.listen(128)# 等待客户端建立连接的请求, 只有客户端和服务端建立连接成功代码才会解阻塞,代码才能继续往下执行# 1. 专门和客户端通信的套接字: service_client_socket# 2. 客户端的ip地址和端口号: ip_portservice_client_socket, ip_port = tcp_server_socket.accept()# 代码执行到此说明连接建立成功print("客户端的ip地址和端口号:", ip_port)# 接收客户端发送的数据, 这次接收数据的最大字节数是1024recv_data = service_client_socket.recv(1024)# 获取数据的长度recv_data_length = len(recv_data)print("接收数据的长度为:", recv_data_length)# 对二进制数据进行解码recv_content = recv_data.decode("utf-8")print("接收客户端的数据为:", recv_content)# 准备发送的数据send_data = "ok, 问题正在处理中...".encode("utf-8")# 发送数据给客户端service_client_socket.send(send_data)# 关闭服务与客户端的套接字, 终止和客户端通信的服务service_client_socket.close()# 关闭服务端的套接字, 终止和客户端提供建立连接请求的服务tcp_server_socket.close()

执行结果:

客户端的ip地址和端口号: ('********', 58854)接收数据的长度为: 40接收客户端的数据为: 你好服务端,我是客户端

说明:

当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需要等待大概1-2分钟。

解决办法有两种:

  1. 更换服务端端口号
  2. 设置端口号复用(推荐大家使用),也就是说让服务端程序退出后端口号立即释放。

设置端口号复用的代码如下:

# 参数1: 表示当前套接字# 参数2: 设置端口号复用选项# 参数3: 设置端口号复用选项对应的值tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

4. 小结

  1. 导入socket模块
  2. 创建TCP套接字‘socket’
    • 参数1: ‘AF_INET’, 表示IPv4地址类型
    • 参数2: ‘SOCK_STREAM’, 表示TCP传输协议类型
  3. 绑定端口号‘bind’
    • 参数: 元组, 比如:(ip地址, 端口号)
  4. 设置监听‘listen’
    • 参数: 最大等待建立连接的个数
  5. 等待接受客户端的连接请求‘accept’
  6. 发送数据‘send’
    • 参数: 要发送的二进制数据, 注意: 字符串需要使用encode()方法进行编码
  7. 接收数据‘recv’
    • 参数: 表示每次接收数据的大小,单位是字节,注意: 解码成字符串使用decode()方法
  8. 关闭套接字‘socket’表示通信完成

(四)TCP 客户端程序开发

学习目标

  • 能够写出 TCP 客户端应用程序发送和接收消息

1. 开发 TCP 客户端程序开发步骤回顾

  1. 创建客户端套接字对象
  2. 和服务端套接字建立连接
  3. 发送数据
  4. 接收数据
  5. 关闭客户端套接字

2. socket 类的介绍

导入 socket 模块 import socket

创建客户端 socket 对象 socket.socket(AddressFamily, Type)

参数说明:

  • AddressFamily 表示IP地址类型, 分为TPv4和IPv6
  • Type 表示传输协议类型

方法说明:

  • connect((host, port)) 表示和服务端套接字建立连接, host是服务器ip地址,port是应用程序的端口号
  • send(data) 表示发送数据,data是二进制数据
  • recv(buffersize) 表示接收数据, buffersize是每次接收数据的长度

3. TCP 客户端程序开发示例代码

import socketif __name__ == '__main__':# 创建tcp客户端套接字# 1. AF_INET:表示ipv4# 2. SOCK_STREAM: tcp传输协议tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 和服务端应用程序建立连接tcp_client_socket.connect(("*******", 8989))# 代码执行到此,说明连接建立成功# 准备发送的数据send_data = "你好服务端,我是客户端小明!".encode("utf-8")# 发送数据tcp_client_socket.send(send_data)# 接收数据, 这次接收的数据最大字节数是1024recv_data = tcp_client_socket.recv(1024)# 返回的直接是服务端程序发送的二进制数据print(recv_data)# 对数据进行解码recv_content = recv_data.decode("utf-8")print("接收服务端的数据为:", recv_content)# 关闭套接字tcp_client_socket.close()

执行结果:

b'hello'接收服务端的数据为: hello

说明

  1. str.encode(编码格式) 表示把字符串编码成为二进制
  2. data.decode(编码格式) 表示把二进制解码成为字符串

多个客户端与服务器进行链接,需要将链接放入循环中

服务端

import socketimport threadingdef data_recv():# 准备发送的数据while True:send_data = input('发送信息:').encode("utf-8")# 发送数据service_client_socket.send(send_data)def data_send():while True:# 接收数据, 这次接收的数据最大字节数是1024recv_data = service_client_socket.recv(1024)# 返回的直接是服务端程序发送的二进制数据# print(recv_data)# 对数据进行解码recv_content = recv_data.decode("utf-8")print("接收服务端的数据为:", recv_content)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(("0.0.0.0", 8989))# 设置监听# 128:最大等待建立连接的个数, 提示: 目前是单任务的服务端,同一时刻只能服务与一个客户端,后续使用多任务能够让服务端同时服务与多个客户端,# 不需要让客户端进行等待建立连接# listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字来完成tcp_server_socket.listen(1)# 等待客户端建立连接的请求, 只有客户端和服务端建立连接成功代码才会解阻塞,代码才能继续往下执行# 1. 专门和客户端通信的套接字: service_client_socket# 2. 客户端的ip地址和端口号: ip_portwhile True:service_client_socket, ip_port = tcp_server_socket.accept()# 代码执行到此说明连接建立成功print("客户端的ip地址和端口号:", ip_port)t1 = threading.Thread(target=data_recv)t1.start()t2 = threading.Thread(target=data_send)t2.start()# 关闭服务与客户端的套接字, 终止和客户端通信的服务service_client_socket.close()

其他客户端

import socketimport threadingdef data_recv():# 准备发送的数据while True:send_data = input('发送信息:').encode("utf-8")# 发送数据tcp_client_socket.send(send_data)def data_send():while True:# 接收数据, 这次接收的数据最大字节数是1024recv_data = tcp_client_socket.recv(1024)# 返回的直接是服务端程序发送的二进制数据# print(recv_data)# 对数据进行解码recv_content = recv_data.decode("utf-8")print("接收服务端的数据为:", recv_content)if __name__ == '__main__':# 创建tcp客户端套接字# 1. AF_INET:表示ipv4# 2. SOCK_STREAM: tcp传输协议tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 和服务端应用程序建立连接tcp_client_socket.connect(("*******", 8000))# 代码执行到此,说明连接建立成功t1 = threading.Thread(target=data_recv)t1.start()t2 = threading.Thread(target=data_send)t2.start()t1.join()t2.join()# 关闭套接字tcp_client_socket.close()

4. 小结

  1. 导入socket模块
  2. 创建TCP套接字‘socket’
    • 参数1: ‘AF_INET’, 表示IPv4地址类型
    • 参数2: ‘SOCK_STREAM’, 表示TCP传输协议类型
  3. 发送数据‘send’
    • 参数1: 要发送的二进制数据, 注意: 字符串需要使用encode()方法进行编码
  4. 接收数据‘recv’
    • 参数1: 表示每次接收数据的大小,单位是字节
  5. 关闭套接字‘socket’表示通信完成

(五)TCP网络应用程序的注意点

学习目标

  • 能够说出开发TCP网络应用程序的注意点

1. 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