Python网络编程入门

Python网络编程入门

一、网络编程基础

当今的世界是一个互联的世界,绝大多数的计算机和人都在通过网络和他人传递信息、沟通互联。怎么做到实现网络的通信?需要了解通信机制。

网络通信是通过多方协作共同完成的,各个参与方需要遵守同一规则约定,才能保证通信有序进行。这样的规则约定,就是所谓的 网络协议 。

为降低复杂度并提高灵活性,大型软件系统一般采用分层的思路进行设计。每个层只专注于本层的处理逻辑,复杂性大大降低;各个层互相配合,共同完成复杂的业务处理。

国际标准组织推出的OSI(Open System Interconnection Reference Model,开放式系统互联通信参考模型)的网络模型(也成为体系结构),共有七层,实则分得太细,比较复杂,过于理想化,不太实用,被用作学校进行网络基础教育的示范和典型。在现实中发展壮大起来的TCP/IP网络模型是四层模型,从根本上和OSI七层网络模型是一样的,只是合并了几层,两者的对应关系如下图:

特别提示:TCP/IP是 Transmission Control Protocol/Internet Protocol的简写,从字面意义上讲,有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称。具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。

在TCP/IP协议中,其各层之间的通信机制,大致如下图所示:

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。由于是基于双方连接的情况下传输的,因此它的连接以及数据传输是非常稳定可靠的,可以使一台计算机发出的字节流完好无损的发生给另一台计算机。对要求可靠性非常高的应用程序会选择此种通信方式。

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去。UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。传输速率快,但不太稳定,会出现发生失败的情况,更极端的就是发生掉包的情况。

IP地址和端口

在网络中每台计算机都必须有一个的IP地址;目前,IP地址广泛使用的版本是IPv4 32位,4个字节,用点分十进制的格式表示,例如:192.168.1.100 。

127.0.0.1 是固定ip地址,代表当前计算机,相当于面向对象里的 "this"。

一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来 实现。那么,主机是怎样区分不同的网络服务呢?通过“IP地址+端口号”来区 分不同的服务的。

程序员实现网络通信,原则上讲,需要将程序代码和TCP/IP网络协议连接起来——直接去和TCP/IP协议打交道,这需要他具备大量、扎实和高水平的网络相关知识,编写大量的网络协议实现相关代码,这些工作对于大多数人来说,不但是困难的,也是耗时费力,还不一定做得好的。这也不符合现代编程理念,因为网络通信是一个非常通用,应用非常广泛的场景,底层内容大家都是一样的,完全可以封装成一个通用的模块,提供调用接口,方便大家使用,而不用重复造轮子。幸运的是,对于普通的程序员有socket帮助,简单地说,socket是对TCP/IP协议的封装和应用(程序员层面上),是一个针对TCP或者UDP编程的接口,是进程之间用来对话的中间层。

有了套接字(socket)其各层之间的通信机制,大致如下图所示:

进行网络编程,通常需要编写两个程序文件,一个作为服务端,一个作为客户端。

下面的示意图展示 Socket和 TCP 的数据流:

其中,客户(Client)端: 发起访问的一方; 服务器(Server)端: 接受访问的一方。

Server端的编写流程

1.建立Socket负责具体通信。这个socket其实只负责接受对方的请求

2.绑定端口和地址

3.监听接入的访问Socket

4.接受访问的Socket,可以理解接受访问即建立了一个通讯的链路通路

5.接受对方的发送内容,利用接收到的Socket接受内容

6.如果有必要,给对方发送反馈信息

7.关闭链路通路

Client端流程

1.建立通信socket

2.链接对方,请求与对方建立通路

3.发送内容到对方服务器

4.接受对方的反馈

5.关闭链接通路

下面的示意图展示 Socket和UDP的数据流:

Server端流程

1.建立socket

2.绑定,为创建的socket指派固定的端口和ip地址

3.接受对方发送内容

4.给对方发送反馈,此步骤为非必须步骤

Client端流程

1.建立通信的socket

2.发送内容到指定服务器

3.接受服务器给定的反馈内容

二、Python 网络编程

用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。Python 网络编程和其它语言类似,通常通过"Socket(套接字)"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

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

低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统 Socket 接口的全部方法。socket --- 底层网络接口 — Python 3.10.0 文档

高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。socketserver --- 用于网络服务器的框架 — Python 3.10.0 文档

下面先介绍Socket(套接字)编程,然后介绍网络服务模块 SocketServer编程。

Python 中的 socket()函数/方法来创建套接字,语法格式如下:

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

参数

family: 套接字家族可以使 AF_UNIX 或者 AF_INET。

type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAM 或 SOCK_DGRAM。

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()

关闭套接字

s.getpeername()

返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

s.getsockname()

返回套接字自己的地址。通常是一个元组(ipaddr,port)

s.setsockopt(level,optname,value)

设置给定套接字选项的值。

s.getsockopt(level,optname[.buflen])

返回套接字选项的值。

s.settimeout(timeout)

设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())

s.gettimeout()

返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。

s.fileno()

返回套接字的文件描述符。

s.setblocking(flag)

如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

s.makefile()

创建一个与该套接字相关连的文件

简单TCP通信功能示例

进行网络编程,通常需要编写两个py文件,一个服务端,一个客户端。

用Socket进行TCP协议编程,对于服务器,要提供IP地址和端口号,监听连接请求,对每一个新的连接,创建一个线程或进程来处理。对于客户端,需要要主动连接服务器的IP和指定端口。

1、server端源码(server端.py)如下:

import socket

ip_port = ('127.0.0.1', 9999)
 
sk = socket.socket()            # 创建套接字
sk.bind(ip_port)                # 绑定服务地址
sk.listen(5)                    # 监听连接请求
print('启动socket服务,等待客户端连接...')
conn, address = sk.accept()     # 等待连接,此处自动阻塞
while True:     # 一个死循环,直到客户端发送‘exit’的信号,才关闭连接
    client_data = conn.recv(1024).decode()      # 接收信息
    if client_data == "exit":       # 判断是否退出连接
        exit("通信结束")
    print("来自%s的客户端向你发来信息:%s" % (address, client_data))
    conn.sendall('服务器已经收到你的信息'.encode())    # 回馈信息给客户端
conn.close()    # 关闭连接

2、client端(client端.py)源码如下:

import socket
 
ip_port = ('127.0.0.1', 9999)
 
s = socket.socket()     # 创建套接字
 
s.connect(ip_port)      # 连接服务器
 
while True:     # 通过一个死循环不断接收用户输入,并发送给服务器
    inp = input("请输入要发送的信息: ").strip()
    if not inp:     # 防止输入空信息,导致异常退出
        continue
    s.sendall(inp.encode())
 
    if inp == "exit":   # 如果输入的是‘exit’,表示断开连接
        print("结束通信!")
        break
 
    server_reply = s.recv(1024).decode()
    print(server_reply)
 
s.close()       # 关闭连接

特别说明,我们的例子中为了方便实验使用了特殊的IP地址127.0.0.1,这个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。

打开两个cmd(命令行)窗口,一个先运行服务器端程序,另一个运行客户端程序,就可以看到效果了。

我这里,为了方便管理与测试将这两个文件放在这个文件夹中:D:\Python网络编程实践

先启动server端,打开cmd输入

Python D:\Python网络编程实践\server端.py

再启动\client,打开cmd输

Python D:\Python网络编程实践\client端.py

运行效果参见下图:

简单UDP通信功能示例

相对TCP编程,UDP编程就简单多了,当然可靠性和安全性也差很多。由于UDP没有握手和挥手的过程,因此accept()和connect()方法都不需要。

server端源码(UDPserver端.py)如下:

import socket

ip_port = ('127.0.0.1', 9999)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) # 创建套接字
sk.bind(ip_port) # 绑定服务地址
print('服务端启动,等待客户端连接...') 
while True:
    data = sk.recv(1024).strip().decode()  # 接收信息
    print(data)
    if data == "exit":
        print("客户端主动断开连接!")
        break
 
sk.close() # 关闭连接

2、client端(UDPclient端.py)源码如下:

import socket

ip_port = ('127.0.0.1', 9999)
 
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) # 创建套接字
while True:
    inp = input('发送的消息:').strip()
    sk.sendto(inp.encode(), ip_port)  # 发送信息
    if inp == 'exit':
        break
 
sk.close()

打开两个cmd(命令行)窗口,一个先运行服务器端程序,另一个运行客户端程序,就可以看到效果了。

我这里,为了方便管理与测试将这两个文件放在这个文件夹中:D:\Python网络编程实践

先启动server端,打开cmd输入

Python D:\Python网络编程实践\UDPserver端.py

再启动\client,打开cmd输

Python D:\Python网络编程实践\UDPclient端.py

运行效果参见下图:

socketserver模块编程

socketserver 模块简化了编写网络服务器的任务。

socketserver --- 用于网络服务器的框架 — Python 3.10.0 文档

虽说用socket(套接字)编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较 好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也 是Python标准库中很多服务器框架的基础。

socketserver 包含4个类型:TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer。前两个用于windows,后两个用于Unix。

ThreadingTCPServer()方法/函数可实现多线程。多线程即同时与多个用户交互且互不影响。

ForkingTCPServer()是多进程方法/函数。多进程同多线程道理一样。 但是此方法/函数适用于Unix。

创建一个SocketServer分3步:(1)创建一个请求处理类,并继承BaseRequestHandler,且在类中重写BaseRequestHandler的handle(). (2)实例化一个TCPServer,且传递server ip 和第一步所创建的请求处理类给实例化对象。(3)处理多个请求 server.serve_forver()。

使用socketserver编程的例子

客户端的代码很好理解,可以和前面一样,关键是服务器端。

1、server端源码(socketServer端.py)如下:

import socketserver

class MyServer(socketserver.BaseRequestHandler):
    """
    必须继承socketserver.BaseRequestHandler类
    """
    def handle(self):
        """
        必须实现这个方法!
        :return:
        """
        conn = self.request         # request里封装了所有请求的数据
        conn.sendall('欢迎访问socketserver服务器!'.encode())
        while True:
            data = conn.recv(1024).decode()
            if data == "exit":
                print("断开与%s的连接!" % (self.client_address,))
                break
            print("来自%s的客户端向你发来信息:%s" % (self.client_address, data))
            conn.sendall(('已收到你的消息<%s>' % data).encode())

# 创建一个多线程TCP服务器
server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyServer)
print("启动socketserver服务器!")
# 启动服务器,服务器将一直保持运行状态
server.serve_forever()

2、client端(clientMy端2.py)源码如下:

import socket

ip_port = ('127.0.0.1', 9999)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
data = sk.recv(1024).decode()
print('服务器:', data)
while True:
    inp = input('请输入要发送的信息:').strip()
    if not inp:
        continue

    sk.sendall(inp.encode())

    if inp == 'exit':
        print("谢谢使用,再见!")
        break
    data = sk.recv(1024).decode()
    print('服务器:', data)
sk.close()

打开两个cmd(命令行)窗口,一个先运行服务器端程序,另一个运行客户端程序,就可以看到效果了。

我这里,为了方便管理与测试将这两个文件放在这个文件夹中:D:\Python网络编程实践

先启动server端,打开cmd输入

Python D:\Python网络编程实践\socketServer端.py

再启动\client,打开cmd输

Python D:\Python网络编程实践\client端2.py

运行效果参见下图:

附录

https://segmentfault.com/a/1190000016501735

FTP和TCP、UDP - qichueng - 博客园

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值