首先看一下最终效果,在终端模式下实现类似于聊天的对话框
实现方式如下,通过两个list: iwords和swords分别缓存来自终端输入和socket收到的数据,然后再将数据通过屏幕输出并通过socket接口发送出去,通过四个独立的线程分别完成各自的工作:
myinput:获取输入区域内的输入数据,并将数据append到iwords和swords
myoutput:检测swords长度并通过swords.pop(0)的方式将list中的数据按队列的方式显示到输出区域
sserver:socket的server端程序,用于接收数据,并将其append到swords中
sclient:socket的client端程序,用户将iwords中的数据通过socket接口发送出去
sserver和sclient都通过参数flag来控制是启用TCP或者UDP协议,(0:tcp, 1:udp)
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
import curses import curses.textpad import threading import time import socket l_name='test1' l_addr = '' l_port = 50007 r_addr = '127.0.0.1' r_port = 50008 iwords = [] swords = [] def myoutput(stdwin): global iwords, owords lines = 0 while True: if len(swords) != 0: wtmp = swords.pop(0) lines += int(len(wtmp) / curses.COLS) +1 if lines >= curses.LINES - 3: stdwin.erase() lines = int(len(wtmp) / curses.COLS) +1 stdwin.addstr(wtmp + '\n') stdwin.refresh() time.sleep(0.1) def myinput(stdwin): global iwords, swords, l_name while True: stdwin.addstr(l_name + '>') stdwin.refresh() wtmp = '' while True: k = stdwin.getkey() if k != '\n': wtmp += k else: iwords.append(wtmp) wtmp = l_name + '(' + time.strftime('%X', time.localtime(time.time())) + '):' + wtmp swords.append(wtmp) stdwin.erase() break def sserver(flag=0): global swords, l_addr, l_port try: if flag==0: tcps = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcps.bind((l_addr, l_port)) tcps.listen(1) while True: tconn, taddr = tcps.accept() while tconn: tdata = tconn.recv(1024) wtmp = tdata.decode('utf-8') swords.append(wtmp) time.sleep(0.2) elif flag==1: udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udps.bind((l_addr, l_port)) while True: udata, uaddr = udps.recvfrom(1024) wtmp = udata.decode('utf-8') swords.append(wtmp) time.sleep(0.2) except Exception: raise Exception finally: if flag==0: tcps.close() else: udps.close() exit(-1) def sclient(flag=0): global iwords, r_addr, r_port, l_name try: if flag==0: tcpc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) while True: tconn = tcpc.connect_ex((r_addr, r_port)) if tconn !=0: time.sleep(1) continue else: while True: if len(iwords) != 0: tcpc.sendall(bytearray(l_name + '(' + time.strftime('%X', time.localtime(time.time())) + '):' + iwords.pop(0), 'utf-8')) time.sleep(0.2) elif flag==1: udpc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: if len(iwords) !=0: udpc.sendto(bytearray(l_name + '(' + time.strftime('%X', time.localtime(time.time())) + '):' + iwords.pop(0), 'utf-8'), (r_addr, r_port)) time.sleep(0.2) except Exception: raise Exception finally: if flag==0: tcpc.close() else: udpc.close() exit(-1) try: stdscr = curses.initscr() stdwin1 = curses.newwin(1,curses.COLS,curses.LINES-1,0) stdwin2 = curses.newwin(1000, curses.COLS, 0,0) #stdwin2.idlok(1) #stdwin2.scrollok(True) threads = [threading.Thread(target=myinput, name='myinput', args=(stdwin1,)), threading.Thread(target=myoutput, name='myoutput',args=(stdwin2,)), threading.Thread(target=sserver, name='sserver', args=(0,)), threading.Thread(target=sclient, name='sclient', args=(0,))] for i in threads: i.start() i.join() except Exception: raise Exception print('python curses operate win error') finally: curses.endwin() |
对话双方用的代码都是一样的,唯一需要修改的就是对端地址和对端端口两个参数(r_addr和r_port),在本地同时起两个console测试就是最上面的效果图,TCP建链状态如下:
用wireshark跟踪也可以看到消息内容:
已知的问题:
1.程序没有结束功能,如果是通过python运行的,可以按下面方式杀死进程
sudo ps aux | grep socket | grep -v 'grep'| awk '{print $2}' | xargs kill -9
2.因为程序没有正常退出的功能,所以有的时候socket会初始化失败,大致都是因为端口被占用或者已经被监听了
3.输入框的提示符显示不友好,有时候无法正从刷新出来
4.偶然遇见一次tcp connect重复多次会出现建立监听的情况,但是后来没有再复现