基于TCP协议的socket
一、基本用法
1.1 服务端
import socket # 1.买手机 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # server = socket.socket() 默认为网络类型的 TCP协议 # 参数1指定socket类型为AF_INET,表示为网络类型 # 参数2指定的传输协议为SOCK_STREAM表示TCP协议, SOCK_DGRAM为UDP协议 # 2.插入手机卡 server.bind(("127.0.0.1",8080)) # 作为服务器必须要有自己的IP和端口号,并且不应该发生变化 # 127.0.0.1 为回送地址,表示当前电脑本身 # IP一定是本机IP,本机可能会有多个IP(无线|有线) # 注意:需要参数是一个元组,端口就是普通证书 # 3.手机开始待机 server.listen() # 开机监听8080端口,看着这个端口有没有数据传输过来 while True: # 4.接电话 client_socket, close_addr = server.accept() # 完成了三次握手 print("握手成功") # 接受连接请求 # client表示客户端的socket # addr 表示客户端的地址信息 # accept 是一个阻塞函数,会一直等到有客户链接过来,再继续执行 while True: try: # 5.收发消息 # 发送数据与接收数据,对方可能会异常下线,都放入try中 data = client_socket.recv(1024) # 在linux,windows中,对方如果强行下线了,服务器不会抛出异常,只会收到空消息 # 故需要嘉盛if判断,如果为空,则意味着对方下线了,应该关闭链接,跳出循环 if not data: client_socket .close() break print("客户端发来的数据:", data) msg = input("输入发送的信息:").strip() client_socket.send(msg.encode("utf-8")) except ConnectionResetError as e: print("%s %s" % (close_addr[0],close_addr[1]),e) # 断开链接 client_socket.close() # 完成四次挥手 break # 6.关机,但是服务器一般不会关机 # server.close()
1.2 客户端
import socket # 1.买电话 client = socket.socket() # 2.打电话 client.connect(("127.0.0.1",8080)) # 作为客户端,IP和端口可以变化,所有系统会自动随机端口给客户端 # 添加循环,用来重复收发数据 while True: msg = input("输入发送的信息:").strip() if not msg: continue client.send(msg.encode("utf-8")) # 3.开始通话,发送与接收数据 data = client.recv(1024) print("服务端发送的信息",data.decode("utf-8")) client.close() # 4.关机
二、常见函数
# 服务端套接字函数 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() 得到阻塞套接字操作的超时时间
三、
""" 客户端, 可随时开启,随时关闭 """ import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) data = client.recv(1024) print(data) """ 服务端 固定的ip和port 24小时不间断提供服务 """ import socket 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()
四、
1.可能是由于你已经启动了服务器程序,却又再次启动了服务器程序,同一个端口不能被多个进程使用导致!
2.三次握手或四次挥手时,发生了异常导致对方程序已经结束而服务器任然处于time_wait状态导致!
3.在高并发的场景下,由于链接的客户端太多,也会产生大量处于time_wait状态的链接
# 第1种原因,很简单关闭之前运行的服务器即可 """ 如果已经开启了服务器 再次运行将会抛出 端口占用异常 把之前开启的服务器关掉即可 有些情况 明明已经关闭了进程 但是 还是端口占用 可能是进程正在后台运行 可以通过端口查出进程 进行关闭 windows下 netstat -ano | findstr 9898 tasklist | findstr 进程id 拿到进程名称 taskkill /f /t /im 进程名称 """ # 第2,3中原因导致的问题,有两种解决方案: #加入一条socket配置,重用ip和端口 import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket.socket() sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字 sk.listen() #监听链接 conn,addr = sk.accept() #接受客户端链接 ret = conn.recv(1024) #接收客户端信息 print(ret) #打印客户端信息 conn.send(b'hi') #向客户端发送信息 conn.close() #关闭客户端套接字 sk.close() #关闭服务器套接字(可选)
粘包 仅发生在TCP协议中
1. 发送端 发送的数据量小 并且间隔短 会粘
2. 接收端 一次性读取了两次数据的内容 会粘
3. 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起无论是那种情况,其根本原因在于 接收端不知道数据到底有多少
# 先发长度给对方 再发真实数据 """ 发送端 1. 使用struct 将真实数据的长度转为固定的字节数据 2. 发送长度数据 3. 发送真实数据 接收端 1.先收长度数据 字节数固定 2.再收真实数据 真实可能很长 需要循环接收 发送端和接收端必须都处理粘包 才算真正的解决了 """ # 客户端 import socket import struct c = socket.socket() c.connect(("127.0.0.1",8888)) while True: cmd = input(">>>:").strip() c.send(cmd.encode("utf-8")) data = c.recv(4) length = struct.unpack("i",data)[0] print(length) size = 0 res = b"" while size < length: temp = c.recv(1024) size += len(temp) res += temp print(res.decode("gbk")) # 服务端 import socket import subprocess import struct server = socket.socket() server.bind(("127.0.0.1",8888)) server.listen() while True: client, addr = server.accept() while True: cmd = client.recv(1024).decode("utf-8") p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1) data = p.stdout.read()+p.stderr.read() length = len(data) len_data = struct.pack("i",length) client.send(len_data) print(length) client.send(data)
什么是报头:当需要在传输数据时 传呼一些额外参数时就需要自定义报头例如我们要实现文件上传下载,不光要传输文件数据,还需要传输文件名字,md5值等等,报头本质是一个json 数据。
# 客户端 import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) # 1.先接受字典报头 header_dict = client.recv(4) # 2.解析报头 获取字典的长度 dict_size = struct.unpack('i',header_dict)[0] # 解包的时候一定要加上索引0 # 3.接收字典数据 dict_bytes = client.recv(dict_size) dict_json = json.loads(dict_bytes.decode('utf-8')) # 4.从字典中获取信息 print(dict_json) recv_size = 0 real_data = b'' while recv_size < dict_json.get('file_size'): # real_size = 102400 data = client.recv(1024) real_data += data recv_size += len(data) print(real_data.decode('gbk')) # 服务端 import socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: cmd = conn.recv(1024) if len(cmd) == 0:break cmd = cmd.decode('utf-8') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) res = obj.stdout.read() + obj.stderr.read() d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'} json_d = json.dumps(d) # 1.先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 2.发送字典报头 conn.send(header) # 3.发送字典 conn.send(json_d.encode('utf-8')) # 4.再发真实数据 conn.send(res) # conn.send(obj.stdout.read()) # conn.send(obj.stderr.read()) except ConnectionResetError: break conn.close()# 客户端 import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) # 1.先接受字典报头 header_dict = client.recv(4) # 2.解析报头 获取字典的长度 dict_size = struct.unpack('i',header_dict)[0] # 解包的时候一定要加上索引0 # 3.接收字典数据 dict_bytes = client.recv(dict_size) dict_json = json.loads(dict_bytes.decode('utf-8')) # 4.从字典中获取信息 print(dict_json) recv_size = 0 real_data = b'' while recv_size < dict_json.get('file_size'): # real_size = 102400 data = client.recv(1024) real_data += data recv_size += len(data) print(real_data.decode('gbk')) # 服务端 import socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: cmd = conn.recv(1024) if len(cmd) == 0:break cmd = cmd.decode('utf-8') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) res = obj.stdout.read() + obj.stderr.read() d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'} json_d = json.dumps(d) # 1.先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 2.发送字典报头 conn.send(header) # 3.发送字典 conn.send(json_d.encode('utf-8')) # 4.再发真实数据 conn.send(res) # conn.send(obj.stdout.read()) # conn.send(obj.stderr.read()) except ConnectionResetError: break conn.close()
5.1 客户端
import socket import struct import json import os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print('file:%s is not exists' %filename) return else: filesize=os.path.getsize(filename) head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack('i',len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,'rb') as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print('upload successful') client=MYTCPClient(('127.0.0.1',8080)) client.run()
import socket import struct import json import subprocess import os class MYTCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 server_dir='file_upload' def __init__(self, server_address, bind_and_activate=True): """Constructor. May be extended, do not override.""" self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. """ self.socket.close() def get_request(self): """Get the request and client address from the socket. """ return self.socket.accept() def close_request(self, request): """Called to clean up an individual request.""" request.close() def run(self): while True: self.conn,self.client_addr=self.get_request() print('from client ',self.client_addr) while True: try: head_struct = self.conn.recv(4) if not head_struct:break head_len = struct.unpack('i', head_struct)[0] head_json = self.conn.recv(head_len).decode(self.coding) head_dic = json.loads(head_json) print(head_dic) #head_dic={'cmd':'put','filename':'a.txt','filesize':123123} cmd=head_dic['cmd'] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) except Exception: break def put(self,args): file_path=os.path.normpath(os.path.join( 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.conn.recv(self.max_packet_size) f.write(recv_data) recv_size+=len(recv_data) print('recvsize:%s filesize:%s' %(recv_size,filesize)) tcpserver1=MYTCPServer(('127.0.0.1',8080)) tcpserver1.run()