大家好,又见面了,我是你们的朋友全栈君。
socket 编程
-客户端/服务器架构 :即 C/S架构
1,硬件C/S 架构(打印机)
2, 软件C/S 架构(web服务)
C/S架构与socket的关系:socket就是为了完成C/S架构的开发
-osi 七层:
应用层–运输层–网络层–链路层–物理层
socket 抽象层在应用层和运输层之间
socket概念(socket也是套接字)
socket是应用层和TCP/IP协议中间通信的软件层,它是一组接口,在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议封装隐藏在socket接口后,让socket去组织数据,以符合指定协议,所以只需遵循socket规定去编程就可以。
套接字分为2种:
-基于文件型的套接字家族 AF_UNIX
用于一台机器的不同程序之间
linux 一切皆文件,基于文件的套接字调用的是底层的文件系统来取数据,2个套接字进程运行在同一个机器,可以通过访问同一个文件系统来间接完成通信
-基于网络类型的套接字家族 AF_INET
用于网络编程
通过网络来实现2个程序通讯
socket 基于tcp运行流程图如下:
例如:
服务端:
代码语言:javascript复制import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#socket.SOCK_STREAM 是基于流的通讯方式,也就是TCP
#socket.AF_INET 代表是网络嵌套家族类型
phone.bind(('192.168.1.4',8000))
#括号内写IP地址 端口 自己电脑IP是 192.168.1.4 ,监听端口是8000
phone.listen(5) #代表同时可以接5个电话
conn,addr =phone.accept()
msg=conn.recv(1024)# 收消息 ,1024代表可以接收多少字节的信息
print('客户端发来的消息是',msg)
conn.send(msg.upper())#发消息
conn.close()
phone.close()
客户端
代码语言:javascript复制import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('192.168.1.4',8000))
phone.send('hello'.encode('utf-8'))#发消息
data =phone.recv(1024) #收消息
print('收到服务端发来的消息',data)
运行结果是:
服务端:客户端发来的消息是 b’hello’
客户端:收到服务端发来的消息 b’HELLO’
socket 底层工作原理
客户端服务端循环发送接收消息
服务端代码:
代码语言:javascript复制from socket import *
ip_import =('192.168.1.3',8000)
back_log = 5
buffer_size = 1024
代码语言:javascript复制tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_import)
tcp_server.listen(back_log)
代码语言:javascript复制conn,add = tcp_server.accept()
print('双向链接是',conn)
print('客户端的地址是',add)
while True:
msg = conn.recv(buffer_size)
print('客户发来的是',msg.decode('utf-8'))
conn.send(msg.upper())
conn.close()
tcp_server.close()
服务端运行结果是:
双向链接是 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘192.168.1.3’, 8000), raddr=(‘192.168.1.3’, 52951)> 客户端地址是 (‘192.168.1.3’, 52951) 客户发来的是 nihao 客户发来的是 hi 客户发来的是 how are you
客户端代码:
代码语言:javascript复制from socket import *
ip_port=('192.168.1.3',8000)
back_log=5
buffer_size=1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
代码语言:javascript复制while True:
client_msg = input('>>:').strip()
if not client_msg: continue
tcp_client.send(client_msg.encode('utf-8'))
print('客户端已发送消息')
data = tcp_client.recv(buffer_size)
print('收到服务端发来的消息',data.decode('utf-8'))
客户端结果如下:
>>:nihao 收到服务端发来的消息 NIHAO >>:hi 收到服务端发来的消息 HI >>:how are you 收到服务端发来的消息 HOW ARE YOU
注意:如果输入可以是空格,但不可以是空,是空的话会继续要求输入。
socket 收发消息原理刨析
客户端发消息:是从应用程序发送到用户态内存,然后发送到内核态内存然后再通过网卡发出
客户端消息:是从网卡进入内核态内存然后发送到用户态内存
服务端同样如此。
服务端多次接收双向连接
windows 系统和linux/mac系统下,socket 不同之处:
断开客户端,windows 系统下服务端会报错,而linux/mac系统下,服务端接收的是空。
windows 系统下:
服务端:from socket import *
代码语言:javascript复制ip_port =('192.168.1.3',8001)
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,add = tcp_server.accept()
print('接收的链接是',conn)
print('地址是',add)
while True:
print('服务端开始运行了')
try:
data = conn.recv(buffer_size)
print('客户发来的是',data.decode('utf-8'))
conn.send(data.upper())
print('服务端已发送',data.upper())
except Exception:
break
conn.close()
tcp_server.close()
客户端
代码语言:javascript复制from socket import *
ip_port=('192.168.1.3',8001)
back_log=5
buffer_size=1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
client_msg = input('>>:').strip()
if not client_msg: continue
tcp_client.send(client_msg.encode('utf-8'))
print('客户端已发送消息')
data = tcp_client.recv(buffer_size)
print('收到服务端发来的消息',data.decode('utf-8'))
tcp_client.close()
linux 系统下:
代码语言:javascript复制from socket import *
ip_port =('192.168.1.3',8001)
back_log = 5
buffer_size = 1024
tcp_server = socket(AF_INET,SOCK_STREAM)
代码语言:javascript复制tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
#在bind 之前进行socket 设置,使其不卡在time_wait占用地址
代码语言:javascript复制tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,add = tcp_server.accept()
print('接收的链接是',conn)
print('地址是',add)
while True:
print('服务端开始运行了')
try:
data = conn.recv(buffer_size)
if not data: break
print('客户发来的是',data.decode('utf-8'))
conn.send(data.upper())
print('服务端已发送',data.upper())
except Exception:
break
tcp_server.close()
客户端代码如上,不变。
总结:服务端,客户端基本要求:
基于udp 的套接字
udp没有连接
服务端
代码语言:javascript复制from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
udp_server = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报
udp_server.bind(ip_port)
while True:
data,addr = udp_server.recvfrom(buffer_size)
print(data.decode('utf-8'))
udp_server.sendto(data.upper(),addr)
代码语言:javascript复制udp_server.close()
代码语言:javascript复制
客户端
代码语言:javascript复制from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
udp_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报
while True:
msg = input('>>:').strip()
udp_client.sendto(msg.encode('utf-8'),ip_port)
data,addr = udp_client.recvfrom(buffer_size)
print(data.decode('utf-8'))
代码语言:javascript复制udp_client.close()
运用基于udp的套接字,来制作时间服务器(ntp),代码如下:
时间服务端:
代码语言:javascript复制from socket import *
import time
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
time_server = socket(AF_INET,SOCK_DGRAM)
time_server.bind(ip_port)
while True:
data,addr = time_server.recvfrom(buffer_size)
if not data:
fmt = '%Y-%m-%d-%X'
else:
fmt = data.decode('utf-8')
back_time = time.strftime(fmt)
time_server.sendto(back_time.encode('utf-8'),addr)
代码语言:javascript复制time_server.close()
时间客户端:
代码语言:javascript复制from socket import *
ip_port = ('192.168.1.5',8080)
buffer_size = 1024
time_client = socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM 数据报
while True:
msg = input('>>:').strip()
time_client.sendto(msg.encode('utf-8'),ip_port)
data,addr = time_client.recvfrom(buffer_size)
print('服务器的标准时间是:',data.decode('utf-8'))
代码语言:javascript复制time_client.close()
recv 在自己这段的缓冲区为空时,会阻塞
recvfrom 在自己这段的缓冲区为空时,就收一个空
基于tcp实现远程命令
subprocess 模块
代码:变量名=subprocess.Popen(命令,shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
代码语言:javascript复制
将命令结果封装在管道(PIPE)中,stdin 代表输入,stdout 代表输出,stderr代表报错。
想要读取内容:代码:变量名.stdrr.read()
服务端
代码语言:javascript复制from socket import *
import subprocess
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_sever = socket(AF_INET,SOCK_STREAM)
代码语言:javascript复制tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) #设置socket,使其不卡在time wait,占用地址
代码语言:javascript复制tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)
while True:
conn,addr = tcp_sever.accept()
print('新的客户端连接',addr)
while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客户端的命令',cmd)
#执行命令,得到命令的结果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#发消息
if not cmd_res:
代码语言:javascript复制 cmd_res = '操作成功'.encode('utf-8') #此代码代表,如果cmd_res 为空的话,也会显示结果。
代码语言:javascript复制 conn.send(cmd_res)
except Exception as e:
print(e)
break
tcp_sever.close()
客户端
代码语言:javascript复制from socket import *
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').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('utf-8'))
tcp_client.close()
实操结果如下:
粘包
注意:只有tcp 会粘包,udp不会粘包。
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的。
解决粘包
方式一:比较低端一些
服务端:
代码语言:javascript复制from socket import *
import subprocess
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)
while True:
conn,addr = tcp_sever.accept()
print('新的客户端连接',addr)
while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客户端的命令',cmd)
#执行命令,得到命令的结果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#发消息
if not cmd_res:
cmd_res = '操作成功'.encode('utf-8')
length =len(cmd_res)
conn.send(str(length).encode('utf-8'))
client_ready = conn.recv(buffer_size)
if client_ready == b'ready':
conn.send(cmd_res)
except Exception as e:
print(e)
break
tcp_sever.close()
客户端
代码语言:javascript复制from socket import *
ip_port = ('192.168.1.5',8080)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
length= tcp_client.recv(buffer_size)
tcp_client.send(b'ready')
length = int(length.decode('utf-8'))
recv_msg = b''
recv_size = 0
while recv_size < length:
recv_msg =tcp_client.recv(buffer_size)
recv_size = len(recv_msg)
print('命令的执行结果是',recv_msg.decode('utf-8'))
tcp_client.close()
方式二:比较高端一些
服务端:
代码语言:javascript复制from socket import *
import subprocess
import struct
ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024
tcp_sever = socket(AF_INET,SOCK_STREAM)
tcp_sever.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_sever.bind(ip_port)
tcp_sever.listen(back_log)
while True:
conn,addr = tcp_sever.accept()
print('新的客户端连接',addr)
while True:
try:
#收消息
cmd = conn.recv(buffer_size)
if not cmd:break
print('收到客户端的命令',cmd)
#执行命令,得到命令的结果cmd_res
res = subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_res = err
else:
cmd_res = res.stdout.read()
#发消息
if not cmd_res:
cmd_res = '操作成功'.encode('utf-8')
length =len(cmd_res)
conn.send(struct.pack('i',length))
conn.send(cmd_res)
except Exception as e:
print(e)
break
tcp_sever.close()
客户端:
代码语言:javascript复制from socket import *
import struct
from functools import partial
ip_port = ('192.168.1.2',8080)
back_log = 5
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd = input('>>:').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
length_data= tcp_client.recv(4)
length = struct.unpack('i',length_data)[0]
recv_msg = ''.join(iter(partial(tcp_client.recv, buffer_size), b''))
print('命令的执行结果是',recv_msg.decode('utf-8'))
tcp_client.close()
总结:
socket 并发socketserver
socketserver-tcp套接字 并发
并发:即多个客户端与服务端的同时交互
服务端:
代码语言:javascript复制import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print('conn is:', self.request) # self.request相当于conn
# print('addr is:', self.client_address) # self.client_address相当于addr
while True:
try:
# 收消息
data = self.request.recv(1024)
if not data: break
print('收到客户端的消息是:', data,self.client_address)
# 发消息
self.request.send(data.upper())
except Exception as e:
print(e)
break
if __name__ == '__main__':
s = socketserver.ThreadingTCPServer(('192.168.1.2',8081),MyServer)
s.serve_forever()
客户端:
代码语言:javascript复制from socket import *
ip_port = ('192.168.1.2', 8081)
buffer_size = 1024
tcp_client = socket(AF_INET, SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
msg = input('>>: ').strip()
if not msg: continue
tcp_client.send(msg.encode('utf-8'))
print('客户端已经发送消息')
data = tcp_client.recv(buffer_size)
print('收到服务端发来的消息', data.decode('utf-8'))
tcp_client.close()
socketserver-udp套接字 并发
对于tcp来说,self.request = conn
但是对于udp来说,self.request =(data,udp的套接字对象)
服务端:
代码语言:javascript复制import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
#self.request[0] 是data
#self.request[1]是udp的socket套接字
#self.client_address 是conn
#收消息
print('收到客户端的消息是',self.request[0],self.client_address)
#发消息
self.request[1].sendto(self.request[0].upper(),self.client_address)
if __name__ == '__main__':
s=socketserver.ThreadingUDPServer(('192.168.1.2',8082),MyServer) #多线程
s.serve_forever()
客户端:
代码语言:javascript复制from socket import *
ip_port=('192.168.1.2',8082)
buffer_size=1024
udp_client=socket(AF_INET,SOCK_DGRAM) #数据报
while True:
msg=input('>>: ').strip()
udp_client.sendto(msg.encode('utf-8'),ip_port)
data,addr=udp_client.recvfrom(buffer_size)
# print(data.decode('utf-8'))
print(data)
认证客户端合法性
服务端
代码语言:javascript复制from socket import *
import os
import hmac
secret_key = b'ni hao ma'
def conn_auth(conn):
'''
认证客户端连接
:param conn:
:return:
'''
print('开始验证连接的合法性')
msg = os.urandom(32)
conn.sendall(msg)
h=hmac.new(secret_key,msg)#加严
digest=h.digest()
respone=conn.recv(len(digest))
return hmac.compare_digest(respone,digest)#对比respone 与digest 是否一致
def data_handler(conn, bufize=1024):
if not conn_auth(conn):
print('该连接不合法,关闭')
conn.close()
return
print('连接合法,开始通信')
while True:
data = conn.recv(bufize)
if not data: break
conn.send(data.upper())
def server_handle(ip_port,bufize,backlog=5):
'''
只处理连接
:param ip_port:
:param bufize:
:param backlog:
:return:
'''
tcp_socket_server = socket(AF_INET, SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 在bind 之前进行socket 设置,使其不卡在time_wait占用地址
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(backlog)
while True:
conn, addr = tcp_socket_server.accept()
print('conn是%s,addr是%s' % (conn, addr))
data_handler(conn, bufize)
tcp_socket_server.close()
if __name__ == '__main__':
ip_port =('192.168.1.2',8080)
bufize = 1024
server_handle(ip_port,bufize)
客户端:
代码语言:javascript复制from socket import *
import os,hmac
secret_key = b'ni hao ma'
def conn_auth(conn):
'''
验证客户端到服务器的连接合法性
:param conn:
:return:
'''
msg = conn.recv(32)
h = hmac.new(secret_key, msg)
digest = h.digest()
conn.sendall(digest)
def client_handler(ip_port,bufsize=1024):
tcp_socket_client = socket(AF_INET, SOCK_STREAM)
tcp_socket_client.connect(ip_port)
conn_auth(tcp_socket_client)
while True:
#发消息
cmd = input('>>:').strip()
if not cmd: continue
if cmd == 'quit': break
tcp_socket_client.send(cmd.encode('utf-8'))
#收消息
data=tcp_socket_client.recv(bufsize)
print(data.decode('utf-8'))
tcp_socket_client.close()
if __name__ == '__main__':
ip_port = ('192.168.1.2', 8080)
bufize = 1024
client_handler(ip_port,bufize)
作业
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/154421.html原文链接:https://javaforall.cn