python基础——网络编程

前言

互联网时代,现在基本上所有的程序都是网络程序,很少有单机版的程序了。网络编程就是如何在程序中实现两台计算机的通信。

Python语言中,提供了大量的内置模块和第三方模块用于支持各种网络访问,而且Python语言在网络通信方面的优点特别突出,远远

领先其他语言。

通过本章,可以学到:

1 了解TCP和UDP

2 掌握编写UDP Socket客户端应用

3 掌握编写UDP Socket服务器端应用

4 掌握编写TCP Socket客户端应用

5 掌握编写TCP Socket服务器端应用

一、大白话OSI七层协议

互联网的本质就是一系列的网络协议,这个协议就叫OSI协议(一系列协议),按照功能不同,分工不同,人为的分层七层。实际上这个七层是不存在的。没有这七层的概念,只是人为的划分而已。区分出来的目的只是让你明白哪一层是干什么用的。

每一层都运行不同的协议。协议是干什么的,协议就是标准。

实际上还有人把它划成五层、四层。

七层划分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。

五层划分为:应用层、传输层、网络层、数据链路层、物理层。

四层划分为:应用层、传输层、网络层、网络接口层。

二、TCP和UDP协议区别

TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;UDP 主要用于那些对高速传输

和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。

  • TCP:适用于对传输可靠性和顺序性要求高的应用场景,保证数据不丢失、不乱序,并提供流量控制和拥塞控制。
  • UDP:适用于实时性要求高的应用,数据不必完全可靠传输,能容忍部分丢包,传输效率高。

2.1TCP 和 UDP 的主要区别

2.2 TCP协议特点

  • 面向连接:TCP 是面向连接的协议,通信双方在传输数据前必须建立一个连接,这个过程称为"三次握手"。
  • 可靠传输:TCP 提供可靠的数据传输服务,数据包的丢失、重复或顺序错误都会被检测并进行纠正。TCP使用确认机制,接收方必须确认收到的数据包。
  • 数据流控制:TCP 具有流量控制和拥塞控制机制,通过滑动窗口和慢启动等算法,确保网络资源不会被超载。
  • 有序传输:TCP 保证接收方按顺序接收到数据包,即使数据包乱序到达,TCP 也会重新排序。
  • 报文段:TCP 将数据划分为大小适当的报文段,每个报文段都包含一个序列号,用于顺序重组数据。
  • 应用场景:适用于对数据传输可靠性要求较高的场景,如文件传输(FTP)、电子邮件(SMTP)、网页浏览(HTTP/HTTPS)等。

2. 3 UDP协议特点

  • 无连接:UDP 是无连接的协议,发送数据前不需要建立连接,也不会维持连接状态。发送方可以直接将数据报发送给接收方。
  • 不可靠传输:UDP 不保证数据能可靠地送达接收方,不进行数据重传、丢包检测、重排序等操作。数据包可能会丢失、重复或乱序到达。
  • 没有流量控制:UDP 没有流量控制和拥塞控制机制,适用于对实时性要求高、对丢包敏感度较低的应用。
  • 数据报文:UDP 以数据报的形式发送,每个数据报是独立的,报文的大小不能超过 64KB。
  • 应用场景:适用于对实时性要求较高,传输效率重要、容忍一定丢包的应用场景,如视频会议、语音通信(VoIP)、实时游戏、DNS查询等。

2.4 TCP建立连接的三次握手

TCP是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接。 一个TCP连接必须要经过三次"对话"才能建立起
来,其中的过程非常复杂, 只简单的描述下这三次对话的简单过
程:
1)主机A向主机B发出连接请求:"我想给你发数据,可以吗?",这是第一次对话;
2)主机B向主机A发送同意连接和要求同步 (同步就是两台主机一个在发送,一个在接收,协调工作)的数据包 :"可以,你什么时候发?",这是第二次对话;
3)主机A再发出一个数据包确认主机B的要求同步:"我现在就发,你接着吧!", 这是第三次握手。三次"对话"的目的是使数据包的发送和接收同步, 经过三次"对话"
之后,主机A才向主机B正式发送数据。

三次"对话"的目的是使数据包的发送和接收同步, 经过三次"对话"之后,主机A才向主机B正式发送数据。

第一步,客户端发送一个包含SYN即同步(Synchronize)标志的TCP报文,SYN同步报文会指明客户端使用的端口以及TCP连接的初始序号。

第二步,服务器在收到客户端的SYN报文后,将返回一个SYN+ACK的报文,表示客户端的请求被接受,同时TCP序号被加一,ACK即确认(Acknowledgement)

第三步,客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加一,到此一个TCP连接完成。然后才开始通信的第二步:数据处理。

为什么TCP协议有三次握手,而UDP协议没有?

因为三次握手的目的是在client端和server端建立可靠的连接。保证双方发送的数据对方都能接受到,这也是TCP协议的被称为可靠的数据传输协议的原因。而UDP就不一样,UDP不提供可靠的传输模式,发送端并不需要得到接收端的状态,因此UDP协议就用不着使用三次握手。

2.5 TCP断开连接的四次挥手

TCP建立连接要进行3次握手,而断开连接要进行4次:

第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;

第二次: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;

第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;

第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.。

由TCP的三次握手和四次断开可以看出,TCP使用面向连接的通信方式, 大大提高了数据通信的可靠性,使发送数据端和接收端在数据

正式传输前就有了交互, 为数据正式传输打下了可靠的基础。

三、基于TCP协议的socket套接字编程

3.1 什么是scoket?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

3.2 套接字发展史及分类

套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为"伯克利套接字"或"BSD 套接字"。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。

3.2.1 基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

3.2.2 基于网络类型的套接字家族

套接字家族的名字:AF_INET

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

3.3 套接字工作流程

一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束,使用以下Python代码实现:

python 复制代码
import socket

# socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0
socket.socket(socket_family, socket_type, protocal=0)

# 获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能大幅减短我们的代码
tcpSock = socket(AF_INET, SOCK_STREAM)

3.3.1服务端套接字函数

3.3.2客户端套接字函数

3.3.3公共用途的套接字函数

3.3.4面向锁的套接字方法

3.3.5面向文件的套接字的函数

3.4 基于TCP协议的套接字编程(循环)

3.4.1服务端

python 复制代码
import socket
#1、买手机
phone = socket.socket(socket.AF_INET,
                      socket.SOCK_STREAM)  #tcp称为流式协议,udp称为数据报协议SOCK_DGRAM
# print(phone)
#2、插入/绑定手机卡
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1', 8080))
#3、开机
phone.listen(5)  # 半连接池,限制的是请求数
#4、等待电话连接
print('start....')
while True:  # 连接循环
    conn, client_addr = phone.accept()  #(三次握手建立的双向连接,(客户端的ip,端口))
    # print(conn)
    print('已经有一个连接建立成功', client_addr)
    #5、通信:收\发消息
    while True:  # 通信循环
        try:
            print('服务端正在收数据...')
            data = conn.recv(1024)  #最大接收的字节数,没有数据会在原地一直等待收,即发送者发送的数据量必须>0bytes
            # print('===>')
            if len(data) == 0: break  #在客户端单方面断开连接,服务端才会出现收空数据的情况
            print('来自客户端的数据', data)
            conn.send(data.upper())
        except ConnectionResetError:
            break
    #6、挂掉电话连接
    conn.close()
#7、关机
phone.close()

3.4.2客户端

python 复制代码
import socket
#1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# print(phone)
#2、拨电话
phone.connect(('127.0.0.1', 8080))  # 指定服务端ip和端口
#3、通信:发\收消息
while True:  # 通信循环
    msg = input('>>: ').strip()  #msg=''
    if len(msg) == 0: continue
    phone.send(msg.encode('utf-8'))
    # print('has send----->')
    data = phone.recv(1024)
    # print('has recv----->')
    print(data)

#4、关闭
phone.close()

3.5 模拟ssh远程执行命令

3.5.1 服务端

python 复制代码
from socket import *
import subprocess
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
print('start...')
while True:
    conn, client_addr = server.accept()
    while True:
        print('from client:', client_addr)
        cmd = conn.recv(1024)
        if len(cmd) == 0: break
        print('cmd:', cmd)
        obj = subprocess.Popen(cmd.decode('utf8'),  # 输入的cmd命令
                               shell=True,  # 通过shell运行
                               stderr=subprocess.PIPE,  # 把错误输出放入管道,以便打印
                               stdout=subprocess.PIPE)  # 把正确输出放入管道,以便打印
        stdout = obj.stdout.read()  # 打印正确输出
        stderr = obj.stderr.read()  # 打印错误输出
        conn.send(stdout)
        conn.send(stderr)
    conn.close()
server.close()
  • conn.recv(1024):从客户端接收最多 1024 字节的数据。recv() 方法会阻塞,直到接收到数据。接收的是客户端发来的命令。

    • 如果接收到的 cmd 长度为 0,则表示客户端关闭连接,跳出循环。
  • subprocess.Popen():创建一个子进程来执行客户端发来的命令。

    • cmd.decode('utf8'):将接收到的命令解码成字符串,recv() 接收到的是字节数据。
    • shell=True:表示通过 shell 执行命令。
    • stderr=subprocess.PIPEstdout=subprocess.PIPE:将标准输出和标准错误的输出重定向到管道,以便稍后读取。
  • stdout = obj.stdout.read():读取子进程标准输出的内容。

  • stderr = obj.stderr.read():读取子进程标准错误的内容。

  • conn.send(stdout)conn.send(stderr):将命令的执行结果或错误信息通过 conn 发送回客户端。

3.5.2客户端

python 复制代码
import socket

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

client.connect(('127.0.0.1', 8080))

while True:
    data = input('please enter your data')
    client.send(data.encode('utf8'))
    data = client.recv(1024)

    print('from server:', data)

client.close()

3.6 聊天室编程

3.6.1服务端

python 复制代码
import socket
import threading
def handle_client(client_socket, client_address):
    print(f"{client_address} has connected.")
    while True:
        try:
            message = client_socket.recv(1024).decode('utf-8')
            if not message:
                break
            print(f"{client_address}: {message}")
            broadcast(message, client_socket)
        except ConnectionResetError:
            break
    print(f"{client_address} has disconnected.")
    client_socket.close()

def broadcast(message, client_socket):
    for client in clients:
        if client != client_socket:
            client.send(message.encode('utf-8'))

if __name__ == "__main__":
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    print("Server is running...")
    clients = []
    while True:
        client_socket, client_address = server.accept()
        clients.append(client_socket)
        client_handler = threading.Thread(target=handle_client, args=(client_socket, client_address))
        client_handler.start()

3.6.2客户端

python 复制代码
import socket
import threading
def receive_messages(client_socket):
    while True:
        try:
            message = client_socket.recv(1024).decode('utf-8')
            if not message:
                break
            print(message)
        except ConnectionResetError:
            break
def main():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect(('127.0.0.1', 8080))

    thread = threading.Thread(target=receive_messages, args=(client_socket,))
    thread.start()

    while True:
        message = input()
        if message.lower() == 'exit':
            break
        client_socket.send(message.encode('utf-8'))

    client_socket.close()

if __name__ == "__main__":
    main()

四、UDP编程介绍 、

UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。创建Socket时, SOCK_DGRAM 指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用 listen() 方法,而是直接接收来自任何客户端的数据。 recvfrom() 方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用 sendto() 就可以把数据用UDP发给客户端。

4.1 DUP编程的实现

服务端

python 复制代码
#coding=utf-8
from socket import *
#最简化的UDP服务端代码
s = socket(AF_INET,SOCK_DGRAM)  #创建UDP类型的套接字
s.bind(("127.0.0.1",8888))  #绑定端口,ip可以不写
print("等待接收数据!")
recv_data = s.recvfrom(1024)    #1024表示本次接收的最大字节数
print(f"收到远程信息:{recv_data[0].decode('gbk')},from {recv_data[1]}")
s.close()

客户端

python 复制代码
#coding=utf-8
from socket import *
#最简化的UDP客户端发送消息代码
s = socket(AF_INET,SOCK_DGRAM)  #创建UDP类型的套接字
addr = ("127.0.0.1",8888)
data = input("请输入:")
s.sendto(data.encode("gbk"),addr)
s.close()

4.2 UDP持续接受信息

服务端

python 复制代码
#coding=utf-8
from socket import *
#最简化的UDP服务端代码
s = socket(AF_INET,SOCK_DGRAM)  #创建UDP类型的套接字
s.bind(("127.0.0.1",8888))  #绑定端口,ip可以不写
print("等待接收数据!")
while True:
    recv_data = s.recvfrom(1024)    #1024表示本次接收的最大字节数
    recv_content = recv_data[0].decode('gbk')
    print(f"收到远程信息:{recv_content},from {recv_data[1]}")
    if recv_content == "88":
        print("结束聊天!")
        break

s.close()

客户端

python 复制代码
#coding=utf-8
from socket import *
#最简化的UDP客户端发送消息代码
s = socket(AF_INET,SOCK_DGRAM)  #创建UDP类型的套接字
addr = ("127.0.0.1",8888)

while True:
    data = input("请输入:")
    s.sendto(data.encode("gbk"),addr)
    if data == "88":
        print("结束聊天!")
        break

s.close()

参考文献

大白话OSI七层协议 - B站-水论文的程序猿 - 博客园

电脑间数据通信-OSI协议简述版 - B站-水论文的程序猿 - 博客园

TCP协议的三次握手和四次挥手 - B站-水论文的程序猿 - 博客园

基于TCP协议的socket套接字编程 - B站-水论文的程序猿 - 博客园
Socket抽象层 - B站-水论文的程序猿 - 博客园

模拟ssh远程执行命令 - B站-水论文的程序猿 - 博客园

粘包问题 - B站-水论文的程序猿 - 博客园

解决粘包问题 - B站-水论文的程序猿 - 博客园

基于UDP协议的socket套接字编程 - B站-水论文的程序猿 - 博客园

基于socketserver实现并发的socket编程 - B站-水论文的程序猿 - 博客园

相关推荐
速盾cdn2 小时前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin5211232 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin2 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
PersistJiao3 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
黑客Ash6 小时前
【D01】网络安全概论
网络·安全·web安全·php
->yjy6 小时前
计算机网络(第一章)
网络·计算机网络·php
摘星星ʕ•̫͡•ʔ7 小时前
计算机网络 第三章:数据链路层(关于争用期的超详细内容)
网络·计算机网络
.Ayang8 小时前
SSRF漏洞利用
网络·安全·web安全·网络安全·系统安全·网络攻击模型·安全架构
好想打kuo碎8 小时前
1、HCIP之RSTP协议与STP相关安全配置
网络·安全