Python SSL Socket:从服务器和客户端接收和发送

3

好的,我正在尝试通过SSL套接字连接使服务器和客户端之间实现双向通信。我觉得最好的方法是在每个系统中实现两个线程,分别作为服务端和客户端。但是,当我实现这段代码时(显然在另一个服务器/客户端中使用相反对应的端口):

#secserv.py

import socket
from OpenSSL import SSL
import threading
import time

class SecureIn(threading.Thread):
  context = SSL.Context(SSL.SSLv23_METHOD)
  context.use_privatekey_file('key')
  context.use_certificate_file('cert')

  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s = SSL.Connection(context, s)
  s.bind(('', 5570))
  def run(self):
    while True:
      self.s.listen(5)
      (connection, address) = self.s.accept()
      print repr(connection.recv(5570))


class SecureOut(threading.Thread):
  time.sleep(6)
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect(('localhost', 12345))
  sslSocket = socket.ssl(s)
  print repr(sslSocket.server())
  print repr(sslSocket.issuer())
  def run(self):
    sslSocket.write('Hello secure socket\n')

  s.close()


si = SecureIn()
si.start()
time.sleep(6)
so = SecureOut()
so.start()

I get this error:

Traceback (most recent call last):
  File "secserv.py", line 25, in <module>
    class SecureOut(threading.Thread):
  File "secserv.py", line 28, in SecureOut
    s.connect(('localhost', 12345))
  File "/usr/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 111] Connection refused

另外,我尝试过让独立的服务器向所有客户端发送广播消息。我搜索了很多资料,但似乎只有在使用常规套接字时才能找到可行的方法来实现SSL套接字广播。当我尝试使用s.write()s.sendall()时,会出现以下错误:

Traceback (most recent call last):
  File "secserv.py", line 19, in <module>
    s.write('hello client\n')
OpenSSL.SSL.Error: [('SSL routines', 'SSL_write', 'uninitialized')]

从这段代码开始:
import socket
from OpenSSL import SSL
context = SSL.Context(SSL.SSLv23_METHOD)
context.use_privatekey_file('key')
context.use_certificate_file('cert') 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s = SSL.Connection(context, s)
s.bind(('', 12345))
while True:
    s.listen(5) 
    (connection, address) = s.accept()
    print repr(connection.recv(12345))
    #This could also be s.sendall()
    s.write('hello client\n')

请帮助我 StackOverFlow,你是我的唯一希望。我知道这应该很简单,但我的大脑已经紧张到无法思考了。
同时,我对Python还比较新手,因此有可能是它的操作方式或类的加载等方面有些地方我还不懂。
编辑:好吧,我知道这个代码很糟糕。它不是一个要上市销售的产品,它永远不会在生产环境中运行,这只是我尝试让一个概念运作起来,那就是:通过Python SSL连接使服务器和客户端相互发送消息。
我知道这是可怕的代码,但我只是想知道如何让服务器发送一条回复消息,因为每当我尝试时,都会得到最后的错误提示。

1
为什么需要两个套接字?套接字是双向的;您不需要从服务器到客户端建立单独的连接来发送数据。(而且您可能不想这样做,因为互联网上的许多客户端都在NAT后面,无法直接通过此方式访问。)您的设计中是否有必要但未解释的内容? - abarnert
最初我想要与一些客户端(从服务器)进行独占式的交流,但现在我意识到我不需要这样做。然后我尝试让服务器发送数据回来,但就像它所说的那样,我找不到使用套接字进行SSL通信的方法,它一直抛出错误。 - user1585054
同时,你得到的错误提示是告诉你在12345端口上没有任何人在监听。你确定客户端实际上正在尝试在这个时候进行监听吗?看看你的代码,如果双方都是“先按一种方式做所有事情,然后休眠6秒,再按另一种方式做所有事情”,那么服务器在客户端开始监听之前尝试连接是完全有可能的... - abarnert
我曾经认为,在它自己的线程中的 while True: 会在另一个线程运行时继续运行。而且,与此同时运行的另一个版本将是相反的,也就是说,在端口12345上设置服务器并等待连接,然后尝试在5570上发送。 - user1585054
1个回答

4
你的设计在很多层面上看起来都有误。首先,你不需要两个套接字来实现双向数据传输;套接字是双向的。其次,你可能不想从服务器回到客户端建立连接——这在本地测试中可能有效,但一旦部署到互联网上,大多数客户端将位于NAT路由器后面,没有公共地址可以连接。
同时,你的逻辑非常奇怪。你先在一个方向上建立连接,然后等待6秒钟,再在另一个方向上建立连接。你为什么要这样做呢?
如果服务器在客户端调用它的SecureIn之前或者同时调用SecureOut,它会尝试在没有任何人监听的情况下进行连接,这将导致你看到的错误。
此外,你对每个线程有一个非常奇怪的运行循环。
def run(self):
    while True:
        self.s.listen(5)
        (connection, address) = self.s.accept()
        print repr(connection.recv(5570))

这个程序同时只接受一个客户端连接,从中读取一次数据,然后就泄漏了连接,再去获取下一个客户端。我不确定在一个已经在监听的套接字上再次调用listen(5)是否有效,或者它会做什么。当然,它永远也无法完成任务。(实际上不完全正确——例如,如果客户端连接,然后在您调用recv之前离开,您可能会收到异常,您没有捕获,因此您的程序将退出……)这就是你想要的吗?
还有,为什么使用recv(5570)?那个数字是要读取的最大字节数,不是端口号。
另外,为什么要打印字符串的repr
同时,您是否意识到无论传递什么缓冲区长度,recv都不能保证获得整个消息(或获取只包含一条消息而不是两条消息,如果您有一个客户端发送了多个消息)。它几乎肯定可以在本地主机上正常工作,并且对于如此小的消息,在互联网上它很可能大部分时间都可以正常工作,但并非始终如此。
此外,您似乎混淆了类变量和实例变量之间的区别,因为您在SecureInSecureOut中设置了一堆类变量。这意味着如果您可以同时拥有两个类的实例,则它们将共享相同的SSL.Context等。即使在现实生活中您不需要同时拥有两个实例,但是对于测试,您几乎肯定要创建一个新实例,并且希望它创建一个新套接字等,而不是继续使用旧的实例。所以,代替这样写:
class SecureIn(threading.Thread):
  context = SSL.Context(SSL.SSLv23_METHOD)
  context.use_privatekey_file('key')
  context.use_certificate_file('cert')
  …

请执行以下操作:

class SecureIn(threading.Thread):
  def __init__(self):
    self.context = SSL.Context(SSL.SSLv23_METHOD)
    self.context.use_privatekey_file('key')
    self.context.use_certificate_file('cert')
    …

(当然,你也可以将它放在run方法中。)
基本上,这不太可能有任何有用的作用,无论是否使用SSL。我建议您退后一步,查看一些教程,并编写适用于普通TCP套接字的工作内容,然后再考虑如何对其进行SSL处理。

A) 这只是为了测试目的。就像我说的,我对此很新,并且它永远不会在本地主机以外的任何地方运行。B) 为了测试目的,我希望服务器继续无限接受。C) 完成了所有教程,完成了所有发送和接收,可以使所有这些正常工作,但我找不到的唯一需要的东西是服务器通过SSL套接字发送的方法。 - user1585054
好的,也许如果我只问一个简单的问题,因为这并不是要部署的,这只是我试图让一个新概念工作,它不是专业的,它永远不会被部署。所以直接的问题是,如何在Python中让服务器发送SSL套接字,而不会收到最后的错误消息(这来自不同的代码块)? - user1585054
那个错误信息不是来自于 send,而是来自于 connect。你可以看出来,因为它是一个“连接被拒绝”的错误,并且回溯显示了这一行s.connect(('localhost', 12345))。所以你的问题不是如何通过套接字发送数据,而是如何首先连接套接字。而且它不起作用的原因几乎肯定是因为没有人在端口12345上监听,可能是因为客户端还没有在该套接字上执行其bindlisten操作。由于你只展示了代码的一半,而没有展示有问题的那一半,所以我只能猜测原因,所以我猜测了。 - abarnert
@user1585054:同时,你知道如何使用http://en.wikipedia.org/wiki/Netcathttp://en.wikipedia.org/wiki/Nmap或其他测试套接字代码的工具吗?你可以验证是否有人在localhost:12345上监听,并甚至可以使用netcatopenssl一起连接到端口12345上的任何内容,并发送数据以确保其正常工作。 - abarnert
我提到的是第二个错误,即无法通过SSL套接字从服务器发送。最后一个错误来自我已更新问题的代码。 - user1585054

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接