Socket是应用层与TCP/IP协议族通讯的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来讲,一组简单的接口就是所有,让Socket去组织数据,以符合指定的协议。python
因此,咱们无需深刻理解tcp/udp协议,socket已经为咱们封装好了,咱们只须要遵循socket的规定去编程,写出的程序天然就是遵循tcp/udp标准的。linux
socket本质就是套接字,项目通常在ISO七层模型中除了应用层其余都会牵扯到socket,咱们只要遵循socket的规定去编程,写出的程序天然就是遵循tcp/udp标准的,大大加强了开发者的效率!git
方法 | 用途 |
---|---|
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.close() | 关闭套接字 |
import socket import time server = socket.socket() # 买手机 不传参数默认用的就是TCP协议 server.bind(('127.0.0.1',8080)) # bind((host,port)) 插电话卡 绑定ip和端口 server.listen(5) # 开机 半链接池 conn, addr = server.accept() # 接听电话 等着别人给你打电话 阻塞 # while True: # time.sleep(1) # # data = conn.recv(1024) # 听别人说话 接收1024个字节数据 阻塞 # # print(data) for i in range(31): conn.send(b'hello baby~') # 给别人回话 conn.close() # 挂电话 server.close() # 关机
import socket client = socket.socket() # 拿电话 client.connect(('127.0.0.1',8080)) # 拨号 写的是对方的ip和port for i in range(31): # client.send(b'hello world!') # 对别人说话 data = client.recv(1024) # 听别人说话 print(data,'000') # client.close() # 挂电话import socket # socket.AF #一、买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print(phone) #二、拨电话 phone.connect(('127.0.0.1', 8081)) # 指定服务端ip和端口 #三、通讯:发\收消息 phone.send('hello'.encode('utf-8')) # phone.send(bytes('hello',encoding='utf-8')) data = phone.recv(1024) print(data) # import time # time.sleep(500) #四、关闭 phone.close()
import socket """ 服务端 固定的ip和port 24小时不间断提供服务 """ server = socket.socket() # 生成一个对象 server.bind(('127.0.0.1',8080)) # 绑定ip和port server.listen(5) # 半链接池 while True: conn, addr = server.accept() # 等到别人来 conn就相似因而双向通道 print(addr) # ('127.0.0.1', 51323) 客户端的地址 while True: try: data = conn.recv(1024) print(data) # b'' 针对mac与linux 客户端异常退出以后 服务端不会报错 只会一直收b'' if len(data) == 0:break conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close()
import socket client = socket.socket() # 拿电话 client.connect(('127.0.0.1',8080)) # 拨号 写的是对方的ip和port for i in range(31): # client.send(b'hello world!') # 对别人说话 data = client.recv(1024) # 听别人说话 print(data,'000') # client.close() # 挂电话
在重启服务端时可能会遇到:编程
这个是因为你的服务端仍然存在四次挥手的time_wait状态在占用地址(若是不懂,请深刻研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发状况下会有大量的time_wait状态的优化方法)json
# 加入一条socket配置,重用ip和端口 phone=socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080))
发现系统存在大量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 时间
8.1服务端设计模式
import socket import os import json import struct server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn,addr = server.accept() while True: try: header_len = conn.recv(4) # 解析字典报头 header_len = struct.unpack('i',header_len)[0] # 再接收字典数据 header_dic = conn.recv(header_len) real_dic = json.loads(header_dic.decode('utf-8')) # 获取数据长度 total_size = real_dic.get('file_size') # 循环接收并写入文件 recv_size = 0 with open(real_dic.get('file_name'),'wb') as f: while recv_size < total_size: data = conn.recv(1024) f.write(data) recv_size += len(data) print('上传成功') except ConnectionResetError as e: print(e) break conn.close()
8.2客户端缓存
import socket import json import os import struct client = socket.socket() client.connect(('127.0.0.1',8080)) while True: # 获取电影列表 循环展现 MOVIE_DIR = r'D:\python脱产10期视频\day25\视频' movie_list = os.listdir(MOVIE_DIR) # print(movie_list) for i,movie in enumerate(movie_list,1): print(i,movie) # 用户选择 choice = input('please choice movie to upload>>>:') # 判断是不是数字 if choice.isdigit(): # 将字符串数字转为int choice = int(choice) - 1 # 判断用户选择在不在列表范围内 if choice in range(0,len(movie_list)): # 获取到用户想上传的文件路径 path = movie_list[choice] # 拼接文件的绝对路径 file_path = os.path.join(MOVIE_DIR,path) # 获取文件大小 file_size = os.path.getsize(file_path) # 定义一个字典 res_d = { 'file_name':'性感荷官在线发牌.mp4', 'file_size':file_size, 'msg':'注意身体,多喝养分快线' } # 序列化字典 json_d = json.dumps(res_d) json_bytes = json_d.encode('utf-8') # 1.先制做字典格式的报头 header = struct.pack('i',len(json_bytes)) # 2.发送字典的报头 client.send(header) # 3.再发字典 client.send(json_bytes) # 4.再发文件数据(打开文件循环发送) with open(file_path,'rb') as f: for line in f: client.send(line) else: print('not in range') else: print('must be a number')