网络模型的简介

网络技术是从1990年代中期发展起来的新技术,它把互联网上分散的资源融为有机整体,实现资源的全面共享和有机协作,使人们能够透明地使用资源的整体能力并按需获取信息,资源包括高性能计算机、存储资源、数据资源、信息资源、知识资源、专家资源、大型数据库、网络、传感器等.

网络编程是实现各类网络应用程序的基础,现在大部分的网络应用程序都基于Socket通信的,网络通信协议一共有两种,一种是基于OSI的7层参考模型,而另一种则是TCP/IP协议的4层通信模型,在我们开发环境中会使用TCP/IP作为通信基础,接下来将具体介绍两种通信协议模型.

网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的.中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理.

◆OSI 参考模型◆

1983年,国际标准化组织(International Organization for standardization,ISO)发布了著名的ISO/IEC 7498标准,也就是开放式系统互连参考模型OSI,这个标准定义了网络的七层框架,试图使计算机在整个世界范围内实现互联,其中这七层分别的含义如下:

物理层次 协议作用
应用层 为用户提供服务,给用户一个接口
表示层 为数据提供表示,加密与压缩
会话层 确定数据是否需要传输,建立管理与终止会话
传输层 可靠与不可靠的数据传输,以及后期的错误检测
网络层 进行逻辑地址编址,实现不同网络之间的路由选择
数据链路层 进行硬件(MAC)地址编址有,差错的校验
物理层 电气特性,设备之间进行比特流的传输

以上的列表是一个通用的网络系统模型,并不是一个协议定义.实际上OSI模型从来没有被真正实现过,但是,出于其模型的广泛指导性,现在的网络协议都已经纳入了OSI参考模型的范围之内,OSI参考模型一共有7层,每层的作用在上面有说明,这也是网络方面的基础知识.

◆TCP/IP 参考模型◆

其实早在OSI模型出现之前,就已经有了TCP/IP的研究和实现,时间最早可以追溯到20世纪70年代,为互联网的最旱的通信协议,TCP为传输层的协议,而IP则为网络层的协议,两个层次中有代表性的协议组合代表了一系列的协议族,还包括有 ARP、ICMP 和 UDP 协议等,山于TCP/IP协议出现的比OSI早,所以井不符合OSI模型,他们的对应关系是这样的,如下表所示.

OSI 7层参考模型 TCP/IP 4层参考模型
应用层 应用层
表示层 应用层
会话层 应用层
传输层 传输层
网络层 互联网层
数据链路层 网络接口层
物理层 网络接口层

以上的两个列表左边是OSI模型,右边是TCP/IP模型,从列表可以看到,TCP/IP模型并不关心IP层以下的组成,而是将数据输出统一成了网络接口层,这样一来,IP层只需要将数据发往网络接口层就可以了,而不需要关心下层具体的操作.而在OSI模型中,则将这些功能分成了数据链路层和物理层,而且还进行了进一步的划分,在传输层和网络层大部分还是一致的,而对于OSI中的上面三层,则在TCP/IP模型中合将其合并成了应用层.

在现在的互联网中,主要采用TCP/IP协议,这已经成为了互联网上通信的事实标准,现在TCP/IP协议已经可以运行在各种信道和底层协议之上.

◆Socket 基础知识◆

套接字(Sockct)随着 TCP/IP协议的使用,也越来越多地被使用在网络应用程序的构建中,实际上 Socket编程也已经成为了网络中传送和接收数据的首选方法,套接字最早是由伯克利在BSD中推出的一种进程间通信方案和网络互联的基本机制,现在已经有多种相关的套接字实现,但大部分还是遵循着最初的设计要求.

Socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求,Socket起源于Unix而Unix/Linux基本哲学之一就是"一切皆文件",Socket就是文件模式的一个实现,Socket即是一种特殊的文件,一些Socket函数就是对其进行的操作(读/写IO、打开、关闭)等.

Pythhon 标准库中支持套接口的模块是Socket,其中包含生成套接字、等待连接、建立连接和传输数据的方法,任何应用程序需要使用套接字,都必须调用Socket方法生成一个套接字对象,对于服务器端而言,首先需要调用 bind 方法绑定一个套接口地址,接着使用 listen 方法开始监听客户端请求.当有客户端请求过来的时候,将通过 accept 方法来生成一个连接对象,然后就可以通过此连接对发送和接收数据了,数据传输完毕后可以调用 close 方法将生成的连接关闭.

服务端与客户端

Socket 常用地址簇:
socket.AF_UNIX unix进程间通信
socket.AF_INET ipv4
socket.AF_INET6 ipv6

Socket 常用地址簇:
socket.SOCK 裸套接字
socket.SOCK_STREAM TCP通信
socket.SOCK_DGRAM UDP通信
SOCK_RAW 原始套接字,可处理ICMP、IGMP等网络报文
SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序
SOCK_SEQPACKET 可靠的连续数据包服务

常用方法:

方法名称 方法说明
sk.socket() 创建一个套接字对象,并返回套接字相关内容:(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(address) 将套接字绑定到地址,在AF_INET下,以元组(host,port)的形式表示地址
sk.listen(5) 开始监听传入连接,且最大接收5个链接请求,请求数可根据机器性能动态调整
sk.setblocking(bool) 是否阻塞,如果设置为False,那accept和recv时一旦无数据,则报错
sk.accept() 接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据,address是连接客户端的地址,接收TCP客户的连接(阻塞式)等待连接的到来
sk.connect(address) 连接到address处的套接字,一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误
sk.connect_ex(address) 同上,只不过会有返回值,连接成功时返回0,连接失败时候返回编码,例如:10061
sk.close() 关闭套接字
sk.recv(bufsize[,flag]) 接受套接字的数据.数据以字符串形式返回,bufsize指定最多可以接收的数量,flag提供有关消息的其他信息,通常可以忽略
sk.recvfrom(bufsize[.flag]) 与recv()类似,但返回值是(data,address),其中data是包含接收数据的字符串,address是发送数据的套接字地址
sk.send(bytes[,flag]) 将string中的数据发送到连接的套接字.返回值是要发送的字节数量,该数量可能小于string的字节大小.即:可能未将指定内容全部发送
sk.sendall(bytes[,flag]) 将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据.成功返回None,失败则抛出异常,内部通过递归调用send,将所有内容发送出去
sk.sendto(bytes[,flag],address) 将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址.返回值是发送的字节数,该函数主要用于UDP协议
sk.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒.值为None表示没有超时期.一般超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s)
sk.getpeername() 返回连接套接字的远程地址,返回值通常是元组(ipaddr,port)
sk.getsockname() 返回套接字自己的地址,通常是一个元组(ipaddr,port)
sk.fileno() 套接字的文件描述符

◆实现TCP传输◆

服务端: 首先启动服务端,然后服务端会创建套接字,并绑定localhost:9999端口,设置最大连接数为5,然后发送数据.

import socketip_addr=("localhost",9999)server = socket.socket()                #创建套接字对象
server.bind(ip_addr)                    #绑定IP地址和端口,必须是元组形式
server.listen(5)                        #设置连接池挂起的数量,并发选项while True:conn,addr=server.accept() #接收客户连接,conn是客户端连接服务端的信号,addr客户端ip,portwhile True:try:send_data=input("请输入要发送的命令:").strip()if len(send_data) == 0: continue               #如果发送数据为空,则继续下次循环conn.send(bytes(send_data,encoding="utf-8"))   #发送一条消息,格式是bytes,utf-8recv_data=conn.recv(1024)                      #conn.recv接收客户端信息前1024字节print(str(recv_data,encoding="utf-8"))         #将数据转换成字符串,并使用UTF8编码except Exception:breakserver.close()

客户端: 客户端启动后,创建套接字,并主动连接localhost:9999端口,等待接收数据,并根据接收到的数据做回复.

import socket
import osip_addr=("localhost",9999)client=socket.socket()                                  #创建套接字对象
client.connect(ip_addr)                                 #指定连接的服务器地址while True:try:recv_data=client.recv(1024)                     #接收服务端前1024字节数据data = os.popen(str(recv_data, encoding="utf-8")).read()print("执行命令:",recv_data)                     #客户端返回执行命令if len(data) == 0: continue                     #发送数据为空,则退出client.send(bytes(data, encoding="utf-8"))      #返回执行结果,反向发送except Exception:breakclient.close()

◆实现UDP传输◆

UDP服务端: UDP服务端绑定端口,并等待接收数据,收到数据后返回一个OK作为结束.

import socketip_addr = ("localhost",9999)
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
server.bind(ip_addr)while True:data,(host,port) = server.recvfrom(1024)                     #等待接收数据print(data,host,port)                                        #打印数据server.sendto(bytes("ok",encoding="utf-8"),(host,port))

UDP客户端: 客户端绑定IP地址,然后发送数据,并等待服务端回应OK作为结束.

import socketip_addr = ("localhost",9999)
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)while True:temp = input("请输入发送数据:").strip()if temp == "exit": breakclient.sendto(bytes(temp,encoding="utf-8"),ip_addr)          #UDP发送数据data = client.recvfrom(1024)                                 #等待接收数据print(data)client.close()

◆实现文件传输◆

服务端: 首先服务端打开文件并计算大小后,发送给客户端文件的大小,并等待传输数据.

import socketip_addr=("localhost",8888)server=socket.socket()
server.bind(ip_addr)
server.listen(5)conn,addr=server.accept()# 打开文件,并发送文件大小...
f=open("wang.txt","r").read()
conn.send(bytes(str(len(f)),encoding="utf-8"))          #计算文件大小,并发送给远程
print("发送文件大小完成:",len(f))                        #打印一下,看结果...conn.send(bytes(f,encoding="utf-8"))                    #开始发送数据

客户端: 客户端收到文件大小后,开始接收数据,每次接收完数据以后累加,直到全部传输完成以后退出循环.

import socket
import osip_addr=("localhost",8888)client=socket.socket()
client.connect(ip_addr)#接收文件大小,并保存在变量里
file_size=int(client.recv(1024))        #保存文件的大小
max_size=0                              #定义一个变量,用于做比较
print("这个文件大小为:",file_size)f=open("file.txt","wb")while file_size != max_size:           #如果不相等,则说明文件没有传输完data=client.recv(1024)             #每次传输1024字节f.write(data)max_size=max_size+len(data)        #将传输数据累加到变量print("本次传输:",max_size)print("传输完成....")

传输文件(简单写法): Socket也可以实现远程文件的传输,比如以下代码就实现了服务端向客户端传递数据的过程.

#===============================================================
#                传输服务端
import socketserver = socket.socket()
server.bind(("localhost",8888))
server.listen(5)conn,addr=server.accept()
f=open("lyshark.mp4","r").read()
conn.sendall(bytes(f,encoding="utf-8"))
#===============================================================
#               传输客户端
import socket
import osclient=socket.socket()
client.connect(("localhost",8888))f=open("lyshark.mp4","wb")
data=client.recv(10240)
f.write(data)

传输文件(完整写法):

#===============================================================
#           客户端import socket
import os
import jsonip_addr=("127.0.0.1",9999)
client=socket.socket()
client.connect(ip_addr)def file_put(filedir):if os.path.isfile(filedir):file_name = filedir                                                #指定文件名称file_size = os.stat(file_name).st_size                             #计算文件大小file_msg = {"action":"put","name":file_name,"size":file_size}      #构建一个json文件client.send(bytes(json.dumps(file_msg),encoding="utf-8"))          #发送传输需要的数据print("文件名: %s --> 文件大小: %s "%(file_name,file_size))with open(file_name,"rb") as f:for line in f:client.send(line)print("文件已发送: %s" % len(line))print("文件发送完成...")file_put("文件名称.txt")        #调用函数,传输文件#===============================================================
#           服务端
import socket
import jsonip_addr=("127.0.0.1",9999)server=socket.socket()
server.bind(ip_addr)
server.listen(5)conn,addr=server.accept()file_msg=conn.recv(1024)
msg_data = json.loads(file_msg)if msg_data.get("action") == "put":          #是PUT则执行上传file_name = msg_data.get("name")file_size = msg_data.get("size")recv_size = 0with open(file_name, "wb") as f:while recv_size != file_size:data = conn.recv(1024)f.write(data)recv_size += len(data)print("文件大小: %s 传输大小: %s" % (file_size, recv_size))print("文件 %s 传输成功..." % file_size)elif msg_data.get("action") == "get":          #是GET则执行上传print("这里写下载的过程..")

Socket Server

SocketServer 是标准库中一个高级的网络处理模块,用于简化网络客户与服务器的实现(在前面使用Socket的过程中,我们先设置了Socket的类型,然后依次调用bind(),listen(),accept()最后使用while循环来让服务器不断的接受请求,而这些步骤可以通过SocketServer包来简化),模块中已经实现了一些可供使用的类.

SocketServer 内部使用IO多路复用以及"多线程"和"多进程",从而实现并发处理多个客户端请求的Socket服务端,即每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个"线程"或"进程"专门负责处理当前客户端的所有请求,底层还是对socket进行了封装和加入线程、进程就实现了进一步对Socket函数的二次封装.

◆简单的线程交互◆

服务端:

import socketserverclass MyServer(socketserver.BaseRequestHandler):                    #定义方法,并继承Base基类def handle(self):                                               #默认执行handle里面的过程conn = self.requestconn.sendall(bytes("服务端链接正常...",encoding="utf-8"))    #发送服务端正常print("客户端IP:%s 过来访问了..."%str(self.client_address))  #显示过来访问的IP地址while True:try:data = conn.recv(1024)                             #接收1024字节的数据if len(data)==0:continueprint("收到数据:%s"%str(data,encoding="utf-8"))     #打印收到的数据except Exception:continueif __name__ == "__main__":                                         #程序的开头server = socketserver.ThreadingTCPServer(("127.0.0.1",9999),MyServer)server.serve_forever()

客户端:

import socketip_addr = ("127.0.0.1",9999)client = socket.socket()                             #加载套接字
client.connect(ip_addr)                              #绑定地址msg = client.recv(1024)
print(str(msg,encoding="utf-8"))while True:try:data=input("输入发送数据:").strip()client.send(bytes(data,encoding="utf-8"))except Exception:continue

◆高级多线程交互◆

服务端:

import socketserver
import os
import sysuser=["admin","guest","lyshark"]
pasd=["1233","123123","123456"]def login(x,y):for i in range(len(user)):if x == user[i] and y == str(pasd[i]):return 1else:continuereturn 0class MyServer(socketserver.BaseRequestHandler):def handle(self):conn=self.requestconn.send(bytes("服务端初始化完毕....",encoding="utf-8"))print("客户:"+str(self.client_address)+"过来访问了...")username=str(conn.recv(1024),encoding="utf-8")password=str(conn.recv(1024),encoding="utf-8")print("用户%s:登陆密码%s:"%(username,password))ret=login(username,password)                          #登陆验证if ret == 1:print("登陆完成....")conn.send(bytes("登陆成功,你的用户名密码正确...",encoding="utf-8"))else:print("登陆失败...")conn.send(bytes("登陆失败,你的用户名密码错误...", encoding="utf-8"))if __name__ == "__main__":server = socketserver.ThreadingTCPServer(("127.0.0.1",9999),MyServer)server.serve_forever()

客户端:

import socketclient=socket.socket()
client.connect(("127.0.0.1",9999))msg=client.recv(1024)                      #初始化
print(str(msg,encoding="utf-8"))           #接收服务端传来的消息username=input("用户名:").strip()          #输入用户名和密码
passworld=input("用户密码:").strip()client.send(bytes(username,encoding="utf-8"))
client.send(bytes(passworld,encoding="utf-8"))ret=client.recv(1024)                      #返回状态
print(str(ret,encoding="utf-8"))

异步IO数据通信

在上面的实例中除了SocketServer的内容外,其他的服务器端的实现都是同步的,也就是说,服务程序只有处理完一个连接后,才能处理另外一个连接,如果需要让服务器端应用程序能够同时处理多个连接,则需要使用异步通信方式.

当同时有多个连接的时候,采用SocketServer和线程的方式都可以,但是对于那种持续时间长且数据突发的多连接,前面的这些处理方式所占用的资源太大,一种改进的方式是在一定的时间段内查看已有的连接并处理,处理的过程包括读取数据和发送数据,在 Python 标准库中包含了一种专门的异步IO通信方式,它就是select模块.

同步机制 && 异步机制

● 同步机制:调用发出之后不会立即返回,进程会一直询问内核准备好数据没有,但一旦返回,则内核返回最终结果,数据已经从内核内存复制到进程内存了,这种方式没有通知机制.
● 异步机制:调用发出之后,被调用方立即返回消息,但返回的并非最终结果,被调用者通过状态、通知机制来通知调用者,或通过回调函数来处理,这种方式有有通知机制.

阻塞IO && 非阻塞IO

● 阻塞IO:当用户线程发起一个IO请求操作,内核会去查看要读取的数据是否就绪,如果数据没有就绪,则会一直等待,直到数据就绪,当数据就绪之后,便将数据拷贝到用户线程,最后结束整个过程.
● 非阻塞IO:当用户线程发起一个IO请求操作,内核会去查看要读取的数据是否就绪,如果数据没有就绪,如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪.
● 阻塞(blocking IO),非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息.

常用异步机制 Select,Poll,Epoll

● select 最早于1983年出现在4.2BSD中,目前所有的平台上都支持,但select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024个文件.
● poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制,它的开销随着文件描述符数量的增加而增大.
● epoll 直到Linux2.6才出现了由内核直接支持的实现方法,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法,理论上边缘触发的性能要更高一些,但是代码实现相当复杂.

◆Select◆

Select 的使用方法是监视指定的文件描述符,并在文件描述符集改变的时候做出响应,在Python标准库中,具体的实现了Select模块中的Select方法,这实际上也是Select系统调用的一个接口.

Select 服务端: 使用Select模块构建并发环境,在单线程下实现多并发的要求.

import socket
import select
import queueserver = socket.socket()
server.bind(("0.0.0.0",9999))
server.listen(100)
server.setblocking(False)inputs = [server,]      #建立的连接请求,server是本机的意思
outputs = []
msg_dic = {}            #创建一个空字典,用来存放发送的消息while True:              # ↓ input=接收,output=发送,excep=异常read,write,excep = select.select(inputs,outputs,inputs)         #select负责监听连接请求#print("当前的连接实例:",read)                                    #打印出当前的连接实例for ret in read:if ret is server:       #如果ret=server实例,实例本身被访问,说明来了新的连接请求conn,addr = server.accept()print("来了一个新连接:",addr)inputs.append(conn)            #将这个新的连接实例,加入到select的监测列表中.msg_dic[conn] = queue.Queue()  #初始化一个队列,后面存要返回给这个客户端的数据else:                              #否则准备接收老连接发来的请求data = ret.recv(1024)print("收到的数据:",data)msg_dic[ret].put(data)         #放入队列中,等待下次循环outputs.append(ret)            #放入发送列表中,下次循环将发送这条数据# -----------------------------------------------------------------------#      针对主机的发送环节.for w in write:                        #要返回给客户端的连接列表data_to_client = msg_dic[w].get()  #获取需要发送的数据w.send(data_to_client)             #返回给客户端源数据outputs.remove(w)    #确保下次循环的时候writeable,不返回这个已经处理完的连接了#-----------------------------------------------------------------------#      异常主机自动清理环节for e in excep:             #循环检测,如果主机异常断开,则从inputs主机列表中自动移除if e in outputs:        #保证下次select 监测的时候,不在检测这个异常的链接请求outputs.remove(e)if e in inputs:inputs.remove(e)

Select 客户端: 客户端直接绑定服务端的IP地址,只需要简单的实现数据的收发即可.

import socketclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("localhost",9999))while True:msg = input("发送数据:").strip()client.sendall(bytes(msg,encoding="utf-8"))data = client.recv(1024)print("收到数据:",data)
client.close()

此处的Socket服务端相比与原生的Socket,他支持当某一个请求不再发送数据时,服务器端不会等待而是可以去处理其他请求的数据.但是,如果每个请求的耗时比较长,select版本的服务器端也无法完成同时操作. 

◆Selectors◆

Selectors 模块,是在Python 3.x以后加入的新模块,其实就是在Select的基础之上进行了更加深入的封装,但是需要注意的是,Selectors模块会根据用户所在的平台的不同来选择性的使用select或者是epoll,对用户来说是透明的,但是还是需要注意这一点,但是此模块如果不做后端开发,一般也很少使用,这里了解即可.

服务端:

import selectors
import socketself = selectors.DefaultSelector()def accept(sock,mask):conn,addr = sock.accept()print('accepted', conn, 'from', addr,mask)conn.setblocking(False)self.register(conn, selectors.EVENT_READ, read)     #新连接注册read回调函数def read(conn, mask):data = conn.recv(1024)    #返回数据if data:print('echoing', repr(data), 'to', conn)conn.send(data)else:print('closing', conn)self.unregister(conn)conn.close()sock = socket.socket()
sock.bind(('localhost',6666))
sock.listen(100)
sock.setblocking(False)
self.register(sock,selectors.EVENT_READ,accept)while True:events = self.select()           #默认阻塞,有活动连接就返回活动的连接列表for key, mask in events:callback = key.data          #acceptcallback(key.fileobj, mask)  #key.fileobj= 文件句柄

客户端:

import socketclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("localhost",6666))while True:msg = input("发送数据:").strip()client.sendall(bytes(msg,encoding="utf-8"))data = client.recv(1024)print("收到数据:",data)
client.close()

TCP 实现的通信

client

import socket
import sys
import selectors
import types# 测试类
class Client:def __init__(self, host, port, numConn):self.host = host  # 待连接的远程主机的域名self.port = portself.message = [b'message 1 from client', b'message 2 from client']self.numConn = numConnself.selector = selectors.DefaultSelector()def connet(self):  # 连接方法server_addr = (self.host, self.port)for i in range(0, self.numConn):connid = i + 1print('开始连接', connid, '到', server_addr)sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.setblocking(False)sock.connect_ex(server_addr)#连接服务端events = selectors.EVENT_READ | selectors.EVENT_WRITEdata = types.SimpleNamespace(connid=connid,msg_total=sum(len(m) for m in self.message),recv_total=0,messages=list(self.message),outb=b'')self.selector.register(sock, events, data=data)try:while True:events = self.selector.select(timeout=1)if events:for key, mask in events:self.service_connection(key, mask)finally:self.selector.close()def service_connection(self,key, mask):sock = key.fileobjdata = key.dataif mask & selectors.EVENT_READ:recv_data = sock.recv(1024)  if recv_data:print("收到", repr(recv_data), "来自连接", data.connid)data.recv_total += len(recv_data)if not recv_data or data.recv_total == data.msg_total:print("关闭连接:", data.connid)self.selector.unregister(sock)sock.close()if mask & selectors.EVENT_WRITE:if not data.outb and data.messages:data.outb = data.messages.pop(0)if data.outb:print("发送", repr(data.outb), "到连接", data.connid)sent = sock.send(data.outb)  #发送数据data.outb = data.outb[sent:]#清空数据if __name__ == '__main__':cl = Client('127.0.0.1', 8800, 5)cl.connet()

server

import socket
import sys
import selectors
import typesclass server:def __init__(self,ip,port):self.port=portself.ip=ipself.selector  = selectors.DefaultSelector()#初始化selectordef start(self):s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)try:s.bind((self.ip,self.port))s.listen()print('等待连接:',(self.ip,self.port))s.setblocking(False) # 非阻塞self.selector.register(s,selectors.EVENT_READ,None)#注册I/O对象while True:events = self.selector.select(timeout=None)#阻塞调用,等待新的读/写事件for key, mask in events:if key.data is None:#新的连接请求self.accept_wrapper(key.fileobj)else:#收到客户端连接发送的数据self.service_connection(key, mask)except socket.error as e:print(e)sys.exit()finally:s.close() #关闭服务端def accept_wrapper(self,sock):conn, addr = sock.accept()  # Should be ready to readprint('接收客户端连接', addr)conn.setblocking(False) #非阻塞data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')#socket数据events = selectors.EVENT_READ | selectors.EVENT_WRITE #监听读写self.selector.register(conn, events, data=data)#注册客户端socketdef service_connection(self,key, mask):sock = key.fileobjdata = key.dataif mask & selectors.EVENT_READ:recv_data = sock.recv(1024)  # 接收数据if recv_data:data.outb += recv_dataelse:#客户端断开连接print('关闭连接', data.addr)self.selector.unregister(sock)#取消注册,防止出错sock.close()if mask & selectors.EVENT_WRITE:if data.outb:print('发送', repr(data.outb), '到', data.addr)sent = sock.send(data.outb)  data.outb = data.outb[sent:]if __name__ == '__main__':s = server('',8800)s.start()

lib套接字通信

server

import sys
import selectors
import json
import io
import structrequest_search = {"morpheus": "Follow the white rabbit. \U0001f430","ring": "In the caves beneath the Misty Mountains. \U0001f48d","\U0001f436": "\U0001f43e Playing ball! \U0001f3d0",
}class Message:def __init__(self, selector, sock, addr):self.selector = selectorself.sock = sockself.addr = addrself._recv_buffer = b""self._send_buffer = b""self._jsonheader_len = Noneself.jsonheader = Noneself.request = Noneself.response_created = Falsedef _set_selector_events_mask(self, mode):if mode == "r":events = selectors.EVENT_READelif mode == "w":events = selectors.EVENT_WRITEelif mode == "rw":events = selectors.EVENT_READ | selectors.EVENT_WRITEelse:raise ValueError(f"Invalid events mask mode {repr(mode)}.")self.selector.modify(self.sock, events, data=self)def _read(self):try:data = self.sock.recv(4096)except BlockingIOError:passelse:if data:self._recv_buffer += dataelse:raise RuntimeError("Peer closed.")def _write(self):if self._send_buffer:print("sending", repr(self._send_buffer), "to", self.addr)try:sent = self.sock.send(self._send_buffer)except BlockingIOError:passelse:self._send_buffer = self._send_buffer[sent:]if sent and not self._send_buffer:self.close()def _json_encode(self, obj, encoding):return json.dumps(obj, ensure_ascii=False).encode(encoding)def _json_decode(self, json_bytes, encoding):tiow = io.TextIOWrapper(io.BytesIO(json_bytes), encoding=encoding, newline="")obj = json.load(tiow)tiow.close()return objdef _create_message(self, *, content_bytes, content_type, content_encoding):jsonheader = {"byteorder": sys.byteorder,"content-type": content_type,"content-encoding": content_encoding,"content-length": len(content_bytes),}jsonheader_bytes = self._json_encode(jsonheader, "utf-8")message_hdr = struct.pack(">H", len(jsonheader_bytes))message = message_hdr + jsonheader_bytes + content_bytesreturn messagedef _create_response_json_content(self):action = self.request.get("action")if action == "search":query = self.request.get("value")answer = request_search.get(query) or f'No match for "{query}".'content = {"result": answer}else:content = {"result": f'Error: invalid action "{action}".'}content_encoding = "utf-8"response = {"content_bytes": self._json_encode(content, content_encoding),"content_type": "text/json","content_encoding": content_encoding,}return responsedef _create_response_binary_content(self):response = {"content_bytes": b"First 10 bytes of request: "+ self.request[:10],"content_type": "binary/custom-server-binary-type","content_encoding": "binary",}return responsedef process_events(self, mask):if mask & selectors.EVENT_READ:self.read()if mask & selectors.EVENT_WRITE:self.write()def read(self):self._read()if self._jsonheader_len is None:self.process_protoheader()if self._jsonheader_len is not None:if self.jsonheader is None:self.process_jsonheader()if self.jsonheader:if self.request is None:self.process_request()def write(self):if self.request:if not self.response_created:self.create_response()self._write()def close(self):print("closing connection to", self.addr)try:self.selector.unregister(self.sock)except Exception as e:print(f"error: selector.unregister() exception for",f"{self.addr}: {repr(e)}",)try:self.sock.close()except OSError as e:print(f"error: socket.close() exception for",f"{self.addr}: {repr(e)}",)finally:self.sock = Nonedef process_protoheader(self):hdrlen = 2if len(self._recv_buffer) >= hdrlen:self._jsonheader_len = struct.unpack(">H", self._recv_buffer[:hdrlen])[0]self._recv_buffer = self._recv_buffer[hdrlen:]def process_jsonheader(self):hdrlen = self._jsonheader_lenif len(self._recv_buffer) >= hdrlen:self.jsonheader = self._json_decode(self._recv_buffer[:hdrlen], "utf-8")self._recv_buffer = self._recv_buffer[hdrlen:]for reqhdr in ("byteorder","content-length","content-type","content-encoding",):if reqhdr not in self.jsonheader:raise ValueError(f'Missing required header "{reqhdr}".')def process_request(self):content_len = self.jsonheader["content-length"]if not len(self._recv_buffer) >= content_len:returndata = self._recv_buffer[:content_len]self._recv_buffer = self._recv_buffer[content_len:]if self.jsonheader["content-type"] == "text/json":encoding = self.jsonheader["content-encoding"]self.request = self._json_decode(data, encoding)print("received request", repr(self.request), "from", self.addr)else:self.request = dataprint(f'received {self.jsonheader["content-type"]} request from',self.addr,)self._set_selector_events_mask("w")def create_response(self):if self.jsonheader["content-type"] == "text/json":response = self._create_response_json_content()else:response = self._create_response_binary_content()message = self._create_message(**response)self.response_created = Trueself._send_buffer += message

client

import sys
import selectors
import json
import io
import structclass Message:def __init__(self, selector, sock, addr, request):self.selector = selectorself.sock = sockself.addr = addrself.request = requestself._recv_buffer = b""self._send_buffer = b""self._request_queued = Falseself._jsonheader_len = Noneself.jsonheader = Noneself.response = Nonedef _set_selector_events_mask(self, mode):if mode == "r":events = selectors.EVENT_READelif mode == "w":events = selectors.EVENT_WRITEelif mode == "rw":events = selectors.EVENT_READ | selectors.EVENT_WRITEelse:raise ValueError(f"Invalid events mask mode {repr(mode)}.")self.selector.modify(self.sock, events, data=self)def _read(self):try:data = self.sock.recv(4096)except BlockingIOError:passelse:if data:self._recv_buffer += dataelse:raise RuntimeError("Peer closed.")def _write(self):if self._send_buffer:print("sending", repr(self._send_buffer), "to", self.addr)try:sent = self.sock.send(self._send_buffer)except BlockingIOError:passelse:self._send_buffer = self._send_buffer[sent:]def _json_encode(self, obj, encoding):return json.dumps(obj, ensure_ascii=False).encode(encoding)def _json_decode(self, json_bytes, encoding):tiow = io.TextIOWrapper(io.BytesIO(json_bytes), encoding=encoding, newline="")obj = json.load(tiow)tiow.close()return objdef _create_message(self, *, content_bytes, content_type, content_encoding):jsonheader = {"byteorder": sys.byteorder,"content-type": content_type,"content-encoding": content_encoding,"content-length": len(content_bytes),}jsonheader_bytes = self._json_encode(jsonheader, "utf-8")message_hdr = struct.pack(">H", len(jsonheader_bytes))message = message_hdr + jsonheader_bytes + content_bytesreturn messagedef _process_response_json_content(self):content = self.responseresult = content.get("result")print(f"got result: {result}")def _process_response_binary_content(self):content = self.responseprint(f"got response: {repr(content)}")def process_events(self, mask):if mask & selectors.EVENT_READ:self.read()if mask & selectors.EVENT_WRITE:self.write()def read(self):self._read()if self._jsonheader_len is None:self.process_protoheader()if self._jsonheader_len is not None:if self.jsonheader is None:self.process_jsonheader()if self.jsonheader:if self.response is None:self.process_response()def write(self):if not self._request_queued:self.queue_request()self._write()if self._request_queued:if not self._send_buffer:self._set_selector_events_mask("r")def close(self):print("closing connection to", self.addr)try:self.selector.unregister(self.sock)except Exception as e:print(f"error: selector.unregister() exception for",f"{self.addr}: {repr(e)}",)try:self.sock.close()except OSError as e:print(f"error: socket.close() exception for",f"{self.addr}: {repr(e)}",)finally:self.sock = Nonedef queue_request(self):content = self.request["content"]content_type = self.request["type"]content_encoding = self.request["encoding"]if content_type == "text/json":req = {"content_bytes": self._json_encode(content, content_encoding),"content_type": content_type,"content_encoding": content_encoding,}else:req = {"content_bytes": content,"content_type": content_type,"content_encoding": content_encoding,}message = self._create_message(**req)self._send_buffer += messageself._request_queued = Truedef process_protoheader(self):hdrlen = 2if len(self._recv_buffer) >= hdrlen:self._jsonheader_len = struct.unpack(">H", self._recv_buffer[:hdrlen])[0]self._recv_buffer = self._recv_buffer[hdrlen:]def process_jsonheader(self):hdrlen = self._jsonheader_lenif len(self._recv_buffer) >= hdrlen:self.jsonheader = self._json_decode(self._recv_buffer[:hdrlen], "utf-8")self._recv_buffer = self._recv_buffer[hdrlen:]for reqhdr in ("byteorder","content-length","content-type","content-encoding",):if reqhdr not in self.jsonheader:raise ValueError(f'Missing required header "{reqhdr}".')def process_response(self):content_len = self.jsonheader["content-length"]if not len(self._recv_buffer) >= content_len:returndata = self._recv_buffer[:content_len]self._recv_buffer = self._recv_buffer[content_len:]if self.jsonheader["content-type"] == "text/json":encoding = self.jsonheader["content-encoding"]self.response = self._json_decode(data, encoding)print("received response", repr(self.response), "from", self.addr)self._process_response_json_content()else:self.response = dataprint(f'received {self.jsonheader["content-type"]} response from',self.addr,)self._process_response_binary_content()self.close()

未完待续

server

import os,time
import socket
import threadingsock = ["0", "0"]def client_hander(socket,addr):try:while True:if sock[0] != "0" and sock[1] != "0" and sock[0] == addr[0]:aaa = socket.recv(4096)print(aaa)while True:bb = input()socket.send(bytes(bb,encoding="utf-8"))except Exception:socket.close()passif __name__ == "__main__":server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(("0.0.0.0", 9999))server.listen(10)while True:client_socket, addr = server.accept()client_thread = threading.Thread(target=client_hander, args=(client_socket,addr,))client_thread.start()ip = input("aa")sock[0] = ipsock[1]="1"

client

import os,socketclient = socket.socket()
client.connect(("192.168.1.2",9999))
client.send(bytes("ipconfig\r\n",encoding="utf-8"))while True:data = client.recv(1024)print(data)

Python Socket套接字编程相关推荐

  1. 自学Python 58 Socket套接字编程 (一)

    Python Socket套接字编程 (一) 文章目录 Python Socket套接字编程 (一) 一.库 Socket 内置函数和属性 二.Python中的socket通信逻辑   Socket又 ...

  2. 基于UDP协议的socket套接字编程 基于socketserver实现并发的socket编程

    基于UDP协议 的socket套接字编程 1.UDP套接字简单示例 1.1服务端 import socketserver = socket.socket(socket.AF_INET,socket.S ...

  3. Linux之socket套接字编程20160704

    介绍套接字之前,我们先看一下传输层的协议TCP与UDP: TCP协议与UDP协议的区别 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UD ...

  4. TCP与UDP协议,socket套接字编程,通信相关操作

    文章目录 TCP与UDP协议 TCP协议 ==三次握手== ==四次挥手== UDP协议 TCP与UDP的区别 应用层 socket套接字 代码优化 循环通信 半连接池 粘包问题 TCP与UDP协议 ...

  5. Socket套接字编程

    一.Socket是什么 1. socket套接字: socket起源于Unix,而Unix/Linux基本哲学之一就是"一切皆文件",都可以用"打开open –> ...

  6. TCP Socket套接字编程 附源码

    TCP 最主要的特点 TCP 是面向连接的运输层协议.应用程序在使用 TCP 协议之前,必须先建立 TCP 连接.在传送数据完毕后,必须释放已经建立的 TCP 连接 每一条 TCP 连接只能有两个端点 ...

  7. TCP与UDP协议、socket套接字编程、通信相关操作(cs架构软件)、TCP黏包问题及解决思路

    OSI七层协议 传输层 1.PORT协议:前面讲过 2.TCP协议与UDP协议:规定了数据传输所遵循的规则(数据传输能够遵循的协议有很多,TCP和UDP是较为常见的两个) TCP协议 基于TCP传输数 ...

  8. Python之套接字编程

    套接字:程序在网络间进行数据传输的一种技术手段,Python使用socket模块. 流式套接字(SOCK_STREAM):采用TCP协议,以字节流方式实现数据传输(面向连接.可靠) 数据报套接字(SO ...

  9. iOS socket 套接字编程

    服务端: #import "ViewController.h"#import <sys/socket.h> #import <netdb.h>@interf ...

最新文章

  1. 几个定制 iTerm2 的 tip
  2. 005-Python之列表,元组,字符串,bytes及bytearray共有操作
  3. 比特币分叉两年记:聊聊当年为什么要分叉?
  4. 动态规划----解121. 买卖股票的最佳时机---最大连续子序列和
  5. jenkis编译报错:需要class,interface或enum
  6. 互联网公司GitHub repo 语言使用情况
  7. 机器学习(Machine Learning)- 吴恩达(Andrew Ng)-学习笔记
  8. 硅谷大佬提前剧透未来!6本书,助你走在AI时代前沿
  9. sklearn 神经网络_机器学习100天-Day2404 循环神经网络RNN(预测时间序列)
  10. iOS-高仿支付宝手势解锁(九宫格)
  11. 电脑f2还原系统步骤_详解电脑重装系统步骤
  12. 学计算机是为了什么演讲稿,求大学计算机协会职位申请书和演讲稿
  13. 压力测试就是一种破坏性的性能测试
  14. 2022年考研数据结构_5 树
  15. 移动金库模式保护客户信息 覆盖17套关键系统
  16. 《深入浅出通信原理》一句话短评
  17. rpcbind 、nfs无法启动问题
  18. 火力发电行业三大知识图谱应用场景,助力火力发电厂清洁高效智慧化运营
  19. ASPUPload3.0注册机
  20. Tegra Nano上手

热门文章

  1. 直播软件源码如何在Android端实现多人视频通话
  2. 墙角下的toLowerCase()
  3. android如何实现qq登陆和qq分享,2021超详细mob平台图解教程?
  4. 数据治理体系化思考与实践
  5. 学习Python,怎能不懂点PEP呢? 1
  6. PID算法实现及参数整定图解(附代码)
  7. oracle的账户无法注册表,oracle用户账号在哪注册表ORA-12514问题解决
  8. matlab非线性数值方程的求解
  9. 浙大版《python程序设计》第四章课后习题
  10. Red Hat Enterprise Linux 7 正式版发布