1.socket 概念
socket 作用是让两台电脑实现通信。工作方式是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 网络通信都是基于 ip+port 定位到具体机器上的具体服务。每台电脑的操作系统有0-65535个port(端口),每个prot都可以独立对外提供服务。如果把一个公司比做一台电脑 ,那公司的前台就相当于ip地址, 每个员工的分机号就相当于端口,你想找公司某个人,必须先打电话到总机,然后再转分机 。
2.实现基本步骤
用流程图可以描述为:
客户端的基本步骤:
# Author: zjt import socket # 第一步:声明协议类型,并生产socket对象 client = socket.socket() # 不传值的话,默认就是ipv4,和TCP # 第二步:建立链接 # connect()只接收一个参数,但是需要传入对方ip地址以及端口号,才能准确连接,所以将两个参数放入一个元组进行传入 client.connect(("localhost",6969)) # 第三步:向服务器端发送数据 client.send(b"hello world!") # 传入发送内容Python2中可用发送字节和字符串,而Python3中只能发字节 #第四步: 接收服务器端返回结果 data = client.recv(1024) #写入从服务器端接收数据的大小1024字节 print("收到反馈:",data) #打印接收结果 #第五步:客户端关闭 client.close()
服务器端的基本步骤:
# Author: zjt ''' 1.声明协议类型 ''' import socket #第一步:声明协议类型 server = socket.socket() # 第二步:服务器绑定端口 server.bind(("localhost",6969)) #bind()也只接收一个参数,故传入参数要以元组的形式写入 #第三步: 监听端口 server.listen() print("等电话....") # 第四步:等待电话打进来 conn,addr = server.accept() # print(conn,addr) print("电话来了") # 第五步:从客户端接收请求 data = conn.recv(1024) # 固定一次只接收1024个字节,data存放接收的数据 print("server receive:",data) #打印接收到的数据 # 第六步:对收到的数据处理操作,并发送回客户端 conn.send(data.upper()) # 第七步:服务器关闭 server.close()
执行顺序:先启动服务端代码,然后启动客户端。
上述方法实现的功能是:客户端将一串字符串传给服务器端,服务器端将收到的字符串小写变成大写,并返回给客户端。此代码只是为了演示,还有需要细节地方需要完善。
3.升级一下功能:客户端与服务器端能传递中文
客户端代码:
import socket client = socket.socket() client.connect(("localhost",6969)) client.send("我是一段中文".encode("utf-8")) #发送中文时先编码 data = client.recv(1024) print("收到来自服务器端的数据:",data.decode()) #从服务器端返回的数据也是btype类型,所以此处需要解码成中文 client.close()
服务器端代码:
import socket server = socket.socket() server.bind(("localhost",6969)) server.listen() print("等待ing....") conn ,addr = server.accept() data = conn.recv(1024) print("收到数据",data) print(data.decode()) #从客户端器端返回的数据是btype类型,所以此处需要decode解码成中文 conn.send(data) print("返回给客户端") server.close()
此时只需要主要发送和接收的时候,注意到底是解码和编码就行。
4.升级:服务器端与多个客户端能通信
之前的连接程序,缺陷很多。每次客户端断开,服务器也跟着断开,这也导致此时不能接收其他客户端的链接,所以对服务器端的代码进行了修改,客户端代码不用变化。import socket,os #服务器端 server = socket.socket() server.bind(('localhost',1008)) server.listen(5) print("等待ing....") while True: #此处循环保证客户端断开的情况下,服务器还处于工作状态 conn, addr = server.accept() print(conn, addr) print("电话来了....") while True: data = conn.recv(1024) print("revc:",data) if not data: print("client is disconnection....") break conn.send(data.upper()) server.close()
客户端代码如下:
#-*-coding:utf-8-*- import socket client = socket.socket() client.connect(('localhost',1008)) while True: msg = raw_input(">>>>:").strip() #if len(msg) == 0:continue # 防止用户输入空让客户>端卡死 client.send(msg.encode("utf-8")) data = client.recv(1024) print("recv:",data.decode()) client.close()
为了实现效果,下面开启了两个客户端窗口,当其中一个客户端断开时,另一个客户端还能跟服务器保持链接。
需要注意的是,这此代码的运行是在linux环境下运行,windows上无法出现一样的效果。
5.继续升级:实现linux目录下文件的传输(SSH)
这次实现的目标是:客户端不管发送什么命令,服务器端读取指定文件,并将其发送给客户端。客户端收到文件后,读取并另存为新文件
对服务器端的代码进行修改:
# 模拟SSH客户端:执行命令并返回结果 import socket,os server = socket.socket() server.bind(('localhost',1008)) server.listen(5) print("等待ing....") while True: conn, addr = server.accept() print(conn, addr) print("电话来了....") while True: data = conn.recv(1024) print("revc:",data) if not data: print("client is disconnection....") break #res = os.popen(data).read() #获取执行结果 #conn.send(res.upper()) f = open("oldboy.avi") #此处暂时打开指定文件。 data = f.read() print(len(data)) #打印读取文件的大小 conn.sendall(data) #sendall() 实现的是send()循环的功能,将目标文件完整的发送出去 f.close()
客户端代码也需要修改:
客户端和服务器端都开启成功后,客户端每次输入一条指令,客户端就接收一部分内容。这其中的原因是客户端接收是有上限的,每次只能接收一部分内容。实验效果如下:#-*-coding:utf-8-*- import socket client = socket.socket() client.connect(('localhost',1008)) f = open("videos",'wb') #此处将打开文件的语句,放在循环外面,避免反复重新打开 while True: msg = raw_input(">>>>:").strip() if len(msg) == 0:continue # 防止用户输入空字>符 client.send(msg.encode("utf-8")) data = client.recv(10240000) #print(data) f.write(data) f.flush() #此处加入了刷新功能的语句 client.close()
6.继续升级:实现SSH传输指令,并返回结果
为了实现目标,我们先完成基本目标:客户端能发送指令,服务器端收到指令后能返回结果
客户端代码:
此处需要注意的是,py3只能发送和接收btype类型,所以指令代码发出去前需要编码成btype类型,接收数据后需要解码成uncode.# Author: zjt import socket client = socket.socket() client.connect(("localhost",4545)) while True: cmd = input(">>>:").strip() if len(cmd)==0 :continue client.send(cmd.encode("utf-8")) cmd_res = client.recv(1024) print(cmd_res.decode()) client.close()
客户端代码:
# Author: zjt import socket,os server =socket.socket() server.bind(('localhost',4545)) server.listen() print("等待....") while True: conn,addr = server.accept() print("new conn:",conn) while True: data = conn.recv(1024) if not data: print("client is disconnection") break cmd_res = os.popen(data.decode()).read() print("before send: ",len(cmd_res)) if len(cmd_res)==0: cmd_res = "cmd has no output...." conn.send(cmd_res.encode("utf-8")) print("发过去了") server.close()
运行后结果如下
但是依然存在问题:当输入的指令需要返回比较大的信息时,客户端每次只能接收部分,输入下条指令时,客户端还在传输之前的信息,直到全部接收完毕,才会接收下一次的返回信息。
解决办法:服务器端发送资料前,先计算内容长度,并结果发送给客户端。而客户端创建一个循环,接收文件大小后,每次比较当前接收了多少内容,直到实际接收内容等于预期接收的长度,则跳出本次循环执行下一条命令。
按照解决办法,先对服务器代码修改,使其能发送文件大小:
# Author: zjt import socket,os server =socket.socket() server.bind(('localhost',4545)) server.listen() print("等待....") while True: conn,addr = server.accept() print("new conn:",conn) while True: data = conn.recv(1024) if not data: print("client is disconnection") break cmd_res = os.popen(data.decode()).read() print("before send: ",len(cmd_res)) if len(cmd_res)==0: cmd_res = "cmd has no output...." conn.send(str(len(cmd_res)).encode("utf-8")) # conn.send(cmd_res.encode("utf-8")) print("发过去了") server.close()
此处发送内容的长度类型为int,conn.send()发送的是btype类型。所以先将int转成字符串,然后encode发送出去,这一点技巧需要掌握。
代码效果如下:
进一步改进,客户端每次每次接收文件长度,都做记录。如果小于文件长度则继续传输,否则停止循环。
客户端代码:
# Author: zjt import socket,os server =socket.socket() server.bind(('localhost',4545)) server.listen() print("等待....") while True: conn,addr = server.accept() print("new conn:",conn) while True: data = conn.recv(1024) if not data: print("client is disconnection") break cmd_res = os.popen(data.decode()).read() print("before send: ",len(cmd_res.encode("utf-8"))) if len(cmd_res)==0: cmd_res = "cmd has no output...." conn.send(str(len(cmd_res.encode("utf-8"))).encode("utf-8")) #发送过去的文件长度,必须是编码后的结果才行。 conn.send(cmd_res.encode("utf-8")) print("发过去了") server.close()
客户端代码:
实现效果:# Author: zjt import socket client = socket.socket() client.connect(("localhost",4545)) while True: cmd = input(">>>:").strip() if len(cmd)==0 :continue client.send(cmd.encode("utf-8")) file_size = int(client.recv(1024)) print("文件大小:",file_size) recv_size = 0 while file_size > recv_size: cmd_res = client.recv(1024) if len(cmd_res)==0: continue # print(cmd_res.decode()) recv_size += len(cmd_res) print(recv_size) else: print("cmd_res has received",recv_size) client.close()