我想使用 python 套接字创建一个服务器,多个客户端可以连接到并回显文本输入。有没有我可以用几行代码设置的基本服务器?我已经准备好客户端连接我只需要知道这些客户端连接的基本 python 套接字服务器是什么样的。
2 回答
如果您想要两个以上的用户,我建议您检查是否最好安装 xmmp/jabber 服务器。
Python 套接字
Python 的套接字文档也有一些简单的例子,展示了简单的聊天功能。请参阅:http ://docs.python.org/2/library/socket.html#example 。
这是一个应该可以解决问题的小片段。它不会产生很好的输出,但它应该可以工作。它使用两个线程来避免启动两个不同的脚本。
# Echo server program
import socket
import thread
TARGET = None
DEFAULT_PORT = 45000
def reciever():
""" Recive messages... """
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', DEFAULT_PORT)) # Listens on all interfaces...
s.listen(True) # Listen on the newly created socket...
conn, addr = s.accept()
while True:
data = conn.recv(1024)
print "\nMessage> %s\n" % data
def sender():
""" The 'client' which sends the messages """
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TARGET, DEFAULT_PORT)) # Connect...
while True:
msg = raw_input("\nMe> ")
s.sendall(msg)
s.close()
while not TARGET:
TARGET = raw_input("Please specify the other client to connect to: ")
thread.start_new_thread(reciever, ())
thread.start_new_thread(sender, ())
while True:
pass
XMLRPC
如果您需要两个以上的用户,您还可以查看 Python 的 XMLRPC 功能。例如...
服务器:
这个迷你服务器允许用户为用户发送消息。然后服务器将它们保存到一个小的 json 文件中。然后,客户端可以为给定用户请求新消息。
import json
import os.path
from SimpleXMLRPCServer import SimpleXMLRPCServer
"""
Saves messages as structure:
{
'client1':['message1', 'message2'],
'client2':['message1', 'message2']
}
"""
STORAGE_FILE = 'messages.json'
def save(messages):
msg_file = open(STORAGE_FILE, 'w+')
json.dump(messages, msg_file)
def get():
if os.path.exists(STORAGE_FILE):
msg_file = open(STORAGE_FILE, 'r')
return json.load(msg_file)
else:
return {}
def deliver(client, message):
""" Deliver the message to the server and persist it in a JSON file..."""
print "Delivering message to %s" % client
messages = get()
if client not in messages:
messages[client] = [message]
else:
messages[client].append(message)
save(messages)
return True
def get_messages(client):
""" Get the undelivered messags for the given client. Remove them from
messages queue. """
messages = get()
if client in messages:
user_messages = messages[client]
messages[client] = []
save(messages)
return user_messages
else:
return []
server = SimpleXMLRPCServer(("localhost", 8000))
print "Listening on port 8000..."
server.register_function(deliver, 'deliver')
server.register_function(get_messages, 'get_messages')
server.serve_forever()
“客户”
为用户发送消息和获取消息的示例用法。
import xmlrpclib
# Connect to the 'Server'...
proxy = xmlrpclib.ServerProxy("http://localhost:8000/")
# Send a message...
proxy.deliver('username', 'The message to send')
# Recieve all messages for user..
for msg in proxy.get_messages('username'):
print "Message> %s" % msg
请注意:这些只是简单的示例。两者都不是真正安全的,因为没有完成发件人/收件人验证。此外,没有运输安全性,因此消息可能会丢失。
对于很多用户来说,最好使用DBMS系统而不是简单的 JSON 文件。
正如尼克 ODell 所说,如果你想要一个简单的 shell 脚本,我也建议你使用 netcat。
不完全符合您的要求,但以下程序和文件提供了您可能想要使用的 GUI 客户端和一些服务器。您将需要 Python 3.x 来运行此处显示的代码:
MultichatClient.py
#! /usr/bin/env python3
from safetkinter import *
from tkinter.constants import *
import socket
import sys
class MultichatClient(Frame):
after_handle = None
def __init__(self, master, remote_host):
super().__init__(master)
self.message_area = ScrolledText(self, width=81, height=21,
wrap=WORD, state=DISABLED)
self.message_area.grid(sticky=NSEW, columnspan=2)
self.send_area = Entry(self)
self.send_area.bind('<Return>', self.keyPressed)
self.send_area.grid(sticky=EW)
b = Button(self, text='Send', command=self.mouseClicked)
b.grid(row=1, column=1)
self.send_area.focus_set()
try:
self.remote = socket.create_connection((remote_host, 8989))
except socket.gaierror:
print('Could not find host {}.'.format(remote_host))
except socket.error:
print('Could not connect to host {}.'.format(remote_host))
else:
self.remote.setblocking(False)
self.after_handle = self.after_idle(self.dataready)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
@classmethod
def main(cls, args):
root = Tk()
root.title('MultichatClient version 1.0')
m = cls(root, args[0])
m.grid(sticky=NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
return 1
def dataready(self):
try:
s = self.remote.recv(1 << 12).decode()
except socket.error:
pass
else:
self.message_area['state'] = NORMAL
self.message_area.insert(END, s)
self.message_area['state'] = DISABLED
self.message_area.see(END)
self.after_handle = self.after(100, self.dataready)
def destroy(self):
if self.after_handle:
self.after_cancel(self.after_handle)
super().destroy()
def mouseClicked(self, e=None):
self.remote.sendall(self.send_area.get().encode() + b'\r\n')
self.send_area.delete(0, END)
keyPressed = mouseClicked
if __name__ == '__main__':
sys.exit(MultichatClient.main(sys.argv[1:]))
Simple_Client.pyw
#! /usr/bin/env python3
"""Provide a GUI for easy interactions with Multichat servers.
This program is an example of a first attempt at implementing a client
for interacting with a Multichat server through purely graphical means."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>'
__date__ = '11 October 2012'
__version__ = 1, 0, 0
################################################################################
from tkinter.messagebox import *
from tkinter.constants import *
from safetkinter import *
import logging
import traceback
import _thread
import socket
import os
import traceback
import sys
import threadbox
################################################################################
class SimpleClient(Frame):
"SimpleClient(master, **kw) -> SimpleClient instance"
@classmethod
def main(cls):
"Create a GUI root and demonstrate the SimpleClient widget."
root = Tk()
root.title('Chat Client')
root.minsize(675, 450)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.bind_all('<Control-Key-a>', cls.handle_control_a)
frame = cls(root)
frame.grid(sticky=NSEW)
root.mainloop()
@staticmethod
def handle_control_a(event):
"Process Ctrl-A commands by widget type."
widget = event.widget
if isinstance(widget, Text):
widget.tag_add(SEL, 1.0, END + '-1c')
return 'break'
if isinstance(widget, Entry):
widget.selection_range(0, END)
return 'break'
def __init__(self, master, **kw):
"Initialize the SimpleClient instance with the widgets it contains."
super().__init__(master, **kw)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# Build Widgets
self.output_area = ScrolledText(self, width=25, height=4, wrap=WORD)
self.input_area = Entry(self)
self.corner = Sizegrip(self)
# Place Widgets
self.output_area.grid(row=0, column=0, columnspan=2, sticky=NSEW)
self.input_area.grid(row=1, column=0, sticky=EW)
self.corner.grid(row=1, column=1, sticky=SE)
# Setup Widgets
self.output_area['state'] = DISABLED
self.input_area.bind('<Return>', self.send)
self.after_idle(self.connect)
def connect(self):
"Try connecting to a server to begin chatting."
self.connection = Connector(self, 'Chat Client').connection
if self.connection is None:
self._root().destroy()
else:
self.connection.setblocking(False)
self.after_idle(self.update)
def send(self, event):
"Send a message across the connection from the given widget."
self.connection.sendall(event.widget.get().encode() + b'\r\n')
event.widget.delete(0, END)
def update(self):
"Update the output area with any incoming messages."
self.output_area['state'] = NORMAL
try:
self.output_area.insert(END, self.connection.recv(1 << 12).decode())
except socket.error:
pass
else:
self.output_area.see(END)
finally:
self.output_area['state'] = DISABLED
self.after(100, self.update)
################################################################################
def start_thread(function, *args, **kwargs):
"Start a new thread of execution while logging any errors."
_thread.start_new_thread(log_errors, (function, args, kwargs))
def log_errors(function, args=(), kwargs={}):
"Execute a function with its arguments and log any exceptions."
try:
function(*args, **kwargs)
except SystemExit:
pass
except:
basename = os.path.basename(sys.argv[0])
filename = os.path.splitext(basename)[0] + '.log'
logging.basicConfig(filename=filename)
logging.error(traceback.format_exc())
################################################################################
class Dialog(Toplevel): # Copies tkinter.simpledialog.Dialog
"Dialog(parent, title=None) -> Dialog instance"
def __init__(self, parent, title=None):
"Initialize a Dialog window that takes focus away from the parent."
super().__init__(parent)
self.withdraw()
if parent.winfo_viewable():
self.transient(parent)
if title:
self.title(title)
self.parent = parent
self.result = None
body = Frame(self)
self.initial_focus = self.body(body)
body.grid(sticky=NSEW, padx=5, pady=5)
self.buttonbox()
if not self.initial_focus:
self.initial_focus = self
self.protocol('WM_DELETE_WINDOW', self.cancel)
if self.parent is not None:
self.geometry('+{}+{}'.format(parent.winfo_rootx() + 50,
parent.winfo_rooty() + 50))
self.deiconify()
self.initial_focus.focus_set()
try:
self.wait_visibility()
except tkinter.TclError:
pass
else:
self.grab_set()
self.wait_window(self)
def destroy(self):
"Destruct the Dialog window."
self.initial_focus = None
super().destroy()
def body(self, master):
"Create the body of this Dialog window."
pass
def buttonbox(self):
"Create the standard buttons and Dialog bindings."
box = Frame(self)
w = Button(box, text='OK', width=10, command=self.ok, default=ACTIVE)
w.grid(row=0, column=0, padx=5, pady=5)
w = Button(box, text='Cancel', width=10, command=self.cancel)
w.grid(row=0, column=1, padx=5, pady=5)
self.bind('<Return>', self.ok)
self.bind('<Escape>', self.cancel)
box.grid()
def ok(self, event=None):
"Validate and apply the changes made by this Dialog."
if not self.validate():
self.initial_focus.focus_set()
return
self.withdraw()
self.update_idletasks()
try:
self.apply()
finally:
self.cancel()
def cancel(self, event=None):
"Close the Dialong window and return to its parent."
if self.parent is not None:
self.parent.focus_set()
self.destroy()
def validate(self):
"Verify that the Dialog is in a valid state."
return True
def apply(self):
"Make any changes the Dialog wishes to accomplish."
pass
################################################################################
class Connector(Dialog):
"Connector(parent, title=None) -> Connector instance"
def body(self, master):
"Customize the Dialog window with some custom widgets."
self.connection = None
self.resizable(False, False)
# Build Widgets
self.prompt = Label(master, text='Enter server IP address:')
self.address = Entry(master)
# Place Widgets
self.prompt.grid(sticky=W, padx=30, pady=2)
self.address.grid(sticky=W, padx=30)
def buttonbox(self):
"Redefine the buttons at the bottom of the window."
w = Button(self, text='Connect', width=10, command=self.ok,
default=ACTIVE)
w.grid(sticky=E, padx=5, pady=5)
self.bind('<Return>', self.ok)
self.bind('<Escape>', self.cancel)
def validate(self):
"Ask a Consumator to make a connection with the given address."
c = Consumator(self, 'Chat Client', (self.address.get(), 8989))
if c.connection is None:
Message(self, icon=WARNING, type=OK, title='Warning',
message='Could not connect to address!').show()
return False
self.connection = c.connection
return True
################################################################################
class Consumator(Dialog):
"Consumator(parent, title, address) -> Consumator instance"
def __init__(self, parent, title, address):
"Initialize the Consumator with the server's address."
self.server_address = address
super().__init__(parent, title)
def body(self, master):
"Create the widgets for this Dialog and start the connection process."
self.connection = None
self.resizable(False, False)
# Build Widgets
self.message = Label(master, text='Trying to connect to address ...')
self.progress = Progressbar(master, orient=HORIZONTAL)
# Place Widgets
self.message.grid(sticky=W, padx=10, pady=2)
self.progress.grid(sticky=EW, padx=10, pady=2)
# Setup Widgets
self.progress.configure(mode='indeterminate', maximum=30)
self.progress.start()
result = []
start_thread(self.connect, result)
self.after_idle(self.poll, result)
def buttonbox(self):
"Cancel the creation of the buttons at the bottom of this Dialog."
pass
@threadbox.MetaBox.thread
def connect(self, result):
"Try connecting to the server address that was given."
try:
result.append(socket.create_connection(self.server_address, 10))
except socket.timeout:
result.append(None)
def poll(self, result):
"Find out if the any connection information is available yet."
if result:
self.connection = result[0]
self.cancel()
else:
self.after(100, self.poll, result)
################################################################################
if __name__ == '__main__':
log_errors(SimpleClient.main)
亲和力.py
"""Allow a simple way to ensure execution is confined to one thread.
This module defines the Affinity data type that runs code on a single thread.
An instance of the class will execute functions only on the thread that made
the object in the first place. The class is useful in a GUI's main loop."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>'
__date__ = '4 June 2012'
__version__ = 1, 0, 0
################################################################################
import sys
import _thread
import queue
################################################################################
def slots(names=''):
"Sets the __slots__ variable in the calling context with private names."
sys._getframe(1).f_locals['__slots__'] = \
tuple('__' + name for name in names.replace(',', ' ').split())
################################################################################
class Affinity:
"Affinity() -> Affinity instance"
slots('thread, action')
def __init__(self):
"Initializes instance with thread identity and job queue."
self.__thread = _thread.get_ident()
self.__action = queue.Queue()
def __call__(self, func, *args, **kwargs):
"Executes function on creating thread and returns result."
if _thread.get_ident() == self.__thread:
while not self.__action.empty():
self.__action.get_nowait()()
return func(*args, **kwargs)
delegate = _Delegate(func, args, kwargs)
self.__action.put_nowait(delegate)
return delegate.value
################################################################################
class _Delegate:
"_Delegate(func, args, kwargs) -> _Delegate instance"
slots('func, args, kwargs, mutex, value, error')
def __init__(self, func, args, kwargs):
"Initializes instance from arguments and prepares to run."
self.__func = func
self.__args = args
self.__kwargs = kwargs
self.__mutex = _thread.allocate_lock()
self.__mutex.acquire()
def __call__(self):
"Executes code with arguments and allows value retrieval."
try:
self.__value = self.__func(*self.__args, **self.__kwargs)
self.__error = False
except:
self.__value = sys.exc_info()[1]
self.__error = True
self.__mutex.release()
@property
def value(self):
"Waits for value availability and raises or returns data."
self.__mutex.acquire()
if self.__error:
raise self.__value
return self.__value
线程盒.py
"""Provide a way to run instance methods on a single thread.
This module allows hierarchical classes to be cloned so that their instances
run on one thread. Method calls are automatically routed through a special
execution engine. This is helpful when building thread-safe GUI code."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>'
__date__ = '9 October 2012'
__version__ = 1, 0, 1
################################################################################
import functools
import affinity
################################################################################
class _object: __slots__ = '_MetaBox__exec', '__dict__'
################################################################################
class MetaBox(type):
"MetaBox(name, bases, classdict, old=None) -> MetaBox instance"
__REGISTRY = {object: _object}
__SENTINEL = object()
@classmethod
def clone(cls, old, update=()):
"Creates a class preferring thread affinity after update."
classdict = dict(old.__dict__)
classdict.update(update)
return cls(old.__name__, old.__bases__, classdict, old)
@classmethod
def thread(cls, func):
"Marks a function to be completely threaded when running."
func.__thread = cls.__SENTINEL
return func
def __new__(cls, name, bases, classdict, old=None):
"Allocates space for a new class after altering its data."
assert '__new__' not in classdict, '__new__ must not be defined!'
assert '__slots__' not in classdict, '__slots__ must not be defined!'
assert '__module__' in classdict, '__module__ must be defined!'
valid = []
for base in bases:
if base in cls.__REGISTRY:
valid.append(cls.__REGISTRY[base])
elif base in cls.__REGISTRY.values():
valid.append(base)
else:
valid.append(cls.clone(base))
for key, value in classdict.items():
if callable(value) and (not hasattr(value, '_MetaBox__thread') or
value.__thread is not cls.__SENTINEL):
classdict[key] = cls.__wrap(value)
classdict.update({'__new__': cls.__new, '__slots__': (), '__module__':
'{}.{}'.format(__name__, classdict['__module__'])})
cls.__REGISTRY[object() if old is None else old] = new = \
super().__new__(cls, name, tuple(valid), classdict)
return new
def __init__(self, name, bases, classdict, old=None):
"Initializes class instance while ignoring the old class."
return super().__init__(name, bases, classdict)
@staticmethod
def __wrap(func):
"Wraps a method so execution runs via an affinity engine."
@functools.wraps(func)
def box(self, *args, **kwargs):
return self.__exec(func, self, *args, **kwargs)
return box
@classmethod
def __new(meta, cls, *args, **kwargs):
"Allocates space for instance and finds __exec attribute."
self = object.__new__(cls)
if 'master' in kwargs:
self.__exec = kwargs['master'].__exec
else:
valid = tuple(meta.__REGISTRY.values())
for value in args:
if isinstance(value, valid):
self.__exec = value.__exec
break
else:
self.__exec = affinity.Affinity()
return self
safetkinter.py
"""Register tkinter classes with threadbox for immediate usage.
This module clones several classes from the tkinter library for use with
threads. Instances from these new classes should run on whatever thread
the root was created on. Child classes inherit the parent's safety."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>'
__date__ = '4 June 2012'
__version__ = 1, 0, 0
################################################################################
import time
import tkinter.filedialog
import tkinter.font
import tkinter.messagebox
import tkinter.scrolledtext
import tkinter.ttk
import threadbox
################################################################################
tkinter.NoDefaultRoot()
@threadbox.MetaBox.thread
def mainloop(self):
"Creates a synthetic main loop so that threads can still run."
while True:
try:
self.update()
except tkinter.TclError:
break
else:
time.sleep(tkinter._tkinter.getbusywaitinterval() / 1000)
threadbox.MetaBox.clone(tkinter.Misc, {'mainloop': mainloop})
################################################################################
OldButton = threadbox.MetaBox.clone(tkinter.Button)
Canvas = threadbox.MetaBox.clone(tkinter.Canvas)
OldFrame = threadbox.MetaBox.clone(tkinter.Frame)
Menu = threadbox.MetaBox.clone(tkinter.Menu)
PhotoImage = threadbox.MetaBox.clone(tkinter.PhotoImage)
Spinbox = threadbox.MetaBox.clone(tkinter.Spinbox)
StringVar = threadbox.MetaBox.clone(tkinter.StringVar)
Text = threadbox.MetaBox.clone(tkinter.Text)
Tk = threadbox.MetaBox.clone(tkinter.Tk)
Toplevel = threadbox.MetaBox.clone(tkinter.Toplevel)
################################################################################
Button = threadbox.MetaBox.clone(tkinter.ttk.Button)
Checkbutton = threadbox.MetaBox.clone(tkinter.ttk.Checkbutton)
Entry = threadbox.MetaBox.clone(tkinter.ttk.Entry)
Frame = threadbox.MetaBox.clone(tkinter.ttk.Frame)
Label = threadbox.MetaBox.clone(tkinter.ttk.Label)
Labelframe = threadbox.MetaBox.clone(tkinter.ttk.Labelframe)
Progressbar = threadbox.MetaBox.clone(tkinter.ttk.Progressbar)
Radiobutton = threadbox.MetaBox.clone(tkinter.ttk.Radiobutton)
Scale = threadbox.MetaBox.clone(tkinter.ttk.Scale)
Scrollbar = threadbox.MetaBox.clone(tkinter.ttk.Scrollbar)
Sizegrip = threadbox.MetaBox.clone(tkinter.ttk.Sizegrip)
Treeview = threadbox.MetaBox.clone(tkinter.ttk.Treeview)
################################################################################
Directory = threadbox.MetaBox.clone(tkinter.filedialog.Directory)
Font = threadbox.MetaBox.clone(tkinter.font.Font)
Message = threadbox.MetaBox.clone(tkinter.messagebox.Message)
ScrolledText = threadbox.MetaBox.clone(tkinter.scrolledtext.ScrolledText)
Simple_Server.py
#! /usr/bin/env python3
import socket, select
def main():
a = [socket.socket(socket.AF_INET, socket.SOCK_STREAM)] # socket array
a[0].bind(('', 8989))
a[0].listen(5)
while True:
for b in select.select(a, [], [])[0]: # ready socket
if b is a[0]:
a.append(b.accept()[0])
else:
try:
c = b.recv(1 << 12) # sent message
except socket.error:
b.shutdown(socket.SHUT_RDWR)
b.close()
a.remove(b)
else:
for d in (d for d in a[1:] if d is not b): # message sink
d.sendall(c)
if __name__ == '__main__':
main()