如何使用Python流式套接字作为代理?

1 投票
3 回答
3502 浏览
提问于 2025-04-17 04:53

我正在尝试写一个Python程序,这个程序会监听一个端口,当有客户端连接上来时,它会启动一个线程,做以下几件事:

  1. 连接到一个远程服务(http://193.108.24.18:8000/magicFM)

  2. 把收到的数据传递给连接的客户端(这个客户端是Windows Media Player)

事情是这样的,我想在工作时听广播,但因为我在另一个国家(这个广播只能在国内听),而且我不能更改我电脑上的代理设置……不过我有一个服务器,想用它作为代理。

提前谢谢你们。

这是我到目前为止做的:

#!/usr/bin/env python
import socket, urllib2

TCP_IP = '0.0.0.0'
TCP_PORT = 5566
BUFFER_SIZE = 16 * 1024  #16 kb/s
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connection address:', addr
req = urllib2.urlopen('http://193.108.24.18:8000/magicFM')

while 1:
    chunk = req.read(BUFFER_SIZE)
    if not chunk: break
    conn.send(chunk)


conn.close()

但是它失败了……错误信息是:

Traceback (most recent call last):
  File "./magicfmproxy.py", line 17, in ?
    conn.send(chunk)
socket.error: (32, 'Broken pipe')

3 个回答

0

为了补充glglgl的回答,你的问题在于违反了协议。

HTTP协议规定了以下内容:

  • 用户请求 GET /magicFM ...
  • 服务器用元数据响应 200 OK ...
  • 服务器继续发送实际数据

想了解更多细节,可以查看这个链接:http://en.wikipedia.org/wiki/HTTP

urllib2.urlopen把这些复杂的事情都隐藏起来,让它看起来就像在读文件一样简单,但你的客户端期望代理能像正常的HTTP服务器那样工作。在这里,urlopen对你来说是个不合适的选择。最好的方法是直接打开一个与服务器的连接,并开始两个并行的循环:

  • 从客户端读取数据,写入服务器
  • 从服务器读取数据,写入客户端

(或者可以在一个循环中使用非阻塞读取;或者使用异步编程)

可能会有一个复杂的情况:HTTP协议规定了“Host”头,客户端会在请求中发送这个头,值是代理的地址。根据你的广播服务器的行为,你可能需要在客户端请求中将“Host: ...”改成正确的地址(不过在现代互联网中,这通常不太重要)。

还有一个有趣的副作用是:代理不会包含任何关于要打开的特定网址的信息,因为你的媒体客户端会为你提供这些网址。

1

我只能猜测,但可能你的问题出在客户端。

我不知道你的客户端试图建立哪些连接,但可能在期望和实际传输之间存在冲突:

  • 你的客户端发起了一个连接,可能会发送一些请求数据。
  • 如果这些请求数据和你用 urllib2.urlopen() 发送到流的数据不匹配,或者从那里返回的答案不匹配,客户端就会取消连接,这样你就会得到一个坏掉的套接字。

我看到两种解决方案:

第一种

  • 尝试将响应行(比如 HTTP/x.x 200 OK 之类的)和头信息也发送回你的客户端 - 这些信息应该在 req.headers 里。

第二种

  • 完全不使用 urllib2.urlopen(),而是直接打开一个普通的套接字连接。但这样的话,你可能需要修改请求的头信息 - Host: 头信息可能需要替换。
2

首先,要通过TCP连接到远程网站,可以使用下面的代码

import socket, struct

def connectToHost(host, port=80, timeout=0):
    try:
            sock=socket.socket()
            timeval=struct.pack("2I", timeout, 0)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeval)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, timeval)
            sock.connect((host, port))
            return sock
    except:
            return None

现在你已经有一个打开的套接字,连接到了远程服务器。接下来,你需要创建一个监听套接字,并在这个套接字上等待连接。一旦有连接到来,就可以使用select来处理多个数据流。

我现在没有时间,这段代码只是一个大概的样子。你需要添加适当的错误处理,也许还需要一些友好的错误提示信息,但如果没有人能提供完整的解决方案,我可能会花时间来完善这段代码。

撰写回答