首先谈一下什么是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一个数据,即可解决粘包问题。