1.认识TCP/IP
计算机网络就是把各个计算机连接在一起,在网络中的计算机可以互相通信。
网络编程是如何在程序中实现两台计算机的通信。
网络通信是两台计算机上的两个进程之间的通信。
为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,互联网协议包含了上百种,但最重要的有TCP和IP协议。
TCP/IP 协议被分为4层:应用层、传输层、网络层、接口层:
- 应用层协议有HTTP、FTP、SMTP等,用来接收来自传输层的数据或按不同应用要求及方式将数据传输至传输层。
- 传输层协议有UDP、TCP,实现数据传输与数据共享。
- 网络层协议有ICMP、IP、IGMP,主要负责网络中的数据包的传送等。
- 接口层协议有ARP、RARP,主要提供链路管理、错误检测、对不同通信媒介有关信息细节问题进行有效处理等。
通信双方必须知道对方的标识,互联网每个计算 机的唯一标识就是IP地址。如果一台计算机同时接入两个或更多的网络,如路由器,就要有两个或多个IP地址。所以,IP地址对应的实际上是计算机的网络接口,通常是网卡。
TCP协议是建立IP协议之上的,负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
2.socket编程
socket 是应用层与传输层、网络层之间进行通信的中间软件抽象层,是一组接口,把复杂的TCP/IP协议隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,调用socket接口函数去组织数据,以符合指定的协议,这样网络间的通信业就简单了许多。
Python 提供了两个基本的socket处理模块。
- socket:提供了标准的BSD Sockets API,可以访问底层操作系统socket接口的全部方法。
- socketsever:提供了服务器中心类,可以简化网络服务器的开发。
2.1 使用socket
服务器
服务器端进程需要申请套接字,然后绑定套接字进行监听。当有客户端发送请求,则接收数据并进行处理,处理完后对客户端进行响应。
- 创建套接字
import socket # socket模块的socket()函数能创建socket对象 socket.socket([family[,type[,proto]]]) ''' family:设置套接字种族,包括AF_UNIX和AF_INET,常用AF_INET选项。 type:设置套接字类型。 proto:协议类型,默认为0,一般不填。 ''' s1 = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 网络通信,协议类型
- 绑定套接字
s1.bind(address) # address 地址必须是一个元组:(host,port) s1.bind((host,port))
- 监听套接字
s1.listen(backlog) # backlog指定最多允许多少个客户端连接服务器
- 等待接受连接
connection,address = s1.accept() # 阻塞状态
- 处理阶段
# 数据以字节串格式返回,bufsize指定最多可以接受的数量。 connection.recv(bufsize[,flag]) # 发送给连接的客户端套接字 connection.send(string[,flag])
- 关闭连接
s1.close()
客户端
客户端只需要申请一个套接字,然后通过套接字连接到服务器端,建立连接之后就可以通信。
- 创建套接字socket对象。
import socket # 导入socket模块 s2 = socket.socket() # 实例化socket 对象
- 连接到服务器端。
s2.connect(address) # address 地址必须是一个元组:(host,port)
- 处理阶段。
# 数据以字节串格式返回,bufsize指定最多可以接受的数量。 s2.recv(bufsize[,flag]) # 将参数string包含的字节串发送到服务器端 s2.send(string[,flag])
- 关闭连接。
s2.close()
示例:使用socket模块构建一个网络通信服务。
# 新建server.py,输入以下命令,作为服务器端响应文件 import socket # 创建服务端服务 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(('localhost',6999)) # 绑定要监听的端口,本地计算机6999 s.listen(5) # 开始监听,使用5个连接排队 while True: conn,addr = s.accept() print(conn,addr) # 输出连接信息 try: data = conn.recv(1024) # 接收数据 print('recive:',data.decode()) # 解码 conn.send(data.upper()) # 发送数据 conn.close() # 关闭连接 except: print('出现异常') break # 新建client.py,输入以下命令,作为客户端请求文件 import socket c = socket.socket(socket.AF_INET,socket.SOCK_STREAM) c.connect(('localhost',6999)) # msg = '欢迎新同学!' c.send(msg.encode('utf-8')) # 发送一条信息,将字符串转换为字节流 data = c.recv(1024) # 接收信息,大小为1024字节 print('recv:',data.decode()) # 输出接收信息 c.close() # 关闭
2.2 使用socketserver
socketserver模块封装了socket模块和select模块,使用多线程处理多个客户端的连接,使用select模块处理高并发访问。
- 服务类:提供了建立连接的过程,如绑定、监听、运行等。
- 请求处理类:专注于如何处理用户发送的数据
服务类
服务类包含5种类型:
- BaseServer:不直接对外服务。
- TCPServer:针对TCP套接字流。
- UDPServer:针对UDP数据报套接字。
- UnixStreamServer和UnixDatagramServer:针对Unix域套接字,不常用。
请求处理类
socketserver 模块提供请求处理类BaseRequestHandler, 以及派生类StreamRequestHandler(处理流式套接字)和DatagramRequestHandler(处理数据报套接字)。请求处理类有3种方法:
- setup():在handle()之前被调用,执行处理请求前的初始化操作,默认不做任何事情。
- handle():执行与处理请求相关的工作。
- finish():在handle()之后调用,执行处理完请求后的清理操作,默认不做任何事情。
使用socketserver创建一个服务,具体步骤如下:
- 创建一个请求处理类,选择StreamRequestHandler或DatagramRequestHandler作为父类,也可以选择BaseRequestHandler作为父类,并重写handle()方法。
- 实例化一个服务类对象,并将服务的地址和第1步创建的请求处理类传递给它。
- 调用服务类对象的handle_request()或server_forever()方法开始处理请求。
示例:使用socketserver模块构建一个网络通信服务。
# 新建server1.py,输入以下命令,作为服务器端响应文件 import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): # 自定义请求处理类 def handle(self): # 重写handle()方法 try: while True: self.data = self.request.recv(1024) # 接收数据 print(self.client_address,self.data) # 打印数据 if not self.data: # 如果没有接收到数据 print('连接丢失') break # 结束轮询 self.request.sendall(self.data.upper()) # 向客户端响应数据 except Exception as e: print(self.client_address,'连接断开') finally: self.request.close() # 关闭 def setup(selfself): # 重写setup()方法 print('setup被执行') def finish(self): # 重写finish()方法 print('finish被执行') if __name__ == "__main__": s = socketserver.TCPServer(('localhost',9999),MyTCPHandler) s.serve_forever() # 新建client1.py,输入以下命令,作为客户端请求文件 import socket c = socket.socket() c.connect(('localhost',9999)) # 连接到服务器 while True: cmd = input('是否退出(y/n)').strip() # 是否退出 if len(cmd)==0: continue if cmd=='y' or cmd=='Y': # 退出交流 break c.send(cmd.encode()) # 发送信息 cmd_res=c.recv(1024) # 接收信息 print(cmd_res.decode()) # 打印信息 c.close() # 关闭