Socket网络编程
socket通常被称作"套接字",应用程序通过"套接字"向网络发出请求或者应答网络请求,是主机或一台计算机上的进程可以通信。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,服务器和客户端各自维护一个"文件",在建立连接打开后,各自的"文件"可以被对方读取和可以向自己的"文件"写入内容 。通讯结束时,关闭各自的"文件"。socket是实现TCP,UDP协议的接口,便于使用TCP、UDP。
socket简单的例子
服务器(server)
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket server = socket.socket() # 声明socket类型,同时创建socket实例 server.bind(('localhost', 8888)) # 为socket实例绑定IP地址和端口号。 server.listen(5) # 启动监听,等待客户端的接入请求 while True: print('等待客户端接入...') conn, addr = server.accept() # Accept阻塞,直到有客户端连接进来 client_data = conn.recv(1024).decode('utf-8') # 接收客户端的信息 print('客户端的消息:',client_data) conn.send('已连接'.encode('utf-8')) # 向客户端发送消息 server.close() # 关闭连接
客户端 (client)
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket client = socket.socket() # 创建一个实例 client.connect(('localhost', 8888)) # 连接到服务器 client.send('请求连接'.encode()) # 发送信息给服务器 client_data = client.recv(1024).decode('utf-8') # 接受服务器反馈的信息 print(client_data) client.close() # 关闭连接
Socket对象常用的方法
服务器端的方法:
- s.bind() #绑定IP地址和端口(host,port)到套接字,在AF_INET下以元组(host,port)的方式表示
- s.listen() #开启TCP监听,backlog指定在拒绝连接之前,操作系统的最大挂起数量,至少1一般都为5
- s.accept() # 被动等待TCP客户端连接,默认为阻塞,等待有客户端连接,程序才会向下走
客户端的方法:
- s.connect() #主动初始化TCP服务器连接,一般address的格式化为元组(host,port),如果连接错误,返回socket.error错误
- s.connect() # connect()函数的扩展版本,出错时犯会错误代码,不会抛出异常
公用的方法:
- s.recv() #接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略
- s.send() #发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
- s.sendall() #完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
- s.recvfrom() #接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址
- s.sendto() #发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
- s.close() #关闭套接字
- s.getpeername() # 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port) 这个方法以及下面的方法我是没用所以就不详细说了,了解一下就好
- s.getsockname() #返回套接字自己的地址。通常是一个元组(ipaddr,port)
- s.setsockopt(level,optname,value ) #设置给定套接字选项的值。
- s.getsockopt(level,optname,value ) # 返回套接字选项的值
- s.settimeout(timeout) # 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
- s.gettimeout() # 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
- s.fileno() #返回套接字的文件描述符。
- s.setblocking(flag) #如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
- s.makefile() #创建一个与该套接字相关连的文件
使用socket实现简单的远程输入系统命令返回结果值
服务器
import socket import os server = socket.socket() server.bind(('localhost', 8888)) server.listen() print('等待客户端接入') while True: conn, addr = server.accept() print('new conn', addr) while True: data = conn.recv(1024).decode("utf-8") if not data: print("客户端断开") break print("执行指令:", data) cmd_res = os.popen(data).read().encode("utf-8") if len(cmd_res) == 0: cmd_res = '输入的命令不合法'.encode('utf-8') conn.send(str(len(cmd_res)).encode('utf-8')) # 向客户端发送它要接受的数据的大小 print(str(len(cmd_res))) check_res = conn.recv(1024) # 主要是为了解决粘包的问题 conn.send(cmd_res) server.close()
客户端
#!/usr/bin/env python #-*-coding:utf-8-*- import socket client = socket.socket() client.connect(('localhost', 8888)) while True: cmd = input('>>>').strip().encode("utf-8") if cmd == 'exit'.encode('utf-8'): break if len(cmd) == 0: continue client.send(cmd) len_res = client.recv(1024).decode("utf-8") client.send(bytes('ok',encoding='utf8')) # 主要是为了在数据太多的时候发生粘包的情况 # print('返回数据的大小', len_res) 查看要接受多少数据 received_num = 0 received_data = '' while received_num != int(len_res): # 主要是实现数据在一次返回中全部接收完毕 recv_num = client.recv(1024) received_num += len(recv_num) received_data += str(recv_num) print(eval(received_data).decode('utf-8')) # cmd_res = client.recv(1024).decode("utf-8") # print(cmd_res) client.close()
粘包;主要是为了解决上面的check_res和下面的返回的数据粘在一起,导致我们的结果不正确。所以在发送数据大小后立马实现接收客户端对数据大小的确认,这样就会是数据产生粘包。
在上面创建socket实例的时候(socket.socket())后面的括号有三个参数
第一个参数:地址簇
- socket.AF_INET 代表的是使用IPV4(默认)
- socket.AF_INET6 代表的是IPv6
第二个参数:类型
- socket.SOCK_STREAM 使用的是TCP(默认)
- socket.SOCK_DGRAM 使用的是UDP
第三个参数:协议
- 0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
下面的列子是根据上面的例子实现远程下载
FTPserver
#!/usr/bin/env python #-*-coding:utf-8-*- import socket import os,subprocess import hashlib server = socket.socket() server.bind(('localhost', 8888)) server.listen() print('等待客户端接入') while True: conn, addr = server.accept() print('new conn', addr) while True: data = conn.recv(1024).decode("utf-8") if not data: print("客户端{}断开".format(addr)) break cmd, filename = data.split() if os.path.isfile(filename): f = open(filename, 'rb') m = hashlib.md5() file_size = str(os.stat(filename).st_size).encode('utf-8') conn.send(file_size) ack = conn.recv(1024) # 粘包 for line in f: m.update(line) conn.send(line) f.close() conn.send(m.hexdigest().encode('utf-8')) # cmd_res = subprocess.getoutput(data).encode('utf-8') # cmd_len = str(len(cmd_res)).encode('utf-8') # conn.send(cmd_len) # ack = conn.recv(1024) # 粘包 # conn.send(cmd_res) server.close()
client
#!/usr/bin/env python #-*-coding:utf-8-*- import socket, hashlib client = socket.socket() client.connect(('192.168.132.66', 8888)) while True: cmd = input('>>>').strip().encode("utf-8") if cmd == 'exit'.encode('utf-8'): break if len(cmd) == 0: continue if cmd.decode('utf-8').startswith('get'): client.send(cmd) file_size = int(client.recv(1024).decode('utf-8')) client.send(b'ok') filename = cmd.decode('utf-8').split()[1] f = open(filename + '.new','wb') m = hashlib.md5() received_data = 0 while received_data != file_size: if file_size - received_data > 1024: size = 1024 else: size = file_size - received_data data = client.recv(size) f.write(data) received_data += len(data) m.update(data) else: print('file recv done',received_data,file_size) received_md5 = client.recv(1024).decode('utf-8') tota_md5 = m.hexdigest() print('发送',received_md5) print("接收",tota_md5) f.close() client.close()
以上的实例中,客户端必须排队与服务器进行通讯,只有当正在通讯的客户端断开连接才能够到下一个客户端通讯,下面我们将说道多个客户端可以同时和服务器通讯,也就是客户端并发。
多并发服务器
#!/usr/bin/env python # -*-coding:utf-8-*- import socketserver # 实现并发的需要的模块 class MyTCPHandler(socketserver.BaseRequestHandler): def handle(self): while True: try: self.data = self.request.recv(1024).strip().decode("utf-8") # self.data 为接收到的信息并转换为utf-8的格式 print("{} wrote:".format(self.client_address[0])) # self.client_address[0]表示的是客户端的IP地址与socket的addr是一样的 # self.client_address[1]表示的是客户端的端口号与socket的port是一样的 print(self.data) if not self.data: break # 上面的判断是客户端自动结束程序关闭连接通道的判断 self.request.send(self.data.upper().encode('utf-8')) except ConnectionResetError as e: print("erro", e) break # 上面的错误处理是直接断开程序做的判断捕获的异常 if __name__ == "__main__": HOST, PORT = "localhost", 8888 # 服务器绑定的IP和端口号 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) # 这个实现多并发的关键就在ThreadingTCPServer()的方法实现了可以同时和多个会话 # 传入端口地址和我们新建的继承自socketserver模块下的BaseRequestHandler类 实例化对象 server.serve_forever() # 通过调用对象的serve_forever()方法来激活服务端
客户端就用简单的客户端就好
#!/usr/bin/env python # -*-coding:utf-8-*- import socket client = socket.socket() client.connect(("localhost", 8888)) while True: msg = input(">>>").strip() if not msg: continue if msg == 'exit': break client.send(msg.encode("utf-8")) received = client.recv(1024) print(received.decode("utf-8")) client.close()
上面的就是一些基础的socket的应用,可以在上面的基础上深入的使用socket。这些是关于我对socket的简单的初期认识。