一、局域网多人聊天室-无私聊功能版
1.1 代码效果演示
1.2服务器端源码
'''
描述:局域网内基于多线程的TCP socket 聊天室。
'''
import socket,threading,time
clients = {}
class client(object):
def __init__(self,socket,addr,username):
self.addr = addr[0]
self.port = addr[1]
self.username = username
self.socket=socket
def sendMsg(self,msg,username,admin):
try:
if admin:
self.socket.send(("%s %s(管理员): %s" % (self.getTime(), username, msg)).encode("utf-8"))
else:
self.socket.send(("%s %s: %s" %(self.getTime(), username, msg)).encode("utf-8"))
return True
except:
return False
def recv(self,mtu=1024):
try:
data = self.socket.recv(mtu).decode("utf-8")
if data == "-!-quit-!-" or not data:
return False
return data
except:
return False
def close(self):
try:
self.socket.close()
return True
except:
return False
def getId(self):
return "%s-%s" % (self.addr,self.port)
def getTime(self):
return str(time.strftime("%Y-%m-%d %H:%M:%S"))
def new_client(c):
try:
print("%s(%s) 尝试连接" %(c.addr,c.port))
data = c.recv()
if not data:
return
if len(data) >= 16:
c.socket.send("用户名太长了")
return
c.username = data
print("用户%s %s(%s)已连接" %(c.username,c.addr,c.port))
c.socket.send("已连接".encode("utf-8"))
while True:
data = c.recv()
if not data:
break
else:
print("用户%s %s(%s) 发送了: %s" % (c.username,c.addr, c.port, data))
broadcast(data,c.username)
except socket.errno as e:
print("Socket error: %s" % str(e))
except Exception as e:
print("Other exception: %s" % str(e))
finally:
print("%s(%s) 断开连接" % (c.addr, c.port))
c.close()
clients.pop(c.getId())
def broadcast(msg,username,admin=False):
for c in clients.values():
c.sendMsg(msg,username,admin)
def start_server(port):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = "192.168.2.158"
server.bind((host, port))
# 监听客户端
server.listen(10)
print("服务器已开启,正在监听{}".format(server.getsockname()))
while True:
# 接受客户端连接
conn, addr = server.accept()
c = client(conn,addr,"")
clients[c.getId()] = c
t = threading.Thread(target=new_client, args=(c,))
t.start()
if __name__ == "__main__":
start_server(23333)
print("服务器已关闭")
1.3客户端源码
import socket,threading
import time,sys
running = False
def send(c):
time.sleep(0.5)
while True:
data = input('')
c.send(data.encode("utf-8"))
if data == "-!-quit-!-":
running = False
break
def recv(c,t2):
username = input("输入用户名: ")
c.send(username.encode("utf-8"))
t2.start()
while running:
try:
data = c.recv(1024).decode("utf-8")
if not data:
break
print(data)
except:
break
if __name__ == "__main__":
ip = ""
addrs = socket.getaddrinfo(socket.gethostname(), None)
print("开始扫描可用的服务器")
for i in range(100,254):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client.settimeout(0.1)
temp = "192.168.2.%s" % i
try:
data = client.connect_ex((temp, 23333))
if data == 0:
print("找到服务器%s" % temp)
a = input("是否连接 y/n")
if a == "y":
ip = temp
break
else:
continue
except:
print("2")
client.close()
if ip == "":
print("未连接任何服务器")
sys.exit()
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 导入 socket 模块
try:
client.connect((ip, 23333))
running = True
t2 = threading.Thread(target=send, args=(client,))
t1 = threading.Thread(target=recv, args=(client,t2))
t1.start()
t1.join()
t2.join()
except:
pass
finally:
print("连接已被关闭")
client.close()
二、局域网多人聊天室-带私聊功能版
开发要求: 用python TCP开发一个聊天室,实现如下功能:该程序实现局域网内的聊天功能,包括服务器端程序和客户端程序两部分。
要求:(1)支持汉字,(2)客户端连接到服务器后,显示在线的用户;然后可以选择任意其他在线用户聊天。
建议不使用GUI(可视化用户界面),以降低开发难度。
2.1服务器端源码
import json
import threading
from socket import *
from time import ctime
class PyChattingServer:
__socket = socket(AF_INET, SOCK_STREAM, 0)
__address = ('', 12231)
__buf = 1024
def __init__(self):
self.__socket.bind(self.__address)
self.__socket.listen(20)
self.__msg_handler = ChattingHandler()
def start_session(self):
print('等待客户连接...\r\n')
try:
while True:
cs, caddr = self.__socket.accept()
# 利用handler来管理线程,实现线程之间的socket的相互通信
self.__msg_handler.start_thread(cs, caddr)
except socket.error:
pass
class ChattingThread(threading.Thread):
__buf = 1024
def __init__(self, cs, caddr, msg_handler):
super(ChattingThread, self).__init__()
self.__cs = cs
self.__caddr = caddr
self.__msg_handler = msg_handler
# 使用多线程管理会话
def run(self):
try:
print('...连接来自于:', self.__caddr)
data = '欢迎你到来kenwanmao聊天室!请输入你的的昵称(不能带空格):'
self.__cs.sendall(bytes(data, 'utf-8'))
while True:
data = self.__cs.recv(self.__buf).decode('utf-8')
if not data:
break
self.__msg_handler.handle_msg(data, self.__cs)
print(data)
except socket.error as e:
print(e.args)
pass
finally:
self.__msg_handler.close_conn(self.__cs)
self.__cs.close()
class ChattingHandler:
__help_str = "[ SYSTEM ]\r\n" \
"输入/checkol,即可获得所有登陆用户信息\r\n" \
"输入/h,即可获得帮助\r\n" \
"输入@用户名 (注意用户名后面的空格)+消息,即可发动单聊\r\n" \
"输入/i,即可屏蔽群聊信息\r\n" \
"再次输入/i,即可取消屏蔽\r\n" \
"所有首字符为/的信息都不会发送出去"
__buf = 1024
__socket_list = []
__user_name_to_socket = {}
__socket_to_user_name = {}
__user_name_to_broadcast_state = {}
def start_thread(self, cs, caddr):
self.__socket_list.append(cs)
chat_thread = ChattingThread(cs, caddr, self)
chat_thread.start()
def close_conn(self, cs):
if cs not in self.__socket_list:
return
# 去除socket的记录
nickname = "SOMEONE"
if cs in self.__socket_list:
self.__socket_list.remove(cs)
# 去除socket与username之间的映射关系
if cs in self.__socket_to_user_name:
nickname = self.__socket_to_user_name[cs]
self.__user_name_to_socket.pop(self.__socket_to_user_name[cs])
self.__socket_to_user_name.pop(cs)
self.__user_name_to_broadcast_state.pop(nickname)
nickname += " "
# 广播某玩家退出聊天室
self.broadcast_system_msg(nickname + "离开了本聊天室")
# 管理用户输入的信息
def handle_msg(self, msg, cs):
js = json.loads(msg)
if js['type'] == "login":
if js['msg'] not in self.__user_name_to_socket:
if ' ' in js['msg']:
self.send_to(json.dumps({
'type': 'login',
'success': False,
'msg': '账号不能够带有空格'
}), cs)
else:
self.__user_name_to_socket[js['msg']] = cs
self.__socket_to_user_name[cs] = js['msg']
self.__user_name_to_broadcast_state[js['msg']] = True
self.send_to(json.dumps({
'type': 'login',
'success': True,
'msg': '昵称建立成功,输入/checkol可查看所有在线的人,输入/help可以查看帮助(所有首字符为/的消息都不会发送)'
}), cs)
# 广播其他人,他已经进入聊天室
self.broadcast_system_msg(js['msg'] + "已经进入了聊天室")
else:
self.send_to(json.dumps({
'type': 'login',
'success': False,
'msg': '账号已存在'
}), cs)
# 若玩家处于屏蔽模式,则无法发送群聊消息
elif js['type'] == "broadcast":
if self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]]:
self.broadcast(js['msg'], cs)
else:
self.send_to(json.dumps({
'type': 'broadcast',
'msg': '屏蔽模式下无法发送群聊信息'
}), cs)
elif js['type'] == "ls":
self.send_to(json.dumps({
'type': 'ls',
'msg': self.get_all_login_user_info()
}), cs)
elif js['type'] == "help":
self.send_to(json.dumps({
'type': 'help',
'msg': self.__help_str
}), cs)
elif js['type'] == "sendto":
self.single_chatting(cs, js['nickname'], js['msg'])
elif js['type'] == "ignore":
self.exchange_ignore_state(cs)
def exchange_ignore_state(self, cs):
if cs in self.__socket_to_user_name:
state = self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]]
if state:
state = False
else:
state = True
self.__user_name_to_broadcast_state.pop(self.__socket_to_user_name[cs])
self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]] = state
if self.__user_name_to_broadcast_state[self.__socket_to_user_name[cs]]:
msg = "通常模式"
else:
msg = "屏蔽模式"
self.send_to(json.dumps({
'type': 'ignore',
'success': True,
'msg': '[TIME : %s]\r\n[ SYSTEM ] : %s\r\n' % (ctime(), "模式切换成功,现在是" + msg)
}), cs)
else:
self.send_to({
'type': 'ignore',
'success': False,
'msg': '切换失败'
}, cs)
def single_chatting(self, cs, nickname, msg):
if nickname in self.__user_name_to_socket:
msg = '[TIME : %s]\r\n[ %s CHATTING TO %s ] : %s\r\n' % (
ctime(), self.__socket_to_user_name[cs], nickname, msg)
self.send_to_list(json.dumps({
'type': 'single',
'msg': msg
}), self.__user_name_to_socket[nickname], cs)
else:
self.send_to(json.dumps({
'type': 'single',
'msg': '该用户不存在'
}), cs)
print(nickname)
def send_to_list(self, msg, *cs):
for i in range(len(cs)):
self.send_to(msg, cs[i])
def get_all_login_user_info(self):
login_list = "[ SYSTEM ] ALIVE USER : "
for key in self.__socket_to_user_name:
login_list += self.__socket_to_user_name[key] + ",\r\n"
return login_list
def send_to(self, msg, cs):
if cs not in self.__socket_list:
self.__socket_list.append(cs)
cs.sendall(bytes(msg, 'utf-8'))
def broadcast_system_msg(self, msg):
data = '[TIME : %s]\r\n[ SYSTEM ] : %s' % (ctime(), msg)
js = json.dumps({
'type': 'system_msg',
'msg': data
})
# 屏蔽了群聊的玩家也可以获得系统的群发信息
for i in range(len(self.__socket_list)):
if self.__socket_list[i] in self.__socket_to_user_name:
self.__socket_list[i].sendall(bytes(js, 'utf-8'))
def broadcast(self, msg, cs):
data = '[TIME : %s]\r\n[%s] : %s\r\n' % (ctime(), self.__socket_to_user_name[cs], msg)
js = json.dumps({
'type': 'broadcast',
'msg': data
})
# 没有的登陆的玩家无法得知消息,屏蔽了群聊的玩家也没办法获取信息
for i in range(len(self.__socket_list)):
if self.__socket_list[i] in self.__socket_to_user_name \
and self.__user_name_to_broadcast_state[self.__socket_to_user_name[self.__socket_list[i]]]:
self.__socket_list[i].sendall(bytes(js, 'utf-8'))
def main():
server = PyChattingServer()
server.start_session()
main()
2.2客户端源码
import json
import threading
from socket import *
is_login = False
is_broadcast = True
#接受消息类
class ClientReceiveThread(threading.Thread):
__buf = 1024
#初始化
def __init__(self, cs):
super(ClientReceiveThread, self).__init__()
self.__cs = cs
def run(self):
self.receive_msg()
def receive_msg(self):
while True:
msg = self.__cs.recv(self.__buf).decode('utf-8')
if not msg:
break
js = json.loads(msg)
if js['type'] == "login":
if js['success']:
global is_login
is_login = True
print(js['msg'])
elif js['type'] == "ignore":
if js['success']:
global is_broadcast
if is_broadcast:
is_broadcast = False
else:
is_broadcast = True
print(js['msg'])
else:
if not is_broadcast:
print("[现在处于屏蔽模式]")
print(js['msg'])
#发送消息类
class ClientSendMsgThread(threading.Thread):
def __init__(self, cs):
super(ClientSendMsgThread, self).__init__()
self.__cs = cs
def run(self):
self.send_msg()
# 根据不同的输入格式来进行不同的聊天方式
def send_msg(self):
while True:
js = None
msg = input()
if not is_login:
js = json.dumps({
'type': 'login',
'msg': msg
})
#包含@,表示进入私聊模式
elif msg[0] == "@":
data = msg.split(' ')
if not data:
print("请重新输入")
break
nickname = data[0]
nickname = nickname.strip("@")
if len(data) == 1:
data.append(" ")
js = json.dumps({
'type': 'sendto',
'nickname': nickname,
'msg': data[1]
})
elif msg == "/help":
js = json.dumps({
'type': 'help',
'msg': None
})
elif msg == "/checkol":
js = json.dumps({
'type': 'ls',
'msg': None
})
elif msg == "/i":
js = json.dumps({
'type': 'ignore',
'msg': None
})
else:
if msg[0] != '/':
js = json.dumps({
'type': 'broadcast',
'msg': msg
})
if js is not None:
self.__cs.sendall(bytes(js, 'utf-8'))
def main():
buf = 1024
# 改变这个的地址,变成服务器的地址,那么只要部署到服务器上就可以全网使用了
address = ("127.0.0.1", 12231)
cs = socket(AF_INET, SOCK_STREAM, 0)
cs.connect(address)
data = cs.recv(buf).decode("utf-8")
if data:
print(data)
receive_thread = ClientReceiveThread(cs)
receive_thread.start()
send_thread = ClientSendMsgThread(cs)
send_thread.start()
while True:
pass
main()