创建多线程TCP服务器 Python 3

3 投票
3 回答
4946 浏览
提问于 2025-04-18 14:08

你好,我试着做一个简单的服务器,可以同时接收多个客户端的连接。我刚学Python,对这个有点难以理解……我尝试把我的代码改成多线程的应用,但没有成功……这是我的代码:

import socket, threading
def message():
    while 1:
        data = connection.recv(1024)
        if not data: break
        #connection.sendall(b'-- Message Received --\n')
        print(data.decode('utf-8'))
    connection.close()

def connection():
    address = input("Insert server ip")
    port = 44444
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((address, port))
    s.listen(1) 
    print("Server started! Waiting for connections...")

def accept connection():
    connection, address = s.accept()
    print('Client connected with address:', address)
    t=thread.Threading(target=message,args=(connection))
    t.run()

我知道我的代码有很多错误,但我刚学Python,抱歉 :(

原来的没有线程的代码是:

import socket

address = input("Insert server ip:")
port = 44444

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((address, port))
s.listen(1)
print("Server started! Waiting for connections...")

connection, address = s.accept()
print('Client connected with address:', address)

while 1:
    data = connection.recv(1024)
    if not data: break

    #connection.sendall(b'-- Message Received --\n')
    print(data.decode('utf-8'))

connection.close()

3 个回答

0

这里有一些示例代码,展示了一个使用线程的套接字连接。

def sock_connection( sock, host ):
  "Handle socket"
  pass

while 1:
  try:
    newsock = sock.accept()
    thread = Thread( target=sock_connection, args=newsock )
    thread.start()
  except Exception, e:
    print "error on socket connection: " % e)
2

这是一篇比较老的帖子,不过里面有个很好的方法可以实现你说的功能。这里有个我之前发的例子链接:

https://bitbucket.org/matthewwachter/tcp_threadedserver/src/master/

还有这个脚本:

from datetime import datetime
from json import loads, dumps
from pprint import pprint
import socket
from threading import Thread

class ThreadedServer(Thread):
    def __init__(self, host, port, timeout=60, debug=False):   
        self.host = host
        self.port = port
        self.timeout = timeout
        self.debug = debug
        Thread.__init__(self)

    # run by the Thread object
    def run(self):
        if self.debug:
            print(datetime.now())
            print('SERVER Starting...', '\n')

        self.listen()

    def listen(self):
        # create an instance of socket
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        # bind the socket to its host and port
        self.sock.bind((self.host, self.port))
        if self.debug:
            print(datetime.now())
            print('SERVER Socket Bound', self.host, self.port, '\n')

        # start listening for a client
        self.sock.listen(5)
        if self.debug:
            print(datetime.now())
            print('SERVER Listening...', '\n')
        while True:
            # get the client object and address
            client, address = self.sock.accept()

            # set a timeout
            client.settimeout(self.timeout)

            if self.debug:
                print(datetime.now())
                print('CLIENT Connected:', client, '\n')

            # start a thread to listen to the client
            Thread(target = self.listenToClient,args = (client,address)).start()

            # send the client a connection message
            # res = {
            #     'cmd': 'connected',
            # }
            # response = dumps(res)
            # client.send(response.encode('utf-8'))

    def listenToClient(self, client, address):
        # set a buffer size ( could be 2048 or 4096 / power of 2 )
        size = 1024
        while True:
            try:
                # try to receive data from the client
                data = client.recv(size).decode('utf-8')
                if data:
                    data = loads(data.rstrip('\0'))
                    if self.debug:
                        print(datetime.now())
                        print('CLIENT Data Received', client)
                        print('Data:')
                        pprint(data, width=1)
                        print('\n')

                    #send a response back to the client
                    res = {
                        'cmd': data['cmd'],
                        'data': data['data']
                    }

                    response = dumps(res)
                    client.send(response.encode('utf-8'))
                else:
                    raise error('Client disconnected')



            except:
                if self.debug:
                    print(datetime.now())
                    print('CLIENT Disconnected:', client, '\n')
                client.close()
                return False


if __name__ == "__main__":
    ThreadedServer('127.0.0.1', 8008, timeout=86400, debug=True).start()
2

你的基本设计已经很接近了,但有很多小问题让你难以继续前进。

首先,你的函数名中有空格,这是不允许的。而且你有一个IndentationError(缩进错误),因为你没有正确缩进函数的内容。


接下来,在accept_connection这个函数里,你对threading的使用不对。

thread.Threading是不存在的;你可能是想写threading.Thread

args必须是一个值的序列(比如元组、列表等)。你可能以为(connection)是一个包含一个值的元组,但实际上不是;元组是用逗号来定义的,而不是用括号。你现在的写法只是把connection用多余的括号包起来了。你应该写成(connection,)

另外,直接在线程对象上调用run只会在当前线程中运行线程的代码。你应该调用start,这样才能启动一个新线程,并在那个线程上调用run方法。


同时,你实际上并没有在任何地方调用这个函数,所以它当然无法执行任何操作。想想你想在哪里调用它。在创建监听套接字后,你想循环调用accept,为每个接受的连接启动一个新的客户端线程,对吧?所以,你想在一个循环中调用它,可以在connection内部,或者在最外层(那样的话connection需要return s)。


最后,你的accept_connection函数无法访问其他函数的局部变量;如果你想让它使用一个名为s的套接字,你必须把它作为参数传递。

所以:

def connection():
    address = input("Insert server ip")
    port = 44444
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((address, port))
    s.listen(1) 
    print("Server started! Waiting for connections...")
    while True:
        accept_connection(s)

def accept_connection(s):
    connection, address = s.accept()
    print('Client connected with address:', address)
    t=thread.Threading(target=message, args=(connection,))
    t.start()

顺便提一下,使用sock.recv(1024)时要小心,不要假设你会收到对方用send(msg)发送的完整消息。你可能会收到完整消息,也可能只收到一半,或者收到完整消息加上客户端稍后发送的另一条消息的一半。套接字只是字节流,就像文件一样,并不是独立消息的流;你需要某种协议来分隔消息。

最简单的协议就是每条消息单独发送一行。这样你可以使用socket.makefile()for line in f:,就像处理真实文件一样。当然,如果你的消息中可以包含换行符,这样就不太适用了,但你可以在一边用反斜杠转义它们,在另一边再解开它们。

撰写回答