Python Socket 多个客户端

50
所以我正在开发一个需要使用套接字处理多个客户端在线游戏的iPhone应用程序。我尝试过Twisted,但是花了很多努力,仍然无法一次发送一堆信息,这就是为什么我现在要尝试套接字的原因。
我的问题是,使用下面的代码,你如何能够连接多个客户端?我尝试过使用列表,但是我无法弄清楚格式。如何实现多个客户端同时连接,并且我能够向特定的客户端发送消息?
谢谢!
#!/usr/bin/python           # This is server.py file
 
import socket               # Import socket module
s = socket.socket()         # Create a socket object
host = socket.gethostname() # Get local machine name
port = 50000                # Reserve a port for your service.
 
print('Server started!')
print('Waiting for clients...')
 
s.bind((host, port))        # Bind to the port
s.listen(5)                 # Now wait for client connection.
c, addr = s.accept()     # Establish connection with client.
print('Got connection from', addr)
while True:
   msg = c.recv(1024)
   print(addr, ' >> ', msg)
   msg = raw_input('SERVER >> ')
   c.send(msg)
   #c.close()                # Close the connection

4
当客户端连接时,使用 c, addr = s.accept() 创建一个会话,并将其存储在一个客户端列表中,然后启动一个线程来处理该会话。 - Mayli
2
会像这样吗?while: c, addr = s.accept() client_list = [] client_list.append(c, addr) - Alec
1
我知道这是一个巨大的改变,但请考虑一下Ruby的EventMachine——我发现EventMachine文档比Twisted文档更容易阅读和理解。Ruby的缺点是库绑定和软件包的生态系统较小,但比以前好了许多... - sarnold
@sarnold 我宁愿坚持使用Python,我觉得坚持下去会很好。不过还是谢谢你的建议。 - Alec
2
@AlecK., 是的,我能理解,特别是如果你已经写了其他Python代码。每次我看到Twisted文档时,他们把一些东西搞得多么复杂,这让我感到惊讶... - sarnold
显示剩余7条评论
9个回答

55

根据您的问题:

我的问题是,使用下面的代码,如何能够连接多个客户端?我尝试过使用列表,但我无法弄清楚其格式。如何实现多个客户端同时连接,并且我能够向特定的客户端发送消息?

使用您提供的代码,您可以这样做:


#!/usr/bin/python           # This is server.py file                                                                                                                                                                           

import socket               # Import socket module
import thread

def on_new_client(clientsocket,addr):
    while True:
        msg = clientsocket.recv(1024)
        #do some checks and if msg == someWeirdSignal: break:
        print addr, ' >> ', msg
        msg = raw_input('SERVER >> ')
        #Maybe some code to compute the last digit of PI, play game or anything else can go here and when you are done.
        clientsocket.send(msg)
    clientsocket.close()

s = socket.socket()         # Create a socket object
host = socket.gethostname() # Get local machine name
port = 50000                # Reserve a port for your service.

print 'Server started!'
print 'Waiting for clients...'

s.bind((host, port))        # Bind to the port
s.listen(5)                 # Now wait for client connection.

print 'Got connection from', addr
while True:
   c, addr = s.accept()     # Establish connection with client.
   thread.start_new_thread(on_new_client,(c,addr))
   #Note it's (addr,) not (addr) because second parameter is a tuple
   #Edit: (c,addr)
   #that's how you pass arguments to functions when creating new threads using thread module.
s.close()

正如Eli Bendersky所提到的,你可以使用进程而不是线程,你还可以检查Python的 threading 模块或其他异步套接字框架。 注意:检查由您自己实现,这只是一个基本框架。


1
你能再检查一下吗?我遇到了这个错误:“data = clientsocket.recv(16384) AttributeError: 'tuple' object has no attribute 'recv'”。 - hoangpx
2
函数中 while True: 后的 clientsocket.close() 和底部 while True: 后的 s.close() 永远不会被调用,因为在 while True: 中没有 break,唯一退出循环的方式是通过异常,而该异常不会被捕获并将结束线程。上下文管理器是确保套接字关闭的更好方法。 - nog642
我已经将您的程序适配到Python3,但是出现了以下错误:Traceback (most recent call last): File "mydir\Server.py", line 29, in <module> print(f"Got connection from "{addr}"") NameError: name 'addr' is not defined - NaNdy
5
在Python 3中,import thread 应该被改为 from threading import Thread,然后将 thread.start_new_thread() 替换为 Thread(target=on_new_client, args=(c,addr))。这样做可以提高代码的可读性和可维护性。 - Jonathan

24

accept可以不断提供新的客户端连接。但需要注意的是,它和其他套接字调用通常会阻塞。因此你有几个选择:

  • 开启新线程来处理客户端,而主线程则返回以接受新的客户端
  • 如上所述,但使用多进程代替线程
  • 使用诸如Twisted等异步套接字框架,或其他大量框架

6
从Python 3.5开始,Python现在已经内置了对于异步编程的支持"asyncio",这是指向第三点的趋势。 - ViFI

8

这里提供一个来自SocketServer文档的示例,它是一个非常不错的起点。

import SocketServer

class MyTCPHandler(SocketServer.BaseRequestHandler):
    """
    The RequestHandler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print "{} wrote:".format(self.client_address[0])
        print self.data
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)

    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()

请在终端中按以下方式尝试:

$ telnet localhost 9999
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello
HELLOConnection closed by foreign host.
$ telnet localhost 9999
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Sausage
SAUSAGEConnection closed by foreign host.

你可能需要使用一个 Forking 或 Threading Mixin,这里有详细的说明。

5
能否使用这个方法来保持多个连接处于活动状态? - cascading-style
1
@cascading-style 我不这么认为,我尝试过这样做是因为我有多个客户端连接,但它会随机超时。 - Vladimir Stazhilov
1
有一个简单的方法,只需实例化一个 socketserver.ThreadingTCPServer 而不是 socketserver.TCPServer(至少在像3.8.8这样的现代Python解释器中)。 - Patrizio Bertoni

2

这个程序将打开26个套接字,您可以连接很多TCP客户端到它上面。

#!usr/bin/python
from thread import *
import socket
import sys

def clientthread(conn):
    buffer=""
    while True:
        data = conn.recv(8192)
        buffer+=data
        print buffer
    #conn.sendall(reply)
    conn.close()

def main():
    try:
        host = '192.168.1.3'
        port = 6666
        tot_socket = 26
        list_sock = []
        for i in range(tot_socket):
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
            s.bind((host, port+i))
            s.listen(10)
            list_sock.append(s)
            print "[*] Server listening on %s %d" %(host, (port+i))

        while 1:
            for j in range(len(list_sock)):
                conn, addr = list_sock[j].accept()
                print '[*] Connected with ' + addr[0] + ':' + str(addr[1])
                start_new_thread(clientthread ,(conn,))
        s.close()

    except KeyboardInterrupt as msg:
        sys.exit(0)


if __name__ == "__main__":
    main()

1

这是我在 Python 3 中完成的工作。

import socket

from threading import Thread


def on_new_client(client_socket, addr):
    while True:
        data = client_socket.recv(1024).decode('utf-8')
        if not data:
            break
        print(f"{addr} >> {data}")
    client_socket.close()


def main():
    host = '0.0.0.0'  # allow any incoming connections
    port = 8888

    s = socket.socket()
    s.bind((host, port))  # bind the socket to the port and ip address

    s.listen(5)  # wait for new connections

    while True:
        c, addr = s.accept()  # Establish connection with client.
        # this returns a new socket object and the IP address of the client
        print(f"New connection from: {addr}")
        thread = Thread(target=on_new_client, args=(c, addr))  # create the thread
        thread.start()  # start the thread
    c.close()
    thread.join()


if __name__ == '__main__':
    main()

0
def get_clients():
    first_run = True
    startMainMenu = False

    while True:
        if first_run:
            global done
            done = False
            Thread(target=animate, args=("Waiting For Connection",)).start()

        Client, address = objSocket.accept()
        global menuIsOn
        if menuIsOn:
            menuIsOn = False  # will stop main menu
            startMainMenu = True

        done = True

        # Get Current Directory in Client Machine
        current_client_directory = Client.recv(1024).decode("utf-8", errors="ignore")

        # beep on connection
        beep()
        print(f"{bcolors.OKBLUE}\n***** Incoming Connection *****{bcolors.OKGREEN}")
        print('* Connected to: ' + address[0] + ':' + str(address[1]))
        try:
            get_client_info(Client, first_run)
        except Exception as e:
            print("Error data received is not a json!")
            print(e)
        now = datetime.now()
        current_time = now.strftime("%D %H:%M:%S")
        print("* Current Time =", current_time)

        print("* Current Folder in Client: " + current_client_directory + bcolors.WARNING)

        connections.append(Client)

        addresses.append(address)

        if first_run:
            Thread(target=threaded_main_menu, daemon=True).start()

            first_run = False
        else:
            print(f"{bcolors.OKBLUE}* Hit Enter To Continue.{bcolors.WARNING}\n#>", end="")
        if startMainMenu == True:
            Thread(target=threaded_main_menu, daemon=True).start()
            startMainMenu = False

1
虽然这可能回答了问题,但请考虑添加解释。 - Abdul Aziz Barkat

0

我尝试了很多方法来处理TCPServer和多个连接。

最稳定和简单的方法是使用ThreadingTCPServer

感谢PatrizioBertoni的评论!

socks = socketserver.ThreadingTCPServer(("0.0.0.0", port), SockHandler)

0
这是一个简单的服务器,它使用threading.Thread来同时处理多个客户端。
import socket, threading

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind(('', 555))
serversocket.listen(5)

def client_listener(connection, address):
    print(f"New connection {connection=} {address=}")
    while True:
        buf = connection.recv(64)
        if not buf:
            print(f"Closing connection {connection=} {address=}")
            break
        print(buf)


while True:
    connection, address = serversocket.accept()
    threading.Thread(target=client_listener, args=(connection, address)).start()

和一个客户:

import socket, time

clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientsocket.connect(('localhost', 555))
clientsocket.send(b'hello1')
time.sleep(5)
clientsocket.send(b'hello2')

-2
#!/usr/bin/python
import sys
import os
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)         
port = 50000

try:
    s.bind((socket.gethostname() , port))
except socket.error as msg:
    print(str(msg))
s.listen(10)
conn, addr = s.accept()  
print 'Got connection from'+addr[0]+':'+str(addr[1]))
while 1:
        msg = s.recv(1024)
        print +addr[0]+, ' >> ', msg
        msg = raw_input('SERVER >>'),host
        s.send(msg)
s.close()

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