关键词:多进程、TCP、共享内存
目前已运行在ali云服务器上,不出意外在服务器到期前都可以正常使用;大概8月到期;
ip 47.108.60.37 端口 2333
使用网络调试助手即可连接
实现难点
进程间通信,如何在单个进程中给其他socket发信息;
1.进程间通信
进程间通信有管道,消息队列,queue,数据库等方式,但是这几种方式不便于实现;
1.管道的局限性在于两个进程间,和控制数据流向,对多进程的数据传输就不是很方便;
2.关于消息队列其实并不是很不清楚,我查阅linux c上的关于消息队列的编程,linux c是用链表实现的,意思是对每个节点的数据都开可以直接进行操作,也算是一种方便。但是python的多进程库queue不太一样,只有put和get,意思是只能逐个放入和逐个取出,找了很多资料也没发现queue有更方便的用法,至少在多进程通信上没有更方便的用法;
3.数据库,目前觉得比较方便一点的是redis开源数据库,但是图快速开发的话,只实现特定功能确实有点大柴小用,虽然说python通过redis库直接进行操作也确实方便,但是需要运行一个redis服务端;
4.共享内存在目前看来是最优解,很方便的可以实现进程间内存共享,在数据量小的情况下至少是最合适的。
2.进程内给所以客户端发信息
最开始使用的共享内存创建的列表来添加不同客户端的socket,但是在删除socket的时候却出现了问题,删除列表内元素使用的remove方法,可能是socket创建后自己本身结果原因,传入的值被remove方法误读了,提示没有该值;如下,此为使用列表作为socket连接记录;
import socket as sk
import multiprocessing as mp
import time as t
import os
def c_s_c(sock_in, addr,sock_list): #进程出来函数
for sock_c in sock_list.items(): #遍历字典元组
sock_c[1].send(("user"+str(addr)+"---in---\n").encode()) #遍历通知所有在聊天室的用户有新用户进入
while True:
readdata = sock_in.recv(1024) #recv函数有阻塞左作用,若连接断开或者sock被关闭,返回空
if readdata:
#print(readdata) #服务端看信息调试用
for sock_c in sock_list.items():
sock_c[1].send(str(addr).encode()+readdata) #转发给所用用户
else:
for sock_c in sock_list.items():
sock_c[1].send(("user"+str(addr)+"---out---").encode()) #encode编码为字节序
del sock_list[addr] #将退出的用户从字典中删除
sock_in.close()
os._exit(0) #终止进程
break #摆设用
def main():
s = sk.socket(sk.AF_INET ,sk.SOCK_STREAM)
s.bind(('',2333))
s.listen()
sock_list=mp.Manager().dict() #共享内存字典--------------------------------
while True:
sock,addr = s.accept()
sock_list[addr]=sock #将地址作为索引--------------------------------
t1 = mp.Process(target=c_s_c, args=(sock, addr,sock_list)) #多进程------
t1.start()
if __name__ == '__main__':
main()
连接单个客户端后打印如下;

退出客户端并不能移除掉连接信息;

如下是使用字典,以ip addr 作为索引,问题才得以解决
import socket as sk
import multiprocessing as mp
import time as t
import os
def c_s_c(sock_in, addr,sock_list): #进程出来函数
for sock_c in sock_list.items(): #遍历字典元组
sock_c[1].send(("user"+str(addr)+"---in---\n").encode()) #遍历通知所有在聊天室的用户有新用户进入
while True:
readdata = sock_in.recv(1024) #recv函数有阻塞左作用,若连接断开或者sock被关闭,返回空
if readdata:
#print(readdata) #服务端看信息调试用
for sock_c in sock_list.items():
sock_c[1].send(str(addr).encode()+readdata) #转发给所用用户
else:
for sock_c in sock_list.items():
sock_c[1].send(("user"+str(addr)+"---out---").encode()) #encode编码为字节序
del sock_list[addr] #将退出的用户从字典中删除
sock_in.close()
os._exit(0) #终止进程
break #摆设用
def main():
s = sk.socket(sk.AF_INET ,sk.SOCK_STREAM)
s.bind(('',2333))
s.listen()
sock_list=mp.Manager().dict() #共享内存字典--------------------------------
while True:
sock,addr = s.accept()
sock_list[addr]=sock #将地址作为索引--------------------------------
t1 = mp.Process(target=c_s_c, args=(sock, addr,sock_list)) #多进程------
t1.start()
if __name__ == '__main__':
main()优化版本1
共享内存在应对大量数据处理时,因每个进程都对同一个变量进行操作,在for循环处易发生错误,但作为一个聊天室demo作演示已绰绰有余,大数据处理还得上数据库,;
import socket as sk
import multiprocessing as mp
import time as t
import os
def c_s_c(sock_in, addr,sock_list): #进程出来函数
for sock_c in sock_list.items():#遍历字典元组
sock_c[1].send(("user"+str(addr)+"---in---\n").encode())#遍历通知所有在聊天室的用户有新用户进入
while True:
try:
readdata = sock_in.recv(1024)#recv函数有阻塞左作用,若连接断开或者sock被关闭,返回空
if readdata:
print(readdata) #服务端看信息调试用
for sock_c in sock_list.items():
try:
sock_c[1].send(str(addr).encode()+readdata)#转发给所用用户
except:
del sock_list[sock_c[0]]
else:
for sock_c in sock_list.items():
if sock_c[0]!=addr: #如果链接中断了,就不再发给该sock,继续操作该sock会引发异常;
sock_c[1].send(("user"+str(addr)+"---out---\n").encode())#encode编码为字节序
del sock_list[addr] #将退出的用户从字典中删除
print("del success")
os._exit(0) #终止进程
break#摆设用
except:
for sock_c in sock_list.items():
if sock_c[0]!=addr: #如果链接中断了,就不再发给该sock,继续操作该sock会引发异常;
sock_c[1].send(("user"+str(addr)+"---out---\n").encode())#encode编码为字节序
del sock_list[addr] #将退出的用户从字典中删除
print("del success")
os._exit(0) #终止进程
break#摆设用
def main():
s = sk.socket(sk.AF_INET ,sk.SOCK_STREAM)
s.setsockopt(sk.SOL_SOCKET, sk.SO_KEEPALIVE, 1)
s.setsockopt(sk.SOL_TCP, sk.TCP_KEEPIDLE, 1)
s.setsockopt(sk.SOL_TCP, sk.TCP_KEEPINTVL, 1)
s.setsockopt(sk.SOL_TCP, sk.TCP_KEEPCNT, 1)
s.bind(('',2333))
s.listen()
sock_list=mp.Manager().dict()#共享内存字典--------------------------------
while True:
sock,addr = s.accept()
sock_list[addr]=sock#将地址作为索引--------------------------------
t1 = mp.Process(target=c_s_c, args=(sock, addr,sock_list)) #多进程------
t1.start()
if __name__ == '__main__':
main()
理论上支持任意多的用户接入~至少几十个接入没有问题;如果需要开发个客户端的话,用单终端实现收和发这个暂时不知道怎么解决,如果用一些gui库的话,在两个窗口上,一个作接收,一个作发送还是有个大概思路
小记2
目前这35行代码运行仍然有需要改善的地方,在使用中发现,如果客户端没有按照3次或者4次握手正常断开,服务端会继续维持这个链接720s,这个结果就影响到了进程的终止处理,因此这里需要加入客户端保活检测机制,后续在尝试解决;
优化版本2
目前这个版本已解决断开造成的异常问题,
import socket as sk
import threading as mp
#
def c_s_c(sock_in, addr,sock_list:dict): #线程处理函数
print(sock_list)
for sock_c in list(sock_list.keys()):#遍历字典
try:
sock_list[sock_c].send(("user"+str(addr)+"---in---\n").encode())#遍历通知所有在聊天室的用户有新用户进入
except:
pass
while True:
readdata = sock_in.recv(1024)#recv函数有阻塞作用,若连接断开或者sock被关闭,返回空
if readdata:
print(readdata) #服务端看信息调试用
for sock_c in list(sock_list.keys()):
try:
sock_list[sock_c].send(str(addr).encode()+readdata)#转发给所用用户
except:
del sock_list[sock_c] #异常则删除此对象
print("del:{} success".format(sock_c))
else:
for sock_c in list(sock_list.keys()):
try:
sock_list[sock_c].send(("user"+str(addr)+"---out---\n").encode())#encode编码为字节序
except:
pass
break#退出线程
def main():
s = sk.socket(sk.AF_INET ,sk.SOCK_STREAM)
s.bind(('',2333))
s.listen()
sock_list={}#字典--------------------------------
while True:
sock,addr = s.accept()
sock_list[addr]=sock#将地址作为索引--------------------------------
t1 = mp.Thread(target=c_s_c, args=(sock, addr,sock_list)) #多进程------
t1.start()
if __name__ == '__main__':
main()
优化版本3
import socket as sk
import threading as mp
#
def c_s_c(sock_in, addr,sock_list:dict): #线程处理函数
print(sock_list)
for sock_c in list(sock_list.keys()):#遍历字典
try:
sock_list[sock_c].send(("user"+str(addr)+"---in---\n").encode())#遍历通知所有在聊天室的用户有新用户进入
except:
pass
while True:
readdata = sock_in.recv(1024)#recv函数有阻塞作用,若连接断开或者sock被关闭,返回空
if readdata:
print(readdata) #服务端看信息调试用
for sock_c in list(sock_list.keys()):
try:
sock_list[sock_c].send(str(addr).encode()+readdata)#转发给所用用户
except:
pass
else:
break#退出循环
for sock_c in list(sock_list.keys()):
try:
sock_list[sock_c].send(("user"+str(addr)+"---out---\n").encode())#encode编码为字节序
except:
del sock_list[sock_c] #异常则删除此对象
print("del:{} success".format(sock_c))
def main():
s = sk.socket(sk.AF_INET ,sk.SOCK_STREAM)
s.bind(('',2333))
s.listen()
sock_list={}#字典--------------------------------
while True:
sock,addr = s.accept()
sock_list[addr]=sock#将地址作为索引--------------------------------
t1 = mp.Thread(target=c_s_c, args=(sock, addr,sock_list)) #多线程------
t1.start()
if __name__ == '__main__':
main()
作者:戳人痛处
















