首先谈一下什么是socket。socket的本质是API接口,是对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;如果说HTTP是轿车,提供了封装或者显示数据的具体形式,那么Socket是发动机,提供了网络通信的能力。
而用socket实现简单的ssh,需要分析在客户端和服务器端的执行过程。
客户端:
第一步,指定协议类型,地址簇;
第二步:链接服务端的地址;
第三步:发送数据;
第四步:接收数据;
第五步:关闭客户端。
服务器端:
可将服务器端比作一个电话打进来的过程。
第一步:指定协议类型,地址簇;
第二步:绑定要监听的端口;
第三步:进行监听,即对绑定的端口进行监听,看是否有“电话”打进来;
第四步:等待电话打进来;
第五步:接受数据;
第六步:返回给客户端数据;
第七步:关闭服务器。
根据上述的过程,我们可写出下面的程序。
客户端:
import socket
client = socket.socket()#第一步,指定协议类型,地址簇;
'''
在socket.socket内部有两个参数:socket.socket(family address,socket protocal type),
family address,即地址簇,包括:
AF.INTE : ipv4
AF.INTE6 : ipv6
AF.UNIX: local
socket protocal type,即协议类型,包括:
socket.SOCK_STREAM : tcp/ip
socket.SOCK_DGRAM : udp
不写默认地址簇ipv4,协议类型为tcp/ip
'''
client.connect(“localhost",9999)#第二步:连接端口,这表示链接本地的9999端口
client.send("hello python!".encode("utf-8"))#第三步:发送数据:hello python!
#send发送数据时,数据必须是byte类型
client.recv(1024)#第四步:接收数据:1024个字节
client.close()#第五步:关闭客户端
服务器端:
import socket
server = socket.socket()#第一步,定义地址簇,协议类型。用法同上
server.bind("localhost",9999)#第二步:绑定要监听的端口
server.listen()#第三步:监听
conn,addr = server.accept()#第四步:等待电话打进来
#conn是客户端连过来在服务器端为其生成的一个链接实例
#addr是客户端地址
data = conn.recv(1024)#第五步:接受数据:1024个字节
conn.send(data.upper())#第六步:发送数据,即接收字符串的大写
server.close()#第七步:关闭服务器端
上述代码只是简单的实现了客户端与服务器端的交互,其中还有一些欠缺的地方,如服务器只能与一个客户端进行交互,且只能与该客户端进行一次“通话”。下面的代码展示了客户端如何同服务器端进行多次交互。
服务器端:
import socket,os
server = socket.socket()
server.bind("localhost",9999)
server.listen()
while True:
#外层循坏,监听服务端“电话”
conn,addr = server.accept()
while True:
#内层循坏,实现服务器与客户端的多次通话
data = conn.recv(1024)
if not data:#客户端发送空数据则跳出外层
print("client has lost...")
break
print("执行指令:",data)
cmd_res = os.popen(data.decode("utf-8")).read()#执行在cmd上运行的结果
#popen只支持字符串,且执行结果也是字符串
if len(cmd_res) == 0:
print("cmd has no output...")
conn.send(str(len(cmd_res.encode("utf-8"))).encode("utf-8")#发送命令长度
#使用len时应将字符串encode,因为中文字符在字符串中与在二进制中的len不同
#在二进制中,中文的len是三个字节,而字符串中是一个字节
conn.send(cmd_res.encode("utf-8")
server.close()
客户端:
import socket
client = socket.socket()
client.connect("localhost",9999)
while True:
#实现发送多条命令
cmd = input(">>:").strip()
if len(cmd) == 0:continue#不能输入为空
client.send(cmd.encode("utf-8")
cmd_res_size = int(client.recv(1024))#接收命令长度
cmd_res = ""
while len(cmd_res.encode("utf-8"))<cmd_res.size:
#send发送缓冲区的数据,而当数据过多时,客户端也只能接收1024个字节的数据。下一次客户端接收的仍是留于缓冲区内未发送的内容
#避免出现接收内容不符情况,使用多次接收来接收客户端遗留在缓冲区的内容
temp = client.recv(1024)
cmd_res += temp.encode("utf-8")
print(cmd_res)
client.close()
注意事项:
1、在Linux上才能实现服务器与多个客户端通话,但通话的基础是在前一客户端断开的情况下才能进行。
这是因为在Linux系统中,当客户端主动断开时,客户端将会接收到空消息,而在windows系统下,客户端断开服务器端将会直接报错。
2、注意当使用send发送文件时,文件必须是byte类型,发送时需encode。接收时,文件也是byte类型的,要想查看字符型消息,需要decode。
3、在Linux系统上,连续send两条内容很有可能发生粘包问题,即把两条send内容合并成一个发送,windows上也很有可能会出现粘包问题。解决的方法是在两个send之间可以接收
一条来自客户端的数据,即recv一个数据,即可解决粘包问题。