Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
创建套接字对象
s = socket.socket(socket_family,socket_type,protocal=0)
# socket_family 可以是 AF_INET 或 AF_INET6,socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM,protocol 一般不填,默认值为 0
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
tcp服务端
import socket
ip_port = ('127.0.0.1', 8081) # ip和port
BUFSIZE = 1024 # 一次接收的字节数
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET == ipV4, SOCK_STREAM == tcp协议
s.setsockopt(SOL_SOCKET,SO_REUSEADDR, 1) # 由于tcp断开连接的四次挥手time_wait的状态会占用地址,加这一句能够重用地址
s.bind(ip_port) # 绑定ip和port
s.listen(5) # 监听客户端的连接
while True: # 新增接收链接循环,可以不停的接受连接
conn, addr = s.accept() # 生成连接对象conn, addr为客户端的地址
# print(conn)
# print(addr)
while True: # 新增通信循环,可以不断的通信,收发消息
try: # 捕获连接突然断开的异常,使程序能正常执行不会终止
msg = conn.recv(BUFSIZE) # 接收消息,此时消息为二进制数据
print(msg, type(msg))
conn.send(msg.upper()) # 向客户端回复消息
except ConnectionResetError as e:
print(e)
break
conn.close() # 关闭连接对象
s.close() # 关闭服务端套接字, 可省略
tcp客户端
import socket
ip_port = ('127.0.0.1', 8081) # 服务端地址和端口
BUFSIZE = 1024 # 一次接收的字节数
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET == ipV4, SOCK_STREAM == tcp协议,与服务端一致
s.connect_ex(ip_port) # 连接服务端
while True: # 新增通信循环,客户端可以不断发收消息
msg = input('>>: ').strip()
if len(msg) == 0:
continue
s.send(msg.encode('utf-8')) # 发消息,只能发送字节类型
feedback = s.recv(BUFSIZE) # 收消息
print(feedback.decode('utf-8'))
s.close() # 关闭套接字
udp是无链接的,先启动哪一端都不会报错
udp服务端
import socket
ip_port = ('127.0.0.1', 9000) # 服务端地址和端口
BUFSIZE = 1024 # 一次接收的字节数
udp_server_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # AF_INET == ipV4, SOCK_DGRAM == udp协议
udp_server_client.bind(ip_port) # 绑定ip和port
while True:
msg, addr = udp_server_client.recvfrom(BUFSIZE) # 循环接收消息,addr == 客户端地址
print(msg, addr)
udp_server_client.sendto(msg.upper(), addr) # 发送消息
udp客户端
import socket
ip_port = ('127.0.0.1', 9000) # 服务端地址和端口
BUFSIZE = 1024 # 一次接收的字节数
udp_server_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # AF_INET == ipV4, SOCK_DGRAM == udp协议,与
# 服务端一致
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)
只有基于tcp的socket才会出现粘包现象
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包
两种情况下会发生粘包
粘包现象的解决方式:
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
struct模块用法示例代码
import struct
import binascii
import ctypes
values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')
print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Before : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)
s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)
print('After pack',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size))
s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('After pack',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0))
发送时:
先发报头长度
再编码报头内容然后发送
最后发真实内容
接收时:
先手报头长度,用struct取出来
根据取出的长度收取报头内容,然后解码,反序列化
从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
实现并发的类必须要继承socketserver.BaseRequestHandler类,要实现handle()方法
import socketserver
import struct
import json
import os
class FtpServer(socketserver.BaseRequestHandler):
coding='utf-8'
server_dir='file_upload'
max_packet_size=1024
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
def handle(self):
print(self.request)
while True:
data=self.request.recv(4)
data_len=struct.unpack('i',data)[0]
head_json=self.request.recv(data_len).decode(self.coding)
head_dic=json.loads(head_json)
# print(head_dic)
cmd=head_dic['cmd']
if hasattr(self,cmd):
func=getattr(self,cmd)
func(head_dic)
def put(self,args):
file_path = os.path.normpath(os.path.join(
self.BASE_DIR,
self.server_dir,
args['filename']
))
filesize = args['filesize']
recv_size = 0
print('----->', file_path)
with open(file_path, 'wb') as f:
while recv_size < filesize:
recv_data = self.request.recv(self.max_packet_size)
f.write(recv_data)
recv_size += len(recv_data)
print('recvsize:%s filesize:%s' % (recv_size, filesize))
ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer) # 这是tcp协议的并发使用方法
# socketserver.ThreadingUDPServer() # udp协议的使用方法
ftpserver.serve_forever()