一、CS架构
server端要求:
1、力求一直提供服务
2、一个server端socket绑定到一个唯一的IP+端口地址,多个客户端发起connect各带一个conn套接字通道 去连接服务端。
二、socket
socket就是为了完成C/S架构软件的开发,但是如果是C/S架构的软件就一定需要解决双方通信问题。
若基于网络通信就需要了解复杂的网络协议 TCP/IP协议,于是socket出现了;
1、什么是sock?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的
2、socket分类
(1)基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信(Linux操作系统)
(2)基于网络类型的套接字家族 AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)(ssh,nginx,mysql)
3、soket套接字的工作流程
服务端:得到Socket对象,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
客户端:客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。
C/S交换阶段:客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
4、基于TCP协议的套接字
(1)实现一个简单的套接字
import socket phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个基于网络的(socket.AF_INET)tcp协议传输的sock(socket.SOCK_STREAM) phon.bind(("127.0.0.1",8090)) #把创建好的套接字,绑定在 唯一的IP和端口上。 phon.listen(80) #开始监听 conn,addr=phon.accept() #等待客户端通过TCP三次握手发起 连接 (把某个连接,和连接的对象分解赋值给 conn,和addr) data=conn.recv(1024) #接收 连接进来的某一个客户端,发送过来的消息 print("接受到来自客户端发来的消息",data) conn.send(data.upper()) #发送接受的消息 给某一个客户端
执行结果
服务端:
客户端:
(2)循环通信的套接字
虽然上述已经实现了一个简单的套接字,但服务端的 socket 只能接受 1个客户端的连接,1次通信后就结束了。
所以可以利用while..Ture构建一个通信循环,实现1个客户端可以和服务端一直通信(循环通信)
服务端:
import socket phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个基于网络的(socket.AF_INET)tcp协议传输的sock(socket.SOCK_STREAM) phon.bind(("127.0.0.1",8090)) #把创建好的套接字,绑定在 唯一的IP和端口上。 phon.listen(80) #开始监听 conn,addr=phon.accept() #等待客户端通过TCP三次握手发起 连接 (把某个连接,和连接的对象分解赋值给 conn,和addr) while True: #循环通信-----------------------------------------------------------------------> data=conn.recv(1024) #接收 连接进来的某一个客户端,发送过来的消息 print("接受到来自客户端发来的消息",data) conn.send(data.upper()) #发送接受的消息 给某一个客户端 conn.close() phon.close()
客户端:
import socket phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phon.connect(('127.0.0.1',8090)) #客户端的phon.connect正好对于服务端的phon1.accept() while True: #循环通信----------------------------------------------------------------------------------->> mes=input('---->: '.strip()) if not mes: continue res=phon.send(mes.encode('utf-8')) #发消息 data=phon.recv(1024) #收消息 print(data.decode('gbk')) phon.close()
3、循环连接的套接字
由于1客户端连接到服务端就会建立一个连接通道(电话线),但是如果客户端关闭了这个通道,服务端的conn.recv(1024)方法无法接收到客户端的数据。
在Windows平:台sock服务端就会报错,抛出异常;
在Linux平台:服务端循环收空数据,陷入死循环;
解决方法:建连接循环,捕捉到客户端关闭了连接通道,服务端继续accept()等待下一次连接;
tcp服务端
import socket import subprocess iphon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #(建立一个socket对象) iphon.bind(('127.0.0.1',8080)) #绑定到 IP+端口上 成为唯一的socket iphon.listen(5) #设置连接池的个数 print('starting........') while True: #连接循环------------------------------------------------------------------> conn,addr=iphon.accept() #等待电话连接 print('电话线路是',conn) print('客户手机号:',addr) while True: #通信循环 发送和接收 try: data=conn.recv(1024) #接受消息 最大从内存里接受1024MB数据 print('客户端发来的消息是%s'%data) data=data.decode('utf-8') #从客户端发来的数据是经过编码的字节数据 所以需要解码 成Unicode res=subprocess.Popen( data, shell=True, stdout=subprocess.PIPE) #解码后传进subprocess.Popen去cmd执行 data1 = res.stdout.read().decode('gbk') #cmd是gbk编码所以需要gbk解码 print(data1) #打印解码后的结果 data1=data1.encode('gbk') #编码 conn.send(data1) #发送消息 #编码后再传输给客户端 except Exception: break conn.close() iphon.close() #
TCP客户端
import socket phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phon.connect(('127.0.0.1',8080)) #客户端的phon.connect正好对于服务端的phon1.accept() while True: #循环通信 mes=input('---->: '.strip()) if not mes: continue res=phon.send(mes.encode('utf-8')) #发消息 data=phon.recv(1024) #收消息 print(data.decode('gbk')) phon.close()
4、由于客户端连接服务端断开后,仍然存在四次挥手的time_wait状态在占用地址(所以 服务器高并发情况下会有大量的time_wait状态的需要优化)
import socket phon=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个基于网络的(socket.AF_INET)tcp协议传输的sock(socket.SOCK_STREAM) phon.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #处理客户端连接断开,后的time_out-------------------------> phon.bind(("127.0.0.1",8090)) #把创建好的套接字,绑定在 唯一的IP和端口上。 phon.listen(80) #开始监听 conn,addr=phon.accept() #等待客户端通过TCP三次握手发起 连接 (把某个连接,和连接的对象分解赋值给 conn,和addr) while True: #循环通信 data=conn.recv(1024) #接收 连接进来的某一个客户端,发送过来的消息 print("接受到来自客户端发来的消息",data) conn.send(data.upper()) #发送接受的消息 给某一个客户端 conn.close() phon.close()
5、1个服务端套接字 和 多个客户端 套接字通信(socketserver模块实现并发 )
循环通信:解决了客户端和服务端可以持续发消息。
连接循环:解决了客户端 突然断开和服务端建立的通道,服务端无法继续和这个关闭了这个通道通信,抛异常退出的问题;
但此时的服务端套接字 还只能和1客户端建立通信管道,无法同时接收 多个客户端套接字的连接,实现并发;
socketserve模块执行流程:
#1、接收多个连接:一个客户端连接建立之后,服务端开一个socketserver.ThreadingTCPServe线程 实例化出一个FTP类的对象。
#2、自动执行Ftp对象handle方法
#3、自动执行对象 handle方法,进入通信循环,self.request就是这个连接客户端的套接字对象。
import socketserver #socketserver()模块基于 线程实现并发 class FTP(socketserver.BaseRequestHandler):#继承socketserver.BaseRequestHandler类 def handle(self): #2、自动执行对象 handle方法,进入通信循环, #3 self.request就是这个连接客户端的套接字对象 print('==========>') while True: #4、进入循环通信 data=self.request.recv(1024) #接收数据方法:self.request.recv(1024) print(data) self.request.send(data) #发送数据方法:self.request.send(data) if __name__ == '__main__': obj=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FTP)#1、接受多个连接:一个客户端连接建立之后,服务端开一个socketserver.ThreadingTCPServe线程 实 例化出一个FTP对象 obj.serve_forever() obj.serve_forever() #等于accpet()永远接收连接