All right, bitches and hoes. 好久不见集美们,本可儿华丽回归写文章噜,虽然只是一些赔钱的小心得,也权当是记录一下吧。这次写的是关于局域网服务端和客户端的通信程序,使用到的是python语言。
使用工具serversocket库
pysimplegui库(使用这个库真的很方便,但是本可人翻遍了全站几乎没有找到合适的中文教程文章。有些文章看起来是翻译了,实则没有一点用处)
服务端
class ThreadingTCPServer(socketserver.ThreadingMixIn,socketserver.TCPServer):#继承ThreadingMixIn,使其支持多线程
pass
class Server(socketserver.baseRequestHandler):
def setup(self):
connection_pool.append(self.request)
connection_name.append(self.client_address)
msg = '服务端已连接,你的客户序号为{}'.format(len(connection_pool)-1) # 客户端连接上了,服务器发送的消息
self.request.sendall(msg.encode('utf-8'))
print_msg(server_name,'{}已连接'.format(self.client_address))
def handle(self):
while True:
try:
data = self.request.recv(1024).decode('utf-8')
if len(data) == 0:#判断链接已经断开
connection_pool.remove(self.request)
connection_name.remove(self.client_address)
break
else:
commute_from = connection_name.index(self.client_address)
print_msg('{} to {}'.format(commute_from,server_name), data)
except:
connection_name.remove(self.client_address)
connection_pool.remove(self.request)
def finish(self):
print_msg(server_name,'{}退出连接'.format(self.client_address))
分析:这个类继承自baseRequestHandler类,是服务端用于处理socket对象通信的主类。实现处理socket通信,需要重写baseRequestHandler类中的三个方法:setup、handle和finish。类中的调用方法顺序为setup()–>handle()–>finish()。handle方法中的while True循环用于一直等待接受客户端发来的消息。
客户端
class Client:
def __init__(self,ip,port):
self.s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.s.connect((ip,port)) #ip 和 port通过界面输入
def send(self,msg):#客户端发送数据方法
try:
self.s.sendall(msg.encode('utf-8'))
self.print_msg(self.get_socket(),msg)
except Exception as e:
self.print_msg(self.get_socket(),'断开连接')
self.con_close()
def print_msg(self,name,msg): #在客户端界面显示信息
print(time.ctime())
print('{}:{}'.format(name,msg))
def receive(self):#客户端接收信息
while True:
msg = self.s.recv(1024).decode(encoding='utf-8')
if len(msg) != 0:
self.print_msg('Server',msg)
else:
break
def get_socket(self):#获取客户端的socketname
return str(self.s.getsockname())
def con_close(self): #关闭socket对象
self.s.shutdown(2)
self.s.close()
分析:客户端单使用socket库实现,需要注意的是,关闭socket连接需要在close之前调用shutdown方法,并设置参数为2,表示关闭双向连接(发送和接受)。调用了close()函数,程序中只是确保了对于某个特定的进程或线程来说,该连接是关闭的;但socket只有在所有的进程调用了close()或者socket超出了工作范围时,才会真正的被关闭或删除。在本程序中,单调用close()会出错。
客户端界面
def window1():
layout = [[ps.Output(size=(80,40),key='output')],
[ps.InputText(size=(40,10),key='input',do_not_clear=False),ps.Button('send')]]
return ps.Window('ChatRoomClient',layout=layout,grab_anywhere=True)
def window2():
Ip_layout = [[ps.Text('IP地址'), ps.InputText(size=(15, 1), key='ip',do_not_clear=True), ps.Text('端口'), ps.InputText(size=(6, 1), key='port',do_not_clear=True),
ps.Button('connect')]]
return ps.Window('IpAndPort',layout=Ip_layout)
def main():
ip_window = window2()
while True:
event,value = ip_window.read()
if event == ps.WIN_CLOSED:
ip_window.close()
break
elif event == 'connect':
if value['ip'] == '' or value['port'] == '':
ps.popup('警告','连接的ip地址和端口号不能为空',keep_on_top=True)
else:
ip = value['ip']
port = int(value['port'])
if re.match(r'(d|[1-9]d|1d{2}|2[0-4]d|25[0-5])(.(d|[1-9]d|1d{2}|2[0-4]d|25[0-5])){3}',ip) and re.match(r'[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]{1}|6553[0-5]',value['port']):
ip_window.hide()
msg_window = window1()#创建socket对象
client = Client(value['ip'], port)
recv_thread = threading.Thread(target=client.receive)#新开线程,循环接受信息
recv_thread.daemon = True #设置守护线程,主线程退出后强制终止子线程
recv_thread.start()
while True:
event1, value1 = msg_window.read()
if event1 == 'send':
if value1['input'] == '':
ps.popup('警告','发送的内容不能为空',keep_on_top=True)
else:
client.send(value1['input'])
elif event1 == ps.WIN_CLOSED:
client.con_close()
msg_window.close()
ip_window.un_hide()
break
else:
ps.popup('警告','输入的ip地址格式或端口格式不对',keep_on_top=True)
分析:使用pysimplegui库,基本的使用方法在站里都可以找到,新开一个线程,专门用来执行阻塞模式下socket对象接收信息的方法,在主线程里直接接受信息的话会出现只有服务端发来信息,客户端才能发信息的的美丽景象,真的很美惹。
服务端界面
def window():
layout = [[ps.Output(size=(80, 40), key='output')],
[ps.InputText(size=(3, 1),key='client'),ps.InputText(size=(40, 10), key='input',do_not_clear=False), ps.Button('send'),ps.Button('show')]]
return ps.Window('ChatRoomServer', layout=layout, grab_anywhere=True)
def main():
server = ThreadingTCPServer((ip, port), Server)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()
msg_window = window()
print_msg(server_name,'等待用户连接中。。。')
while True:
event,value = msg_window.read()
if event == ps.WIN_CLOSED:
if len(connection_pool) != 0:
for request in connection_pool:
request.sendall('服务器即将关闭'.encode('utf-8'))
time.sleep(2)
server.shutdown()
server.server_close()
msg_window.close()
break
elif event == 'send':
if value['client'] == '' or value['input'] == '':
ps.popup('警告','通信内容和通信对象不能为空',keep_on_top=True)
else:
commute_to = int(value['client'])
if commute_to >= len(connection_pool):
ps.popup('警告','该序号下没有用户',keep_on_top=True)
else:
connection_pool[commute_to].sendall(value['input'].encode('utf-8'))
print_msg('{} to {}'.format(server_name,commute_to),value['input'])
elif event == 'show':
if len(connection_name) != 0:
print_msg(server_name,'当前用户列表如下')
for index in range(len(connection_name)):
print('{}:{}'.format(index,connection_name[index]))
else:
print_msg(server_name,'当前尚无用户连接')
分析:新建一个线程并将其设置为守护线程用于整体管理服务端,主线程响应界面输入
结果展示服务端显示两个客户接入
向0号客户发送信息
0号客户端收到
1号客户发送信息
服务端收到信息



