1、socket 套接字工作流程图
2、收发功能
3、不间断一发一收
4、多客户端连接
5、UDP:收发功能
6、UDP:实现时间功能
7、执行命令
8、黏包
socket 套接字工作流程图
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
AF_UNIX: 基于文件编程
基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
AF_INET: 基于网络编程 有AF_INET6 ipv6
CS架构
SOCK_STREAM: TCP协议 数据流式通信
SOCK_DGRAM: UDP协议 数据报式的套接字
## 收发功能
socket服务端
import socket
cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.bind(('192.168.0.12',9090))
cat.listen(4)
conn,addr=cat.accept()
msg=conn.recv(1024)
conn.close()
print('接收到的信息: %s'%msg)
conn.send(msg.upper())
cat.close()
socket客户端
import socket
cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.connect(('192.168.0.12',9090))
cat.send('xiong'.encode('utf-8'))
data=cat.recv(1024)
print('接收到的信息: %s'%data)
#服务端会主动断开 已经传输完客户端数据的连接,将状态改变为TIME_WAIT, 四次挥手之后确定数据已经完全传输完,直接断开并清理状态连接信息
# 收发都是在操作自己的缓存区
# recv 接收的字节, 由recv用户态的应用程序发起
# 回车: 当前 socket 内核态缓存没数据 对端内核态也就没法收到数据,自然也就卡死了
##不间断一发一收
socket服务端
import socket
cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.bind(('127.0.0.1',9009))
cat.listen(5)
conn,addr=cat.accept()
while True:
msg=conn.recv(1024) #回车: 当前 socket 内核态缓存没数据 对端内核态也就没法收到数据,自然也就卡死了
print('接收到的信息: %s'%msg.decode('utf-8'))
if msg == b'q' : break
inp = input('输入一个值: ')
conn.send(inp.encode('utf-8'))
continue
conn.close()
cat.close()
socket客户端
import socket
cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.connect(('127.0.0.1',9009))
while True:
inp = input('输入一个值: ')
if inp == 'q':
cat.send(inp.encode('utf-8'))
break
cat.send(inp.encode('utf-8'))
msg = cat.recv(1024)
print('接收到的信息: %s' % msg.decode('utf-8'))
continue
cat.close()
## 多客户端连接
# 1、当客户端与服务端建立连接,每次最大客户端连接数由listen控制,我这里最大是5个连接
# 2、多个客户端与服务端建立连接,每次只能有一个客户端与服务端通信,其它队列都会保持在队列中
# 3、unix有些系统使用try except无法解决客户端conn连接突然中断, 可以使用 if not conn: break
socket服务端
import socket
cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.bind(('192.168.2.192',9009))
cat.listen(5)
while True: # 客户端退出,如果队列中还有连接那么再重新建立连接
conn,addr=cat.accept() # 连接一次
while True: # 与单个用户建立连接
try:
msg=conn.recv(1024) # 当客户端关掉连接,而服务端连接却没有中断,它就直接报错 ConnectionResetError: [WinError 10054]
except Exception:
break
print('接收到的信息: %s'%msg.decode('utf-8'))
if msg == b'q' : break
conn.send(msg.upper())
conn.close() # 关闭连接
cat.close() # 关闭程序
socket客户端
import socket
cat=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.connect(('192.168.2.192',9009))
while True:
inp = input('输入一个值: ')
if inp == 'q':
cat.send(inp.encode('utf-8'))
break
cat.send(inp.encode('utf-8'))
msg = cat.recv(1024)
print('接收到的信息: %s' % msg.decode('utf-8'))
continue
cat.close()
UDP:收发功能
1、udp不需要建立accept连接,因为无需三次握手建立一条固定的通道
2、多个客户端同时连接服务端,可同时收发信息
3、udp可以接受空 (直接回车)???
recv在自己这端的缓冲区为空时,阻塞
recvfrom在自己这端的缓冲区为空时,就收一个空
UDP_socket服务端
import socket
ip_port=('127.0.0.1',9999)
udp_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_sock.bind(ip_port)
while True:
data,addr=udp_sock.recvfrom(1024)
print(data)
udp_sock.sendto(data,addr)
UDP_socket客户端
import socket
ip_port=('127.0.0.1',9999)
udp_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
inp = input('>>: ')
udp_sock.sendto(inp.encode('utf-8'),ip_port)
data,addr=udp_sock.recvfrom(1024)
print(data)
UDP:实现时间功能
# 1、实现时间功能
# 2、data传递进来是二进制的格式,在strftime之前需要先将格式转换回来
# 3、注意格式转换,发送都是encode,接受基本都是recvfrom
# udp_socket_server端
import socket
import time
ip_port=('127.0.0.1',9001)
udp_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_sock.bind(ip_port)
while True:
data,addr=udp_sock.recvfrom(1024)
print(data)
if not data:
default_time='%Y-%m-%d %X'
else:
default_time=data.decode('utf-8')
udp_back_time=time.strftime(default_time)
udp_sock.sendto(udp_back_time.encode('utf-8'),addr)
# udp_socket_client端
import socket
udp_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
ip_port=('127.0.0.1',9001) #TypeError: sendto() takes 2 or 3 arguments (1 given) 需要带地址
while True:
inp=input('>>: ')
udp_client.sendto(inp.encode('utf-8'),ip_port)
data,addr=udp_client.recvfrom(1024)
print('现在时间是: %s'%data.decode('utf-8'))
# 1、先运行服务端
# 2、再运行客户端打印结果如下:
现在时间是: 18
>>: %Y
现在时间是: 2018
>>:
现在时间是: 2018-01-04 20:49:53
>>:
### 执行命令
# socket TCP服务端
import socket
import subprocess
ip_port=('127.0.0.1',9001)
ip_connect=5
buff_size=1024
command=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
command.bind(ip_port)
command.listen(ip_connect)
while True:
# 客户端断开之后保持重连
data,addr=command.accept()
print('客户端连接信息: %s' %data)
while True:
try:
# 接收客户端传递过来的值
cmd_value=data.recv(buff_size)
except Exception:
break
# 执行传递过来的命令,将结果保存到管道对象中赋值给res
res=subprocess.Popen(cmd_value.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
# 取出stderr的值,如果是空那么执行stdout,不为空说明报错了
err=res.stderr.read()
if err:
cmd_res=err
else:
cmd_res=res.stdout.read()
if not cmd_res: # 判断如果是类似 cd .. 的命令,它到subprocess值为空
cmd_res='命令为空'.encode('gbk')
data.send(cmd_res)
continue
data.close()
command.close()
# socket_客户端
import socket
ip_port=('127.0.0.1',9001)
ip_connect=5
buff_size=1024
command=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
command.connect(ip_port)
while True:
cmd_inp=input('请输入命令: ').strip()
if not cmd_inp: command
if cmd_inp == 'quit': break
command.send(cmd_inp.encode('utf-8'))
data=command.recv(buff_size)
print(data.decode('gbk'))
command.close()
# 最后在客户端这边输入 dir就能看到结果了# 最后在客户端这边输入 dir就能看到结果了
黏包
udp不会黏包,一个recvfrom对应一个sendto,每发送一个包就接收一个包
tcp黏包:优化算法合并了每次send发送的数据, 更优的减少网络负载量, 如果第一次没有收完,那么第二次发送过来的,还是第一次缓存冲区的数据
###### 黏包解决办法
解决粘包的思路:
1、判断数据的长度,
2、封闭消息头
UDP 数据报: TCP数据流的方式 封装了一个消息头(消息来源地址,发送端的IP+端口)
应用程序永远不能操作硬件,能操作硬件的只能是操作系统的内核
用户态内存: socket程序
内核态内存: 内核操作硬件
Forking 进程
threading 线程
多进程比多线程的开销更大
### 服务端配置
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from socket import *
import subprocess
import struct
ip_port=('127.0.0.1',9002)
ip_listen=5
buff_size=1024
# TCP 流式协议
tcp_server=socket(AF_INET,SOCK_STREAM)
# 绑定并监听端口
tcp_server.bind(ip_port)
# 允许back_log 允许有多少个队列
tcp_server.listen(ip_listen)
while True:
# 客户端连接信息,当客户端断开之后,服务端应允许其它客户端连接
conn,addr=tcp_server.accept()
print('客户端连接信息: %s' %conn)
while True:
try:
# 如果是空的,或者客户端非法退出,那么就直接退出程序
data=conn.recv(buff_size)
if not data:break
except Exception:
break
# subprocess接收到的命令应该是str格式,客户端发送过来的值是bytes格式,
res=subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
err=res.stderr.read()
# 如果这个值里有东西,说明它报错了直接返回它
if err:
cmd_res=err
else:
cmd_res=res.stdout.read()
# 如果为空比如cd .. 那么应该给它返回一个空值
if not cmd_res:
cmd_res='value is null '.encode('utf-8')
# 取出这个值最大的长度,并传递给客户端
length=len(cmd_res)
# --------------- 方法一 ---------------
# # length=str(length).encode('utf-8')
# conn.send(length)
# 将最大值给客户端,返回一个值过来,避免黏包
# retu_data=conn.recv(buff_size)
# if retu_data == b'ok':
# conn.send(cmd_res)
# --------------- 方法二 ---------------
# 黏包发送,第一次发送的值,固定为4字节,客户端接收的时候先接收4字节,再取数据
length_data=struct.pack('i',length)
conn.send(length_data)
conn.send(cmd_res)
conn.close()
tcp_server.close()
### 客户端配置
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from socket import *
import struct
ip_port=('127.0.0.1',9002)
ip_listen=5
buff_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
inp=input('请输入命令: ')
tcp_client.send(inp.encode('utf-8'))
# 如果直接回车,为空,那么应该直接让它退出循环重新再来一次
if not inp:continue
# 如果输入quit,那么直接退出程序
if tcp_client == 'quit': break
# --------------- 方法一 ---------------
# 解决黏包
# length=tcp_client.recv(buff_size)
# tcp_client.send('ok'.encode('utf-8'))
#
# length=int(length.decode('utf-8'))
# --------------- 方法二 ---------------
# 服务端发送的第一个值是4字节大小的,手动调置一下
length=tcp_client.recv(4)
# 取出大小
length=struct.unpack('i',length)[0]
# 设置初始值,用于保存
recv_size=0
# 配置二进制的大小
recv_msg=b''
while recv_size < length:
recv_msg+=tcp_client.recv(buff_size)
recv_size = len(recv_msg)
# data=tcp_client.recv(buff_size)
print(recv_msg.decode('gbk'))
# 结果
请输入命令: cd ..
value is null
请输入命令: ipconfig
Windows IP 配置
以太网适配器 本地连接: xxxxxxxxxxxxxxxxxxxx
转载于:https://blog.51cto.com/xiong51/2057579