基于select的python聊天室程序

python网络编程具体参考《python select网络编程详细介绍》

在python中,select函数是一个对底层操作系统的直接访问的接口。它用来监控sockets、files和pipes,等待IO完成(Waiting for I/O completion)。当有可读、可写或是异常事件产生时,select可以很容易的监控到。
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。

聊天室程序如下。运行多个client,则可互相聊天,输入"exit"即可退出

服务器代码

#!/bin/env python
#-*- coding:utf8 -*-

"""
server select
"""


import sys
import time
import socket
import select
import logging
import Queue


g_select_timeout = 10

class Server(object):
    def __init__(self, host='10.1.32.80', port=33333, timeout=2, client_nums=10):
        self.__host = host
        self.__port = port
        self.__timeout = timeout
        self.__client_nums = client_nums
        self.__buffer_size = 1024

        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.setblocking(False)
        self.server.settimeout(self.__timeout)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) #keepalive
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口复用
        server_host = (self.__host, self.__port)
        try:
            self.server.bind(server_host)
            self.server.listen(self.__client_nums)
        except:
            raise

        self.inputs = [self.server] #select 接收文件描述符列表  
        self.outputs = [] #输出文件描述符列表
        self.message_queues = {}#消息队列
        self.client_info = {}

    def run(self):
        while True:
            readable , writable , exceptional = select.select(self.inputs, self.outputs, self.inputs, g_select_timeout)
            if not (readable or writable or exceptional) :
                continue

            for s in readable :
                if s is self.server:#是客户端连接
                    connection, client_address = s.accept()
                    #print "connection", connection
                    print "%s connect." % str(client_address)
                    connection.setblocking(0) #非阻塞
                    self.inputs.append(connection) #客户端添加到inputs
                    self.client_info[connection] = str(client_address)
                    self.message_queues[connection] = Queue.Queue()  #每个客户端一个消息队列

                else:#是client, 数据发送过来
                    try:
                        data = s.recv(self.__buffer_size)
                    except:
                        err_msg = "Client Error!"
                        logging.error(err_msg)
                    if data :
                        #print data
                        data = "%s %s say: %s" % (time.strftime("%Y-%m-%d %H:%M:%S"), self.client_info[s], data)
                        self.message_queues[s].put(data) #队列添加消息 
                        
                        if s not in self.outputs: #要回复消息
                            self.outputs.append(s)
                    else: #客户端断开
                        #Interpret empty result as closed connection
                        print "Client:%s Close." % str(self.client_info[s])
                        if s in self.outputs :
                            self.outputs.remove(s)
                        self.inputs.remove(s)
                        s.close()
                        del self.message_queues[s]
                        del self.client_info[s]

            for s in writable: #outputs 有消息就要发出去了
                try:
                    next_msg = self.message_queues[s].get_nowait()  #非阻塞获取
                except Queue.Empty:
                    err_msg = "Output Queue is Empty!"
                    #g_logFd.writeFormatMsg(g_logFd.LEVEL_INFO, err_msg)
                    self.outputs.remove(s)
                except Exception, e:  #发送的时候客户端关闭了则会出现writable和readable同时有数据,会出现message_queues的keyerror
                    err_msg = "Send Data Error! ErrMsg:%s" % str(e)
                    logging.error(err_msg)
                    if s in self.outputs:
                        self.outputs.remove(s)
                else:
                    for cli in self.client_info: #发送给其他客户端
                        if cli is not s:
                            try:
                                cli.sendall(next_msg)
                            except Exception, e: #发送失败就关掉
                                err_msg = "Send Data to %s  Error! ErrMsg:%s" % (str(self.client_info[cli]), str(e))
                                logging.error(err_msg)
                                print "Client: %s Close Error." % str(self.client_info[cli])
                                if cli in self.inputs:
                                    self.inputs.remove(cli)
                                    cli.close()
                                if cli in self.outputs:
                                    self.outputs.remove(s)
                                if cli in self.message_queues:
                                    del self.message_queues[s]
                                del self.client_info[cli]

            for s in exceptional:
                logging.error("Client:%s Close Error." % str(self.client_info[cli]))
                if s in self.inputs:
                    self.inputs.remove(s)
                    s.close()
                if s in self.outputs:
                    self.outputs.remove(s)
                if s in self.message_queues:
                    del self.message_queues[s]
                del self.client_info[s]
        

if "__main__" == __name__:
    Server().run()

客户端代码

#!/usr/local/bin/python
# *-* coding:utf-8 -*-

"""
client.py
"""


import sys
import time
import socket
import threading


class Client(object):
    def __init__(self, host, port=33333, timeout=1, reconnect=2):
        self.__host = host
        self.__port = port
        self.__timeout = timeout
        self.__buffer_size = 1024
        self.__flag = 1
        self.client = None
        self.__lock = threading.Lock() 
    
    @property
    def flag(self):
        return self.__flag

    @flag.setter
    def flag(self, new_num):
        self.__flag = new_num

    def __connect(self):
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        #client.bind(('0.0.0.0', 12345,))
        client.setblocking(True)
        client.settimeout(self.__timeout)
        client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口复用
        server_host = (self.__host, self.__port)
        try:
            client.connect(server_host)
        except:
            raise
        return client

    def send_msg(self):
        if not self.client:
            return
        while True:
            time.sleep(0.1)
            #data = raw_input()
            data = sys.stdin.readline().strip()
            if "exit" == data.lower():
                with self.__lock:
                    self.flag = 0
                break
            self.client.sendall(data)
        return

    def recv_msg(self):
        if not self.client:
            return
        while True:
            data = None
            with self.__lock:
                if not self.flag:
                    print 'ByeBye~~'
                    break
            try:
                data = self.client.recv(self.__buffer_size)
            except socket.timeout:
                continue
            except:
                raise
            if data:
                print "%s\n" % data
            time.sleep(0.1)
        return

    def run(self):
        self.client = self.__connect()
        send_proc = threading.Thread(target=self.send_msg)
        recv_proc = threading.Thread(target=self.recv_msg)
        recv_proc.start()
        send_proc.start()
        recv_proc.join()
        send_proc.join()
        self.client.close()


if "__main__" == __name__:
    Client('10.1.32.80').run()

 

posted @ 2016-11-23 12:14  RunningPower  阅读(1247)  评论(0编辑  收藏  举报