第八章 网络编程
网络编程
1.网络基础
用途:未来的web框架的学习 未来的工作场景做铺垫
- 两个运行中的程序如何传递信息?
- 通过文件
- 两台机器上的两个运行中的程序如何通信?
- 通过网络
-
- 网络应用开发架构
- C/S
- client 客户端
- server 服务端
- 例如:迅雷 qq 浏览器 飞秋 输入法 百度云 pycharm git VNC 红蜘蛛 各种游戏
- B/S
- browser 浏览器
- server 服务端
- 例如:淘宝 邮箱 各种游戏 百度 博客园 知乎 豆瓣 抽屉
- 统一程序的入口
- B/S和C/S架构的关系:B/S是特殊的C/S架构
- C/S
- 网络应用开发架构
3.网卡:是一个实际存在计算机中的硬件
4.mac地址:每块网卡上都有一个全球独一无二的mac地址
5.交换机:链接多台机器并帮助通讯的物理设备,只认识mac地址。
6.协议:两台物理设备之间对于要发送的内容,长度,顺序的一些约定
7.ip地址:
- ipv4协议 4位的点分十进制,32位2进制表示
- 0.0.0.0 - 255.255.255.255
- ipv6协议 6位的冒分十六进制 128位2进制表示
- 0:0:0:0:0:0-FFFF:FFFF:FFFF:FFFF:FFFF:FFFF
8.公网ip:能被所有人访问到ip地址
9.内网ip:这些区间的ip地址公网不会使用,避免了公网ip和内网ip的重叠
- 192.168.0.0 - 192.168.255.255
- 172.16.0.0 - 172.31.255.255
- 10.0.0.0 - 10.255.255.255
10.arp协议:通过ip地址获取mac地址
11.网关ip:一个局域网的网络出口,访问局域网之外的区域都需要经过路由器和网关
12.网段:指的是一个地址段 ,如x.x.x.0或x.x.0.0或x.0.0.0
13.子网掩码:判断两台机器是否在同一个网段、子网内。
14.port:端口 ,0-65535
- ip地址能够确认一台机器
- ip+port能确认一台机器上的一个应用
2.osi七层模型
- 第七层:应用层
- 第六层:表示层
- 第五层:会话层
- 第四层:传输层
- 第三层:网络层
- 第二层:数据链路层
- 第一层:物理层
3.osi 五层协议
层数 | 名称 | 协议 | 物理设备 | |
---|---|---|---|---|
第五层 | 应用层 | python代码相关 | http/https/ftp/smtp协议 | |
第四层 | 传输层 | port端口 | tcp、udp协议 | 四层路由器、四层交换机 |
第三层 | 网络层 | ip地址相关 | ipv4、ipv6协议 | 三层路由器、三层交换机 |
第二层 | 数据链路层 | mac地址相关 | arp协议,以太网协议 | 网卡、二层交换机 |
第一层 | 物理层 | 电信号 |
4.tcp、udp协议
tcp协议:
- 特点:
- 可靠、慢、全双工通信
- 建立链接时:三次握手
- 断开链接时:四次挥手
- 在建立起连接之后
- 发送的每一条信息都有回执
- 为了保证数据的完整性,还有重传机制
- 长连接:会一直占用双方的端口
- 能够传递的数据长度几乎没有限制
应用场景:
- 文件的上传下载
- 发送邮件,网盘,缓存电影等
SYN=1,是tcp的标志位,代表这是一个请求信息
ACK=1,代表确认信息
seq=x 代表这一个数据包的序列号, 一半都加到ACK+1代表是这个包
3次握手:connect客户端发起一个syn链接请求,如果得到了server端响应ack的同时还会再收到一个由server端发来的syc链接请求client端进行回复ack之后,就建立起了一个tcp协议的链接
4次挥手:每一端发起的close操作都是一次fin的断开请求,得到'断开确认ack'之后,就可以结束一端的数据发送
如果两端都发起close,那么就是两次请求和两次回复,一共是四次操作.
服务端确认信息和close操作不能同时,是因为可能服务端还没处理完数据
udp协议:
1、特点:
- 无连接,速度快
- 可能会丢消息
- 能够传递的长度有限,是根据数据传递的设备的设置有关
应用场景:
- 即时通信类
- qq,微信,飞秋等
tcp协议和udp协议的区别:
tcp协议:是一个面向连接的,流式的,可靠的,慢的,全双工通信
- 邮件 文件 http web
udp协议:是一个面向数据报的,无连接的,不可靠的,快的,能完成一对一,一对多,多对多的高效通讯协议
- 即时聊天工具 在线视频
总结
- TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
- UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
- tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头
5.scoket(套接字)
什么是socket?
Socket是应用层与TCP/ip协议族通信的中间软件抽象层,它是一组接口,帮助我们完成了所有信息的组织和拼接
基于tcp协议的socket
- tcp是基于链接的,必须先启动服务端,绑定ip和端口,然后再启动客户端去链接服务端
#服务端 import socket socket_server=socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) socket_server.bind(('127.0.0.1',8802)) #把地址绑定到套接字 socket_server.listen(5)#监听链接 conn,addr = socket_server.accept()#接受客户端链接 print(conn,addr) #conn为套接字,addr为地址端口 ret = conn.recv(1024)#接受客户端信息 print(ret) conn.send(ret.upper())#向客户端发送信息 conn.close()#关闭客户端套接字 socket_server.close()#关闭服务端套接字
#客户端 import socket socket_client = socket.socket() #创建客户端套接字 socket_client.connect(('127.0.0.1',8802))#尝试链接服务器 socket_client.send('hello!'.encode('utf-8'))#发送消息 #send、recv本质是发给自己的操作系统,操作网卡在根据tcp、udp协议往后面传 ret = socket_client.recv(1024) #接受消息 print(ret.decode('utf-8')) socket_client.close() #关闭客户端套接字 #注意消息的传递都是bytes类型
加上通讯循环+解决bug+多台客户端一个个访问
#服务端 import socket socket_server=socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #会遇到端口在用,加上这句搞定 socket_server.bind(('127.0.0.1',8805)) #把地址绑定到套接字 socket_server.listen(5) while True: #这里循环接受客户端连接,一个断链后,可以继续接受。串行,这里还不是并发 conn,addr = socket_server.accept() print(conn,addr) while True: try: data = conn.recv(1024) if not data:break print(data) conn.send(data.upper()) except ConnectionResetError:#在window机器上会报这个错误 break conn.close()#关闭一个客户端 socket_server.close() #客户端 import socket socket_client = socket.socket() socket_client.connect(('127.0.0.1',8805)) while True: try: msg = input('<<<').strip() if not msg: continue socket_client.send(msg.encode('utf-8')) data = socket_client.recv(1024) print(data.decode('utf-8')) except ConnectionResetError: break socket_client.close()
socket实现ssh服务
#服务端 import socket,subprocess ip_port = ('127.0.0.1',8081) ssh_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) ssh_server.bind(ip_port) ssh_server.listen(5) while 1: conn,addr=ssh_server.accept() print('客户端',addr) while 1: cmd = conn.recv(1024) if not cmd:break print('recv cmd',cmd) res = subprocess.Popen(cmd.decode('GBK'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) #windows机器上是GBK stderr = res.stderr.read() stdout = res.stdout.read() print('res length',len((stdout))) conn.send(stderr) conn.send(stdout) conn.close() #客户端 import socket ip_port = ('127.0.0.1', 8081) ssh_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_client.connect_ex(ip_port) while 1: msg = input('>>').strip() if not msg:continue if msg == 'quit':break ssh_client.send(msg.encode('utf-8')) res = ssh_client.recv(1024) print(res.decode('GBK'))
基于udp协议的socket:
#server import socket import subprocess ip_port = ('127.0.0.1', 9003) bufsize = 1024 udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_server.bind(ip_port) #不用建立链接 while True: # 收消息 cmd, addr = udp_server.recvfrom(bufsize) print('用户命令----->', cmd,addr) # 逻辑处理 res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE) stderr = res.stderr.read() stdout = res.stdout.read() # 发消息 udp_server.sendto(stdout + stderr, addr) #回消息也要指定端口 udp_server.close()
#client from socket import * import time ip_port = ('127.0.0.1', 9003) bufsize = 1024 udp_client = socket(AF_INET, SOCK_DGRAM) while True: msg = input('>>: ').strip() if len(msg) == 0: continue udp_client.sendto(msg.encode('utf-8'), ip_port) #发送消息时要告诉向哪个端口发消息 data, addr = udp_client.recvfrom(bufsize) print(data.decode('utf-8'), end='')
6.粘包现象:
定义:同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种现象就是黏包
粘包的成因:只有TCP有粘包现象,UDP永远不会粘包
tcp协议的粘包现象:
- 什么是粘包现象
- 发生在发送端的粘包
- 由于两个数据的发送时间间隔短+数据的长度小
- 所以由tcp协议的优化机制将两条信息作为一条信息发送出去了
- 为了减少tcp协议中的“确认收到”的网络延迟时间
- 发生再接收端的粘包
- 由于tcp协议中所传输的数据无边界,所以来不及接收的多条
- 数据会在接收放的内核的缓存端黏在一起
- 本质: 接收信息的边界不清晰
- 发生在发送端的粘包
总结:
- 粘包现象只发生在tcp协议
- 从表面上来看,粘包问题主要是因为发送方和接受方的缓存机制,tcp协议的面向流通信的特点
- 实际上。主要还会因为接受方不知道消息之间的边界,不知道一次性提取多少字节的数据造成的
解决粘包问题 :
- 自定义协议1
- 首先发送报头的长度,报头长度4个字节,内容是发送的报文的字节长度
- 在发送报头
- 自定义协议2
- 我们专门用来做文件发送的协议
- 先发送报头字典的字节长度,再发送字典(字典中包含文件的名字、大小),再发送文件的内容
#服务端 import socket,subprocess import json,struct ip_port = ('127.0.0.1',8082) ssh_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) ssh_server.bind(ip_port) ssh_server.listen(5) while 1: conn,addr=ssh_server.accept() print('客户端',addr) while 1: try: #1、收命令 cmd = conn.recv(8096) if not cmd:break #2、执行命令拿到结果 res = subprocess.Popen(cmd.decode('GBK'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) stderr = res.stderr.read() stdout = res.stdout.read() #3、把命令的结果返回给客户端 #第一步:制作固定长度的报头 header_dic = { 'filename':'a.txt', 'md5':'xxx', 'total_size':len(stdout)+len(stderr) } header_json = json.dumps(header_dic) header_bytes = header_json.encode('GBK') #第二步:先发送报头的长度 conn.send(struct.pack('i',len(header_bytes))) #这里的固定长度是4,为了防止粘包 #第三步:在发送报头 conn.send(header_bytes) #第四步:在发送真实的数据 conn.send(stderr) conn.send(stdout) except ConnectionResetError: break conn.close() ssh_server.close()
#客户端 import socket,json,struct ip_port = ('127.0.0.1', 8082) ssh_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_client.connect_ex(ip_port) while 1: msg = input('>>').strip() if not msg:continue if msg == 'quit':break ssh_client.send(msg.encode('GBK')) #2、拿到命令,并打印 #第一步:接受报头的长度 obj=ssh_client.recv(4) #bytes类型的 header_size =struct.unpack('i',obj)[0] #解包 报头的长度 #第二步:接受报头 header_bytes=ssh_client.recv(header_size) #收的报头的长度 #第三步:从报头中解析出对应真实的数据 header_json = header_bytes.decode('GBK') header_dic = json.loads(header_json) total_size = header_dic['total_size'] #第四步:接受真实的数据 recv_size = 0 recv_data = b'' while recv_size < total_size: res = ssh_client.recv(1024) recv_size+=len(res) recv_data+=res print(recv_data.decode('GBK')) ssh_client.close()
7.实现文件传输
简单版本:
#server import os import socket,subprocess import json,struct ip_port = ('127.0.0.1',9999) ssh_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) ssh_server.bind(ip_port) ssh_server.listen(5) share_dir =r'D:\pycharm2018\python_stack\03面向对象&网络编程基础\网络编程_\文件传输\server\share' while 1: conn,addr=ssh_server.accept() print('客户端',addr) while 1: try: #1、收命令 res = conn.recv(8096) #get 1.mp4 if not res:break #2、解析命令,拿到相应的参数 cmds=res.decode('GBK').split() filename = cmds[1] #3、以读的方式打开文件,读取文件内容返回给客户端 #第一步:制作固定长度的报头 header_dic = { 'filename':filename, 'md5':'xxx', 'total_size':os.path.getsize('%s\%s'%(share_dir,filename)) } header_json = json.dumps(header_dic) header_bytes = header_json.encode('GBK') #第二步:先发送报头的长度 conn.send(struct.pack('i',len(header_bytes))) #这里的固定长度是4,为了防止粘包 #第三步:在发送报头 conn.send(header_bytes) #第四步:在发送真实的数据 with open('%s\%s'%(share_dir,filename),'rb')as f: for line in f: conn.send(line) except ConnectionResetError: break conn.close() ssh_server.close()
#client import socket,json,struct ip_port = ('127.0.0.1', 9999) ssh_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_client.connect_ex(ip_port) download_dir = r'D:\pycharm2018\python_stack\03面向对象&网络编程基础\网络编程_\文件传输\client\download' while 1: msg = input('>>').strip() #get 1.mp4 if not msg:continue if msg == 'quit':break ssh_client.send(msg.encode('GBK')) #2、拿到命令,并打印 #第一步:接受报头的长度 obj=ssh_client.recv(4) #bytes类型的 header_size =struct.unpack('i',obj)[0] #解包 报头的长度 #第二步:接受报头 header_bytes=ssh_client.recv(header_size) #收的报头的长度 #第三步:从报头中解析出对应真实的数据 header_json = header_bytes.decode('GBK') header_dic = json.loads(header_json) total_size = header_dic['total_size'] filename = header_dic['filename'] #第四步:接受真实的数据 with open('%s\%s'%(download_dir,filename),'wb')as f: #windows recv_size = 0 while recv_size < total_size: res = ssh_client.recv(1024) f.write(res) recv_size+=len(res) print('总共%s,现在下载了%s' % (total_size, recv_size)) ssh_client.close()
函数版本:
#服务端 import os import socket,subprocess import json,struct share_dir =r'D:\pycharm2018\python_stack\03面向对象&网络编程基础\网络编程_\文件传输\函数版本\server\share' def get(conn,filename): # 3、以读的方式打开文件,读取文件内容返回给客户端 # 第一步:制作固定长度的报头 header_dic = { 'filename': filename, 'md5': 'xxx', 'total_size': os.path.getsize('%s\%s' % (share_dir, filename)) } header_json = json.dumps(header_dic) header_bytes = header_json.encode('GBK') # 第二步:先发送报头的长度 conn.send(struct.pack('i', len(header_bytes))) # 这里的固定长度是4,为了防止粘包 # 第三步:在发送报头 conn.send(header_bytes) # 第四步:在发送真实的数据 with open('%s\%s' % (share_dir, filename), 'rb')as f: for line in f: conn.send(line) def put(ssh_client): # 2、拿到命令,并打印 # 第一步:接受报头的长度 obj = ssh_client.recv(4) # bytes类型的 header_size = struct.unpack('i', obj)[0] # 解包 报头的长度 # 第二步:接受报头 header_bytes = ssh_client.recv(header_size) # 收的报头的长度 # 第三步:从报头中解析出对应真实的数据 header_json = header_bytes.decode('GBK') header_dic = json.loads(header_json) total_size = header_dic['total_size'] filename = header_dic['filename'] # 第四步:接受真实的数据 with open('%s\%s' % (share_dir, filename), 'wb')as f: # windows recv_size = 0 while recv_size < total_size: res = ssh_client.recv(1024) f.write(res) recv_size += len(res) print('总共%s,现在下载了%s' % (total_size, recv_size)) def run(): ip_port = ('127.0.0.1',8889) ssh_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) ssh_server.bind(ip_port) ssh_server.listen(5) while 1: conn,addr=ssh_server.accept() print('客户端',addr) while 1: try: #1、收命令 res = conn.recv(8096) #get 1.mp4 if not res:break #2、解析命令,拿到相应的参数 cmd=res.decode('GBK') cmds = cmd.split() print(cmds) filename = cmds[1] if cmds[0] == 'get': get(conn,filename) elif cmds[0] == 'put': put(conn) except ConnectionResetError: break conn.close() ssh_server.close() if __name__ == '__main__': run()
#客户端 import socket,json,struct import os download_dir = r'D:\pycharm2018\python_stack\03面向对象&网络编程基础\网络编程_\文件传输\简单版本\client\download' def get(ssh_client): # 2、拿到命令,并打印 # 第一步:接受报头的长度 obj = ssh_client.recv(4) # bytes类型的 header_size = struct.unpack('i', obj)[0] # 解包 报头的长度 # 第二步:接受报头 header_bytes = ssh_client.recv(header_size) # 收的报头的长度 # 第三步:从报头中解析出对应真实的数据 header_json = header_bytes.decode('GBK') header_dic = json.loads(header_json) total_size = header_dic['total_size'] filename = header_dic['filename'] # 第四步:接受真实的数据 with open('%s\%s' % (download_dir, filename), 'wb')as f: # windows recv_size = 0 while recv_size < total_size: res = ssh_client.recv(1024) f.write(res) recv_size += len(res) print('总共%s,现在下载了%s' % (total_size, recv_size)) def put(ssh_client,filename): # 3、以读的方式打开文件,读取文件内容返回给客户端 # 第一步:制作固定长度的报头 header_dic = { 'filename': filename, 'md5': 'xxx', 'total_size': os.path.getsize('%s\%s' % (download_dir, filename)) } header_json = json.dumps(header_dic) header_bytes = header_json.encode('GBK') # 第二步:先发送报头的长度 ssh_client.send(struct.pack('i', len(header_bytes))) # 这里的固定长度是4,为了防止粘包 # 第三步:在发送报头 ssh_client.send(header_bytes) # 第四步:在发送真实的数据 with open('%s\%s' % (download_dir, filename), 'rb')as f: for line in f: ssh_client.send(line) print(line) def run(): ip_port = ('127.0.0.1', 8889) ssh_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ssh_client.connect_ex(ip_port) while 1: try: msg = input('>>').strip() #get 1.mp4 if not msg:continue if msg == 'quit':break ssh_client.send(msg.encode('GBK')) inp = msg.split() filename = inp[1] if inp[0] == 'get': #下载操作,服务端读取,内容发给客户端 get(ssh_client) elif inp[0] == 'put': #上传操作,从本地读取,发给服务端 put(ssh_client,filename) except ConnectionResetError: break ssh_client.close() if __name__ == '__main__': run()
面向对象版本:
#服务端 import socket import struct import json,subprocess import os class MYTCPServer: address_famliy =socket.AF_INET #协议 socket_type = socket.SOCK_STREAM #TCP coding = 'GBK' max_packet_size = 8096 request_queue_size = 5 allow_reuse_address = False server_dir = r'D:\pycharm2018\python_stack\03面向对象&网络编程基础\网络编程_\文件传输\面向对象版本\load' def __init__(self,server_address): self.server_address = server_address self.socket = socket.socket(self.address_famliy,self.socket_type) self.server_bind() self.server_activate() def server_bind(self): 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): self.socket.listen(self.request_queue_size) def server_close(self): self.socket.close() def get_request(self): return self.socket.accept() def close_request(self,request): request.close() def get(self): header_dic = { 'filename':self.filename, 'total_size':os.path.getsize(self.server_dir) } head_json = json.dumps(header_dic) header_bytes = head_json.encode(self.coding) #发送报头长度 self.conn.send(struct.pack('i',len(header_bytes))) #再发报头 self.conn.send(header_bytes) #发送真实数据 with open('%s'%self.filename,'rb')as f: for line in f: self.conn.send(line) def put(self): pass def run(self): while 1: self.conn,self.addr = self.get_request() while 1: try: # 1、收命令 res = self.conn.recv(self.max_packet_size) if not res:break print(res) # 2、解析命令,拿到相应的参数 cmd = res.decode(self.coding) cmds = cmd.split() print(cmds) self.cmd=cmds[0] self.filename = cmds[1] if hasattr(self,self.cmd): func = getattr(self,self.cmd) func() else:print('没有这个功能') except ConnectionResetError: break ##### obj =MYTCPServer(('127.0.0.1',8888)) obj.run()
·
#客户端 import socket import struct import json,subprocess import os class MYTCPClient: address_famliy =socket.AF_INET #协议 socket_type = socket.SOCK_STREAM #TCP coding = 'GBK' server_dir =r'D:\pycharm2018\python_stack\03面向对象&网络编程基础\网络编程_\文件传输\面向对象版本\up' def __init__(self,server_address): self.server_address = server_address self.socket = socket.socket(self.address_famliy,self.socket_type) self.connect_ex() def connect_ex(self): self.socket.connect_ex(self.server_address) def server_close(self): self.socket.close() def close_request(self,request): request.close() def get(self): obj=self.socket.recv(4) header_size = struct.unpack('i',obj)[0] header_bytes = self.socket.recv(header_size) header_json = header_bytes.decode(self.coding) header_dic = json.loads(header_json) #根据字典筛选有用信息 total_size = header_dic['total_size'] filename = header_dic['filename'] #写文件 with open('%s'%self.filename,'rb')as f: recv_size = 0 while recv_size< total_size: res=self.socket.recv(1024) f.write(recv_size) recv_size+=len(res) print('总共%s,现在下载了%s' % (total_size, recv_size)) def put(self): pass def run(self): while 1: try: # 1、收命令 msg = input('>>').strip() # get 1.mp4 if not msg: continue if msg == 'quit': break self.socket.send(msg.encode(self.coding)) print(msg.encode(self.coding)) inp = msg.split() self.filename = inp[1] self.cmd = inp[0] if hasattr(self,self.cmd): func = getattr(self,self.cmd) func() except ConnectionResetError: break ##### obj =MYTCPClient(('127.0.0.1',8888)) obj.run()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)