socket
socket
- 一.前情概要
- 1 C/S架构
- 2 Socket
- 3 套接字分类
- 二.基于TCP的套接字(Socket)
- 1 简单示例
- 2 模拟手机打电话
- 3 TCP粘包
- 粘包问题解决方案
- 基于TCP协议实现ssh功能
- 三.基于UDP的套接字(Socket)
- 1 简单示例
一.前情概要
1 C/S架构
C/S架构就是客户端/服务器架构,
其中server端具有以下特点:
- 一直提供服务
- 有明确的唯一的地址(ip+port),可以让客户端找到
五层网络通信协议由上至下包括:
应用层(数据)
传输层(TCP/IP)
网络层(IP)
数据链路层(以太网)
物理层(二进制)
C/S架构软件是基于网络进行通信的
Client------internet------Server
2 Socket
Socket就是ip+port,
网络通信的本质就是两个应用程序间的通信,通过ip+port就可以在网络中唯一找到1个应用程序。
Socket是应用层与传输层协议通信的中间软件抽象层,
Socket将复杂的TCP/IP协议隐藏在Socket接口中。
socket与TCP协议的关系:socket是对TCP协议、UDP协议...
3 套接字分类
套接字 :源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
(ps:看样子socket就是套接字)
套接字分为两类:
- 基于文件类型的套接字:AF_UNIX 基于文件通信
- 基于网络类型的套接字:AF_INET 基于网络通信
我们要介绍的是基于网络的套接字
二.基于TCP的套接字(Socket)
1 简单示例
模拟服务端与客户端通信:
socket server:
import socket
ip_port = ('127.0.0.1', 8989)
sk = socket.socket() # 默认TCP协议
sk.bind(ip_port)
sk.listen(5)
while True:
print('server waiting ...')
conn, addr = sk.accept()
client_data = conn.recv(1024)
print(client_data)
conn.sendall('whats up,man?')
conn.close()
socket client:
import socket
ip_port = ('127.0.0.1', 8989)
sk = socket.socket()
sk.connect(ip_port)
sk.sendall(b'hello, anybody here?')
server_reply = sk.recv(1024)
print(server_reply)
sk.close()
2 模拟手机打电话
客户端发送数据,服务器异常处理
Socket服务端:
import socket
ip_port = ('127.0.0.1', 8191)
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 参数1:地址簇:IPv4(默认) 参数2:流式socket , for TCP (默认)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 重用ip和端口,解决:服务重启时,会出现Address already in use错误
phone.bind(ip_port)
phone.listen(5)
while True: # 循环接收连接
print('starting...')
conn, addr = phone.accept()
while True: # 循环接收消息
try: # 异常捕获方式(windows作为服务器)
data = conn.recv(1024)
if not data: # 异常捕获方式(linux作为服务器)
break
print('客户端发来的消息:', data)
conn.send("what's going on?".encode()) # 编码为byte发送消息
except Exception:
break
conn.close()
phone.close()
客户端:
import socket
ip_port = ('127.0.0.1', 8191)
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(ip_port)
while True:
msg = input('>>:').strip()
if msg == '':
continue
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data)
phone.close()
3 TCP粘包
问题:
tcp是流式协议,会导致粘包问题!
解决:
根据自己定义协议方式解决粘包问题,自定义协议就是将传输数据增加报头
报头要求:
1.固定长度
2.包含对将要发送数据的描述信息
粘包问题解决方案
server要做的事情:
1.报头制作:
# data_s = 数据
# 1.计算数据长度
data_len = len(data_s)
# 2.形成报头字典
head_dict = {'data_size':data_len}
# 3.转为json字符串
head_json = json.dumps(head_dict) # import json
# 4.转为字节byts
head_byts = head_json.encode('utf-8')
2.报头长度:
# 1.报头长度:
head_len = len(head_byts)
# 2.处理二进制数据,按照给定的格式(fmt),把数据封装成固定长度字节
h_l = struct.pack('i',head_len) #import struct,这里i表示将二进制转为4字节
3.数据发送:
# 1.发送包头长度
conn.send(h_l)
# 2.发送包头
conn.send(head_byts)
# 3.发送数据
conn.send(data)
client要做的事情:
# 1.接收报头长度
h_l = phone.recv(4)
head_len = struct.unpack('i',h_l)[0] #取元组索引为0的值
# 2.接收报头
head_byts = phone.recv(head_len)
head_json = head_byts.decode('utf-8') #解码
head_dict = json.loads(head_json)
data_len = head_dicr['data_size']
# 3.接收数据
recv_size = 0
data_s = b''
while recv_size <data_len:
data = phone.recv(1024)
recv_size += len(data)
data_s += data
# 最后得到data_s
基于TCP协议实现ssh功能
server:
import socket
import subprocess
import json
import struct
host_ip = '127.0.0.1'
host_port = 8393
ss = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ss.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
ss.bind((host_ip,host_port))
ss.listen(5)
while True:
print('start listening...')
conn, addr = ss.accept()
print('客户端信息:', conn, '客户端地址:', addr)
while True:
try:
#接收命令
cmd = conn.recv(1024)
if not cmd:break
print('client的命令:',cmd)
res = subprocess.Popen(cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out_res = res.stdout.read()
err_res = res.stderr.read()
data_len = len(out_res)+len(err_res)
head_dict = {'data_size':data_len}
head_json = json.dumps(head_dict)
head_byts = head_json.encode('utf-8')
head_len = len(head_byts)
h_l = struct.pack('i',head_len)
conn.send(h_l)
conn.send(head_byts)
conn.send(out_res)
conn.send(err_res)
except Exception:
break
conn.close()
print('-'*60)
ss.close()
client:
import socket
import struct
import json
host_ip = '127.0.0.1'
host_port = 8393
cs = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cs.connect((host_ip,host_port))
while True:
cmd = input('>>:').strip()
if not cmd:continue
cs.send(cmd.encode('utf8'))
h_l = cs.recv(4)
head_len = struct.unpack('i',h_l)[0]
head_byts = cs.recv(head_len)
head_json = head_byts.decode('utf-8')
head_dict = json.loads(head_json)
data_len = head_dict['data_size']
recv_size = 0
data_s = b''
while recv_size < data_len:
msg = cs.recv(1024)
recv_size += len(msg)
data_s += msg
print(data_s.decode('utf8'))
cs.close()
三.基于UDP的套接字(Socket)
UDP特点:
用户数据报协议,无连接,面向消息的,自带报头(ps:发空没事,不会粘包问题)
TCP和UDP的不同:
tcp是可靠传输
udp是不可靠传输
1 简单示例
客户端输入,服务端返回大写
server:
import socket
ip_port = ('127.0.0.1', 8080)
udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报式socket , for UDP
udp_server.bind(ip_port)
while True:
conn, addr = udp_server.recvfrom(1024)
print(conn, addr)
udp_server.sendto(conn.upper(), addr)
client:
import socket
ip_port = ('127.0.0.1', 8080)
udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报式socket , for UDP
while True:
msg = input('>>:').strip()
udp_client.sendto(msg.encode('utf-8'), (ip_port))
conn, addr = udp_client.recvfrom(1024)
print(conn.decode('utf-8'))