23、基于TCP的sorket编程、通信功能小代码、远程ssh连接(初识粘包问题)
一、基于TCP协议的套接字通信(简单)
- 服务端:
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket.SOCK_DGRAM ———>UDP
# 2、绑定电话卡
phone.bind(('172.0.0.1', 8080))
# 3、开机
phone.listen(5)
print('start')
# 4、接收链接请求
conn, client_addr = phone.accept()
print(client_addr)
# 5、收发消息
# 套接字收发消息都是二进制
data = conn.recv(1024) # 最大接收的字节数
print(data)
conn.send(data.upper())
# 6、回收资源--挂电话
conn.close()
# 7、关机
phone.close()
- 客户端:
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket.SOCK_DGRAM ———>UDP
# 2、打电话
phone.connect(('127.0.0.1', 8080))
# 3、发、收数据
phone.send('hello'.encode('utf-8'))
data = phone.recv(1024)
print(data.decode('utf-8')) # 收到的还是二进制
# 4、关闭
phone.close()
二、加入通讯循环于链接循环(循环)
- 服务端
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# tcp称之为流式协议,udp称之为数据报协议SOCK_DGRAM
# print(phone)
# 2、插入/绑定手机卡
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
# 3、开机
phone.listen(5) # 半连接池,限制的是请求数
# 4、等待电话连接
print('start...')
while True: # 连接循环
conn, client_addr = phone.accept() # 三次握手建立的双向连接(客户端的ip端口)
# print(conn)
print('已经有一个连接建立成功', client_addr)
# 5、通信:收\发消息
while True:
try:
print('服务端正在收数据...')
data = conn.recv(1024)
if len(data) == 0: # 在客户端单方面断开连接,服务端才会出现空数据的情况
break
print('来自客户端的数据', data)
conn.send(data.upper())
except ConnectionAbortedError:
break
# 6、挂掉电话连接
conn.close()
# 7、关机
phone.close() # 写不写都无所谓,到不了这一步
- 客户端
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2、拨电话
phone.connect(('127.0.0.1', 8080)) # 指定服务端ip和端口
# 3、通信、发/收
while True:
msg = input('>>>:').strip()
if len(msg) == 0: # 如果输入的字节为空,服务端会一直处于收的状态
# 为了解决这个bug
continue
phone.send(msg.encode('utf-8'))
# print('has send---->')
data = phone.recv(1024)
# print('has recv')
print(data)
phone.close()
三、地址占用问题
重启时如果遇到:OSERROR:Addres already in use
这个是由于你的服务端仍然存四次挥手的time_wait状态在占用地址
解决方法:
- 方法一
# 加入一条socket配置,重用ip和端口
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
- 方法二(linux)
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然后执行 /sbin/sysctl -p 让参数生效。
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
四、模拟ssh远程执行命令
- 服务端
from socket import *
import subprocess
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
print('start...')
while True:
conn, client_addr = server.accept()
while True:
print('from client:', client_addr)
try:
cmd = conn.recv(1024)
if len(cmd) == 0: break
print('cmd:', cmd)
obj = subprocess.Popen(cmd.decode('utf8'), # 输入的cmd命令
shell=True, # 通过shell运行
stderr=subprocess.PIPE, # 把错误输出放入管道,以便打印
stdout=subprocess.PIPE) # 把正确输出放入管道,以便打印
res1 = obj.stdout.read() # 打印正确输出
res2 = obj.stderr.read() # 打印错误输出
conn.send(res1)
conn.send(res2)
except ConnectionAbortedError:
break
conn.close()
- 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000))
while True:
msg = input('cmd:').strip()
if len(msg) == 0:
print('不能为空,请重新输入')
continue
client.send(msg.encode('utf8'))
res = client.recv(1024)
print(res.decode('gbk'))
- 输入dir命令,由于服务端发送的字节少于1024字节,客户端可以接受
- 输入tasklist命令,由于服务端发送字节多于1024字节,客户端只能接受部分数据,并且当你再次输入dir命令的时候,客户端会接收dir命令的结果,但是会打印上一次的剩余未发送完的数据,这个就是粘包问题(只有TCP有粘包,UDP不会)