python(socket)
C/S架构(客户端/服务器架构),分为硬件C/S架构(打印机)和软件C/S架构。
互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
各层常见物理设备
ip(找到所在局域网)+mac(找到物理主机)+端口(找到应用程序)绑定可以找到目标设备上的目标应用程序。
程序的PID:同一台机器上不同进程或线程的标识。
套接字工作原理
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
模拟客户端与服务端交互
客户端
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect_ex(ip_port) #连接到地址 s.send('linhaifeng nb'.encode('utf-8')) #发消息,编码方式 feedback=s.recv(BUFSIZE) #收消息 print(feedback.decode('utf-8')) s.close() #退出
服务端
# -*- coding: utf-8 -*- import socket ip_port=('127.0.0.1',9000) #通信地址 BUFSIZE=1024 #收发消息的尺寸 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #地址族和通信协议族 s.bind(ip_port) #绑定地址 s.listen(5) #可以接受最多5个 conn,addr=s.accept() #等待接受消息 # print(conn) # print(addr) print('接到来自%s的电话' %addr[0]) msg=conn.recv(BUFSIZE) #听消息,听话 print(msg,type(msg)) conn.send(msg.upper()) #发消息 conn.close() #关闭进程 s.close() #退出
运行客户端,输入信息,在服务端就可以看到
断链接四次握手上面两条线(客户端发起)表示主动断开连接,下面两条(服务端发起)表示被动断开连接。
因为刚开始建立连接没有数据,服务端应答客户端和向客户端发起请求可以并在一起。但是,在断开连接的时候,有可能有数据在上传或者下载,所以不能合并。
客户端循环接受信息
客户端
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect_ex(ip_port) #连接到地址 while True: msg=input('输入:') s.send(msg.encode('utf-8')) #发消息,编码方式 feedback=s.recv(BUFSIZE) #收消息 print(feedback.decode('utf-8')) s.close() #退出
服务端
# -*- coding: utf-8 -*- import socket ip_port=('127.0.0.1',9000) #通信地址 BUFSIZE=1024 #收发消息的尺寸 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #地址族和通信协议族 s.bind(ip_port) #绑定地址 s.listen(5) #可以接受最多5个 conn,addr=s.accept() #等待接受消息 # print(conn) # print(addr) print('接到来自%s的电话' %addr[0]) while True: msg=conn.recv(BUFSIZE) #听消息,听话 print('接收到来自客户端的消息',msg) conn.send(msg.upper()) #发消息 conn.close() #关闭进程 s.close() #退出
conn是一个套接字对象
服务端循环提供服务
客户端
#_*_coding:utf-8_*_ import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect_ex(ip_port) #连接到地址 while True: msg=input('输入:') if not msg:continue #解决只有回车没有数据卡住的情况 s.send(msg.encode('utf-8')) #发消息,编码方式 feedback=s.recv(BUFSIZE) #收消息 print(feedback.decode('utf-8')) s.close() #退出
服务端
# -*- coding: utf-8 -*- import socket ip_port=('127.0.0.1',9000) #通信地址 BUFSIZE=1024 #收发消息的尺寸 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #地址族和通信协议族 s.bind(ip_port) #绑定地址 s.listen(5) #可以接受最多5个 while True: #和多个用户通信 conn,addr=s.accept() #等待接受消息 # print(conn) # print(addr) print('接到来自%s的电话' %addr[0]) while True: #循环接收信息 try: msg=conn.recv(BUFSIZE) #听消息,听话 print('接收到来自客户端的消息',msg) conn.send(msg.upper()) #发消息 except Exception: break conn.close() #关闭进程 s.close() #退出
udp套接字
udp没有三次握手和四次握手,所以没有监听
客户端
#_*_coding:utf-8_*_ import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True: #做到循环发消息 msg=input('>>: ').strip() if not msg:continue #解决只有回车没有数据卡住的情况 udp_server_client.sendto(msg.encode('utf-8'),ip_port) #发信息的内容和端口 back_msg,addr=udp_server_client.recvfrom(BUFSIZE) #收到的消息和地址 print(back_msg.decode('utf-8'),addr)
服务端
# -*- coding: utf-8 -*- import socket ip_port=('127.0.0.1',9000) #地址和端口 BUFSIZE=1024 #字节 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_client.bind(ip_port) #端口绑定 while True: msg,addr=udp_server_client.recvfrom(BUFSIZE) #接受消息和地址 print(msg,addr) udp_server_client.sendto(msg.upper(),addr) #发消息和地址
时间服务器
客户端
#_*_coding:utf-8_*_ from socket import * ip_port=('127.0.0.1',9000) bufsize=1024 tcp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('请输入时间格式(例%Y %m %d)>>: ').strip() tcp_client.sendto(msg.encode('utf-8'),ip_port) data=tcp_client.recv(bufsize) print(data.decode('utf-8')) tcp_client.close()
服务端
# -*- coding: utf-8 -*- from socket import * from time import strftime ip_port = ('127.0.0.1', 9000) bufsize = 1024 tcp_server = socket(AF_INET, SOCK_DGRAM) tcp_server.bind(ip_port) while True: msg, addr = tcp_server.recvfrom(bufsize) print('===>', msg) if not msg: time_fmt = '%Y-%m-%d %X' else: time_fmt = msg.decode('utf-8') back_msg = strftime(time_fmt) tcp_server.sendto(back_msg.encode('utf-8'), addr) tcp_server.close()
基于TCP实现远程执行命令
服务端
# -*- coding: utf-8 -*- from socket import * import subprocess ip_port = ('127.0.0.1', 9000) back_log=5 buffer_size = 1024 tcp_server = socket(AF_INET, SOCK_STREAM) tcp_server.bind(ip_port) tcp_server.listen(back_log) while True: conn,addr = tcp_server.accept() print('新的客户端连接===>',addr) while True: try: #处理异常中断 cmd=conn.recv(buffer_size) print('客户端连接===>', cmd) #执行cmd命令 res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE) err=res.stdout.read() if err: #判断结果 cmd_res=err else: cmd_res=res.stdout.read() conn.send(cmd_res) except Exception as e: print(e) break conn.close() # tcp_server.close()
客户端
#_*_coding:utf-8_*_ from socket import * ip_port = ('127.0.0.1', 9000) back_log=5 buffer_size = 1024 tcp_client = socket(AF_INET, SOCK_STREAM) tcp_client.connect(ip_port) while True: cmd=input('输入cmd命令》》》').strip() if not cmd:continue #处理输入为空 if cmd=='quit':break tcp_client.send(cmd.encode('utf-8')) cmd_res=tcp_client.recv(buffer_size) print('命令执行结果',cmd_res.decode('gbk')) tcp_client.close()
上面无法做到并发,每次只能建立一对连接。
windows环境下,res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码
粘包(由于获取的内容超过缓存区,执行下一个命令时,系统依旧读取上一条命令没有读取完的内容)
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
只有TCP有粘包现象,UDP永远不会粘包(udp只收一次,不收就丢)。
TCP是字节流(没有消息头),UDP是消息流(有消息头)
解决TCP粘包问题:把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
发送时:
先发报头长度
再编码报头内容然后发送
最后发真实内容
接收时:
先手报头长度,用struct取出来
根据取出的长度收取报头内容,然后解码,反序列化
从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
服务端实现
# -*- coding: utf-8 -*- import socket,struct,json import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080)) phone.listen(5) while True: conn,addr=phone.accept() while True: cmd=conn.recv(1024) if not cmd:break print('cmd: %s' %cmd) res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() print(err) if err: back_msg=err else: back_msg=res.stdout.read() #改变 conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度 conn.sendall(back_msg) #在发真实的内容 conn.close()
客户端实现
#_*_coding:utf-8_*_ import socket,time,struct s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if len(msg) == 0:continue if msg == 'quit':break s.send(msg.encode('utf-8')) #改变 l=s.recv(4) #获取接收内容大小 x=struct.unpack('i',l)[0] print(type(x),x) # print(struct.unpack('I',l)) r_s=0 data=b'' while r_s < x: r_d=s.recv(1024) data+=r_d r_s+=len(r_d) # print(data.decode('utf-8')) print(data.decode('gbk')) #windows默认gbk编码