扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
例:点分十进IP地址(192.168.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)
10多年的蠡县网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。营销型网站建设的优势是能够根据用户设备显示端的尺寸不同,自动调整蠡县建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“蠡县网站设计”,“蠡县网站推广”以来,每个客户项目都认真落实执行。端口(port) ”端口”是英文port的意译,可以认为是设备与外界通讯交流的出口 osi模型 套接字(socket)Socket是应用层与TCP/IP协议簇通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
套接字家族 基于文件类型套接字家族的名字:AF_UNIXunix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型套接字家族的名字:AF_INET(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
tcp协议、udp协议TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)
套接字(socket)初始使用 基于TCP协议的socket server端import socket sk = socket.socket() sk.bind((\'127.0.0.1\', 8088)) sk.listen() conn, addr = sk.accept() while True: res = conn.recv(1024).decode(\'utf-8\') if res == \'bye\': break else: print(res) info = input(\'请输入:n>>>\').encode(\'utf-8\') conn.send(info) conn.close() sk.close() client端
import socket sk= socket.socket() sk.connect((\'127.0.0.1\', 8088)) while True: info = input(\'请输入:n>>>\').encode(\'utf-8\') sk.send(info) res = sk.recv(1024).decode(\'utf-8\') if res == \'bye\': break else: print(res) sk.close() 对于Address already in use的情况,可以加入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() #关闭服务器套接字(可选) 一个服务端与多个客户端交互示例
服务端
import socket sk = socket.socket() sk.bind((\'127.0.0.1\', 8088)) # listen([backlog])中的[backlog]参数代表服务端允许多少客户端连接到服务端,即阻塞队列长度,所以一共能与服务器连接的客户端共有backlog+1个 sk.listen(3) while True: conn, addr = sk.accept() # sk.accept()放在循环内部是为了每次与同一个客户端交互结束后会重新建立conn连接,以便下一个客户端连入 res = conn.recv(1024).decode(\'utf-8\') if res == \'bye\': break else: print(\'收到\',res) info = input(\'请输入>>>\').encode(\'utf-8\') conn.send(info) conn.close() # 同上面sk.accept()的作用 sk.close()
客户端(可以多个客户端逐个与服务端进行通讯)
import socket sk = socket.socket() sk.connect((\'127.0.0.1\', 8088)) while True: info = input(\'请输入>>>\').encode(\'utf-8\') if info == \'bye\': break else: sk.send(info) res = sk.recv(1024).decode(\'utf-8\') print(res) sk.close() 基于UDP协议的socket:不可靠 无连接,效率高 server端
import socket ip_port=(\'127.0.0.1\',9000) BUFSIZE=1024 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_client.bind(ip_port) while True: msg,addr=udp_server_client.recvfrom(BUFSIZE) print(msg,addr) udp_server_client.sendto(msg.upper(),addr) client端
import socket ip_port=(\'127.0.0.1\',9000) BUFSIZE=1024 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 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有粘包现象,UDP永远不会粘包
产生黏包的两种情况 发送数据时间间隔很短,数据了很小,会合到一起,产生粘包服务端
from socket import * ip_port=(\'127.0.0.1\',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(10) data2=conn.recv(10) print(\'----->\',data1.decode(\'utf-8\')) print(\'----->\',data2.decode(\'utf-8\')) conn.close()
客户端
import socket BUFSIZE=1024 ip_port=(\'127.0.0.1\',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send(\'hello\'.encode(\'utf-8\')) s.send(\'feng\'.encode(\'utf-8\')) 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
服务端
from socket import * ip_port=(\'127.0.0.1\',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) #一次没有收完整 data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的 print(\'----->\',data1.decode(\'utf-8\')) print(\'----->\',data2.decode(\'utf-8\'))
客户端
import socket BUFSIZE=1024 ip_port=(\'127.0.0.1\',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send(\'hello feng\'.encode(\'utf-8\')) 黏包的解决方案 原理:问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
直接告知客户端发送数据的长度(Server端)
import socket, subprocess sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind((\'localhost\', 8070)) sk.listen() while True: conn, addr = sk.accept() print(\'客户端:\', addr) while True: msg = conn.recv(1024) if not msg: break res_temp = subprocess.Popen(msg.decode(\'utf-8\'), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) error = res_temp.stderr.read() if error: res = error else: res = res_temp.stdout.read() date_len = len(res) conn.send(str(date_len).encode(\'utf-8\')) data = conn.recv(1024).decode(\'utf-8\') if data == \'recv_ready\': conn.sendall() conn.close()
直接告知客户端发送数据的长度(Client端)
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) res = s.connect_ex((\'127.0.0.1\', 8080)) while True: msg = input(\'>>: \').strip() if len(msg) == 0: continue if msg == \'quit\': break s.send(msg.encode(\'utf-8\')) length = int(s.recv(1024).decode(\'utf-8\')) s.send(\'recv_ready\'.encode(\'utf-8\')) send_size = 0 recv_size = 0 data = b\'\' while recv_size < length: data += s.recv(1024) recv_size += len(data) print(data.decode(\'utf-8\'))
借助struct模块打包定制报头(Server端)
import socket, struct, json import subprocess sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 就是它,在bind前加 sk.bind((\'127.0.0.1\', 8080)) sk.listen(5) while True: conn, addr = sk.accept() while True: cmd = conn.recv(1024) if not cmd: break print(\'cmd: %s\' % cmd) res = subprocess.Popen(cmd.decode(\'utf-8\'), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() print(err) if err: back_msg = err else: back_msg = res.stdout.read() conn.send(struct.pack(\'i\', len(back_msg))) # 先发back_msg的长度 conn.sendall(back_msg) # 在发真实的内容 conn.close()
借助struct模块打包定制报头(Server端)
import socket, time, struct s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) res = s.connect_ex((\'127.0.0.1\', 8080)) while True: msg = input(\'>>: \').strip() if len(msg) == 0: continue if msg == \'quit\': break s.send(msg.encode(\'utf-8\')) l = s.recv(4) x = struct.unpack(\'i\', l)[0] print(type(x), x) r_s = 0 data = b\'\' while r_s < x: r_d = s.recv(1024) data += r_d r_s += len(r_d) print(data.decode(\'gbk\')) # windows默认gbk编码 网络传输大文件的解决方案 读取固定字节内容,避免全部读进内存使得内存占用过大
Server端
import json import socket import struct sk = socket.socket() sk.bind((\'127.0.0.1\',8090)) sk.listen() buffer = 1024 conn,addr = sk.accept() # 接收 head_len = conn.recv(4) head_len = struct.unpack(\'i\',head_len)[0] json_head = conn.recv(head_len).decode(\'utf-8\') head = json.loads(json_head) filesize = head[\'filesize\'] with open(head[\'filename\'],\'wb\') as f: while filesize: print(filesize) if filesize >= buffer: content = conn.recv(buffer) f.write(content) filesize -= len(content) else: content = conn.recv(filesize) f.write(content) break conn.close() sk.close()
Client端
import os import json import struct import socket sk = socket.socket() sk.connect((\'127.0.0.1\',8090)) buffer = 2048 # 发送文件 head = {\'filepath\':r\'C:UsersygDesktop\', \'filename\':r\'02 python fullstack s9day32 基于udp的socket服务.mp4\', \'filesize\':None} file_path = os.path.join(head[\'filepath\'],head[\'filename\']) filesize = os.path.getsize(file_path) head[\'filesize\'] = filesize json_head = json.dumps(head) # 字典转成了字符串 bytes_head = json_head.encode(\'utf-8\') # 字符串转bytes # 计算head的长度 head_len = len(bytes_head) # 报头的长度 pack_len = struct.pack(\'i\',head_len) sk.send(pack_len) # 先发报头的长度 sk.send(bytes_head) # 再发送bytes类型的报头 with open(file_path,\'rb\') as f: while filesize: print(filesize) if filesize >= buffer: content = f.read(buffer) # 每次读出来的内容 sk.send(content) filesize -= len(content) else: content = f.read(filesize) sk.send(content) break sk.close() socket方法
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字 s.listen() 开始TCP监听 s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接 s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据 s.send() 发送TCP数据 s.sendall() 发送TCP数据 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() 创建一个与该套接字相关的文件 hmac模块—客户端链接合法性验证 hmac模块—客户端链接合法性验证
链接合法性验证—举例
服务端
# Server端 import os, socket, hmac secret_key = b\'secretkey\' sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) sk.bind((\'localhost\', 8889)) sk.listen() def check_client(conn): send_msg = os.urandom(32) conn.send(send_msg) h = hmac.new(secret_key, send_msg) digest = h.digest() client_digest = conn.recv(1024) return hmac.compare_digest(digest, client_digest) conn, addr = sk.accept() is_legal = check_client(conn) if is_legal: print(\'合法客户端!\') conn.send(b\'legal\') while True: recv_msg = conn.recv(1024).decode(\'utf-8\') if recv_msg.lower() == \'q\': conn.send(b\'q\') conn.close() break else: print(recv_msg) info = input(\'服务端>>>\').strip() if info: conn.send(info.encode(\'utf-8\')) else: print(\'非法客户端!\') conn.close() sk.close()
客户端
#Client端 import socket, hmac secret_key = b\'secretkey\' sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) sk.connect((\'localhost\', 8889)) msg = sk.recv(1024) h = hmac.new(secret_key, msg) digest = h.digest() sk.send(digest) is_legal = sk.recv(1024) if is_legal == b\'legal\': while True: info = input(\'客户端>>>\').strip() if info: sk.send(info.encode(\'utf-8\')) recv_msg = sk.recv(1024).decode(\'utf-8\') if recv_msg == \'q\': break else: print(recv_msg) sk.close() 利用socketserver模块处理服务端与多客户端交互问题
服务端
#Server端 import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): while True: recv_msg = self.request.recv(1024).decode(\'utf-8\') if recv_msg == \'q\': self.request.send(b\'q\') break else: print(recv_msg) info = input(\'服务端>>>\').strip() if info: self.request.send(info.encode(\'utf-8\')) if __name__ == \'__main__\': server = socketserver.ThreadingTCPServer((\'localhost\', 8889), MyServer) server.allow_reuse_address = True server.serve_forever()
客户端
#Client端 import socket sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) sk.connect((\'localhost\', 8889)) while True: info = input(\'客户端>>>\').strip() if info == \'\': continue elif info == \'q\': sk.send(info.encode(\'utf-8\')) break else: sk.send(info.encode(\'utf-8\')) recv_msg = sk.recv(1024).decode(\'utf-8\') print(recv_msg) sk.close()
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流