python聊天程序2 UDP

本文从WordPress迁移而来, 查看全部WordPress迁移文章

这其实是个作业,和之前做的那个功能上是一样的,这次主要是用一下python socket的UDP

一个最简单的UDP程序

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import sys
import socket
import time

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
ip = socket.gethostbyname(socket.gethostname())
port = 34567
address = (ip,port)

s.bind(address)

while True:
data = s.recvfrom(1024)
if not data:
break
print data[1] , ' > ' , data[0]

s.shutdown(socket.SHUT_RDWR)
s.close()

client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
import socket
import time

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sip = socket.gethostbyname(socket.gethostname())
sport = 34567

while True:
data = raw_input('>')
if data == 'exit':
break
s.sendto(data,(sip,sport))

s.shutdown(socket.SHUT_RDWR)
s.close()

接收信息前必须前bind,否则怎么知道在哪个端口受信息呢?

发信息要指定发送的地址

recvfrom(bufszie)

sendto(data,(ip,port)) #两个参数,第二个参数是一个元组,包含ip和port

本程序的目的是实现客户端通信,但不通过服务器,而是客户端之间直接通信

实现方法是

  1. 客户端建立一个UDP的socket,bind一个端口,开始接收信息
  2. 客户端接着连接服务器(TCP),把告诉服务器,自己的UDP监听端口是什么,服务器保存下来
  3. 有用户上线或下线,服务器都通知其余所有用户(TCP)
  4. 用户a要与用户b通信,那么需要得到用户b的bind端口,每次准备通信前,到服务器那里询问,用户b的bind端口是什么,服务器把用户b。bind端口发送给用户a(TCP),用户a将信息直接发送到用户b的这个bind端口(UDP)
  5. 对于客户端,需要2个socket,一个TCP,用于和服务器间通信,一个UDP,用于和其他客户端通信

ps:用了个新的QT控件 QTableWidget 代码在serverUI.py,发现了designer是比较好用的

MyServer.py

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
# -*- coding:utf-8 -*-
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from serverUI import Ui_mainWidget
import socket
import sys
import threading
import time

class MyServer:

#构造函数
def __init__(self):
self.createSocket()
self.createGui()
self.createSignalSlot()

#创建socket
def createSocket(self):
self.mySocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.host = socket.gethostname()
self.ip = socket.gethostbyname(self.host)
self.port = 34567
self.bufsize = 1024
self.clientSocket = {}

#创建GUI
def createGui(self):
self.mainWidget = QWidget()
self.myUi = Ui_mainWidget()
self.myUi.setupUi(self.mainWidget)
self.mainWidget.setWindowTitle('Server | '+str(self.ip)+":"+str(self.port))

#编辑信号和槽
def createSignalSlot(self):
self.myUi.quitPB.clicked.connect(self.quitApp)

#退出程序的槽函数
def quitApp(self):
try:
self.mySocket.shutdown(socket.SHUT_RDWR)
except:
pass #print 'shutdown error'
try:
for key in self.clientSocket:
self.clientSocket[key][0].shutdown(socket.SHUT_RDWR)
self.clientSocket.close()
except:
pass #print 'clientSocket.close() error'
self.mainWidget.close()

#启动线程函数
def start(self):
self.listenThread = threading.Thread(target=self.listenFunc,args=())
self.listenThread.setDaemon(True)
self.listenThread.start()

#监听,等待连接
def listenFunc(self):
self.mySocket.bind((self.ip,self.port))
self.mySocket.listen(10)
while True:
#print 'waiting for connect ...... '
connection , clientAddress = self.mySocket.accept()
clientAddress = self.changeAddress(clientAddress)
self.clientLogin(connection,clientAddress)
recvThread = threading.Thread(target=self.recvFunc,args=(connection,clientAddress))
recvThread.setDaemon(True)
recvThread.start()
self.mySocket.shutdown(socket.SHUT_RDWR)
self.mySocket.close()

def clientLogin(self,con,caddr):
for key in self.clientSocket:
self.clientSocket[key][0].send('ADD'+caddr) #通知其余客户该客户上线
con.send('ADD'+key) #通知该客户,已经在线的客户
self.clientSocket[caddr] = [con,-1]
newItem = QTableWidgetItem(self.PYSTOQTS(caddr)) #QTableWidgetItem(self.PYSTOQTS(str(listenPort)))
self.myUi.clientTW.insertRow(self.myUi.clientTW.rowCount())
self.myUi.clientTW.setItem(self.myUi.clientTW.rowCount()-1,0,newItem)

def clientLogout(self,caddr):
del self.clientSocket[caddr]
for key in self.clientSocket:
self.clientSocket[key][0].send('DEL'+caddr)
for i in range(self.myUi.clientTW.rowCount()):
if self.myUi.clientTW.item(i,0).text() == caddr:
self.myUi.clientTW.removeRow(i)
break

def recvFunc(self,con,caddr):
while True:
try:
data = con.recv(self.bufsize)
except:
break #print 'recv error , lost the connection of ',caddr
if not data:
break #print caddr , 'exit'
#print caddr,data
if data[:4] == 'PORT':
self.clientSocket[caddr][1] = data[4:]
for i in range(self.myUi.clientTW.rowCount()):
if self.myUi.clientTW.item(i,0).text() == caddr:
newItem = QTableWidgetItem(self.PYSTOQTS(data[4:]))
self.myUi.clientTW.setItem(i,1,newItem)
break

elif data[:5] == 'QUERY':
key = data[5:]
con.send('CPORT'+self.clientSocket[key][1])

con.shutdown(socket.SHUT_RDWR)
con.close()
self.clientLogout(caddr)

def show(self):
self.mainWidget.show()

#将 python字符串 转化为 QT字符串
def PYSTOQTS(self,pystring):
return QString(unicode(pystring,'gb2312','ignore'))

#将 QT字符串 转化为 python字符串
def QTSTOPYS(self,qtstring):
return unicode(qtstring.toUtf8(),'utf8','ignore').encode('gb2312')

def changeAddress(self,caddr):
return str(caddr[0])+':'+str(caddr[1])

def main():
qtApp = QApplication(sys.argv)
myServer = MyServer()
myServer.start()
myServer.show()
sys.exit(qtApp.exec_())

if __name__ == '__main__':
main()

MyClient.py

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
147
148
149
150
151
152
153
154
155
156
# -*- coding:utf-8 -*-
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from clientUI import Ui_mainWidget
import socket
import sys
import threading
import time
import random

class MyClient:

def __init__(self):
self.createSocket()
self.createGui()
self.createSignalSlot()

#创建socket,3个,TCPSocket连接服务器(TCP),UDPSocket监听其他客户发来的信息(UDP),sendSocket向其他客户发信息(UDP)
def createSocket(self):
self.ip = socket.gethostbyname(socket.gethostname()) #自己的ip
self.port = -1 #和服务器连接时的端口
self.listenPort = random.randint(10000,60000) #自己监听的端口
self.sip = self.ip
self.sport = 34567
self.cip = '-1'
self.cport = -1
self.TCPSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #连接服务器的socket,TCP
self.UDPSocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #接收并发送信息,UDP
self.UDPSocket.bind((self.ip,self.listenPort))
self.bufsize = 1024

def createGui(self):
self.mainWidget = QWidget()
self.myUi = Ui_mainWidget()
self.myUi.setupUi(self.mainWidget)

def createSignalSlot(self):
self.myUi.quitPB.clicked.connect(self.quitApp)
self.myUi.inputTE.textChanged.connect(self.activeSendPB)
self.myUi.sendPB.clicked.connect(self.sendFunc)

def start(self):
listenThread = threading.Thread(target=self.listenFunc,args=())
listenThread.setDaemon(True)
listenThread.start()
connectThread = threading.Thread(target=self.connectFunc,args=())
connectThread.setDaemon(True)
connectThread.start()

def sendFunc(self):
caddr = self.QTSTOPYS( self.myUi.userLW.currentItem().text() )
qtData = self.myUi.inputTE.toPlainText()
pyData = caddr+'>'+self.QTSTOPYS(qtData)
self.myUi.inputTE.clear()
self.TCPSocket.sendall('QUERY'+caddr) #想服务器发送一个请求,TCP发送
while self.cport == -1:
pass
self.myUi.messageTB.append( self.PYSTOQTS(self.myTime()+'\n'+'To '+caddr+' : '+pyData[pyData.find('>')+1:]) )
caddr = caddr[:caddr.find(':')]
#print caddr,pyData,self.cport
#self.sendSocket.sendto(pyData,(caddr,self.cport)) #向客户端发送,UDP发送
self.UDPSocket.sendto(pyData,(caddr,self.cport)) #向客户端发送,UDP发送
self.cport = -1


#启动监听
def listenFunc(self):
while True:
data = self.UDPSocket.recvfrom(self.bufsize) #UDP接收的数据是一个元组 (数据,(发送者ip,发送者port)) ,第二项又是个元组
#print data[0],data[1]
caddr = data[0][:data[0].find('>')]+'('+str(data[1][1])+')'
data = data[0][data[0].find('>')+1:]
self.myUi.messageTB.append(self.PYSTOQTS(self.myTime()+'\n'+'From '+caddr+' : '+data))
self.UDPSocket.shutdown(socket.SHUT_RDWR)
self.UDPSocket.close()

#连接服务器
def connectFunc(self):
while True:
while True:
try:
self.TCPSocket.connect((self.sip,self.sport)) #TCP连接
break
except:
pass #print 'connect error'
self.TCPSocket.sendall('PORT'+str(self.listenPort))
self.port = self.TCPSocket.getsockname()[1]
self.mainWidget.setWindowTitle('Clinet | '+str(self.ip)+':'+str(self.port)+' | '+str(self.listenPort))
while True:
try:
data = self.TCPSocket.recv(self.bufsize)
except:
break #print 'recv from server error'
if not data:
break #print 'server close'
#print data
if data[:3] == 'ADD':
self.clientLogin(data[3:])
elif data[:3] == 'DEL':
self.clientLogout(data[3:])
elif data[:5] == 'CPORT': #在connectThread线程里修改self.cport变量的值
self.cport = int(data[5:])
self.TCPSocket.shutdown(socket.SHUT_RDWR)
self.TCPSocket.close()


def clientLogin(self,caddr):
self.myUi.userLW.addItem(caddr)

def clientLogout(self,caddr):
for i in range(self.myUi.userLW.count()):
if self.myUi.userLW.item(i).text() == caddr:
self.myUi.userLW.takeItem(i)
break

def show(self):
self.mainWidget.show()

def quitApp(self):
try:
self.TCPSocket.shutdown(socket.SHUT_RDWR)
self.TCPSocket.close()
self.UDPSocket.shutdown(socket.SHUT_RDWR)
self.UDPSocket.close()
except:
pass #print 'close socket error'
self.mainWidget.close()

#显示当前时间,用time.ctime()的话,格式比较恶心,所以自己定义个格式
def myTime(self):
return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())

#将 python字符串 转化为 QT字符串
def PYSTOQTS(self,pystring):
return QString(unicode(pystring,'gb2312','ignore'))

#将 QT字符串 转化为 python字符串
def QTSTOPYS(self,qtstring):
return unicode(qtstring.toUtf8(),'utf8','ignore').encode('gb2312')

#一个小小的槽函数,用于激活send按钮
def activeSendPB(self):
if self.myUi.inputTE.toPlainText().isEmpty() == True:
self.myUi.sendPB.setEnabled(False)
else:
self.myUi.sendPB.setEnabled(True)

def main():
qtApp = QApplication(sys.argv)
myClient = MyClient()
myClient.start()
myClient.show()
sys.exit(qtApp.exec_())

if __name__ == '__main__':
main()

serverUI.py

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
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'serverUI.ui'
#
# Created: Thu Apr 10 17:34:59 2014
# by: PyQt4 UI code generator 4.10.3
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s

try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)

class Ui_mainWidget(object):
def setupUi(self, mainWidget):
mainWidget.setObjectName(_fromUtf8("mainWidget"))
mainWidget.resize(334, 190)
mainWidget.setMinimumSize(QtCore.QSize(334, 190))
mainWidget.setMaximumSize(QtCore.QSize(334, 190))
self.verticalLayout = QtGui.QVBoxLayout(mainWidget)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.clientTW = QtGui.QTableWidget(mainWidget)
self.clientTW.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) #不可编辑
self.clientTW.setAlternatingRowColors(True) #隔行显示颜色
self.clientTW.setSelectionMode(QtGui.QAbstractItemView.NoSelection) #不可选择
self.clientTW.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)#只能选择一行(已经设置了不可选择,这个设置也不起作用了)
self.clientTW.setShowGrid(False) #消除所有格子线
self.clientTW.setObjectName(_fromUtf8("clientTW"))
self.clientTW.setColumnCount(2) #设置2列
self.clientTW.setRowCount(0) #设置0行,需要再添加
item = QtGui.QTableWidgetItem()
self.clientTW.setHorizontalHeaderItem(0, item) #添加表头项
item = QtGui.QTableWidgetItem()
self.clientTW.setHorizontalHeaderItem(1, item) #添加表头项
self.clientTW.horizontalHeader().setCascadingSectionResizes(False)
self.clientTW.horizontalHeader().setDefaultSectionSize(160) #每一列的默认宽度
self.clientTW.horizontalHeader().setStretchLastSection(True) #填充所有空白(最后一列去填充,不是平均填充)
self.clientTW.verticalHeader().setVisible(False) #消除垂直表头(即消除左侧的行标号)
self.verticalLayout.addWidget(self.clientTW)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.quitPB = QtGui.QPushButton(mainWidget)
self.quitPB.setObjectName(_fromUtf8("quitPB"))
self.horizontalLayout.addWidget(self.quitPB)
self.verticalLayout.addLayout(self.horizontalLayout)

self.retranslateUi(mainWidget)
QtCore.QMetaObject.connectSlotsByName(mainWidget)

def retranslateUi(self, mainWidget):
mainWidget.setWindowTitle(_translate("mainWidget", "Server", None))
item = self.clientTW.horizontalHeaderItem(0)
item.setText(_translate("mainWidget", "客户端地址", None))
item = self.clientTW.horizontalHeaderItem(1)
item.setText(_translate("mainWidget", "客户端监听端口", None))
self.quitPB.setText(_translate("mainWidget", "Quit", None))


if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mainWidget = QtGui.QWidget()
ui = Ui_mainWidget()
ui.setupUi(mainWidget)
mainWidget.show()
sys.exit(app.exec_())

clientUI.py

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
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'clientUI.ui'
#
# Created: Fri Apr 11 17:00:18 2014
# by: PyQt4 UI code generator 4.10.3
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s

try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)

class Ui_mainWidget(object):
def setupUi(self, mainWidget):
mainWidget.setObjectName(_fromUtf8("mainWidget"))
mainWidget.resize(580, 458)
mainWidget.setMinimumSize(QtCore.QSize(580, 458))
mainWidget.setMaximumSize(QtCore.QSize(580, 458))
self.layoutWidget = QtGui.QWidget(mainWidget)
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 562, 443))
self.layoutWidget.setObjectName(_fromUtf8("layoutWidget"))
self.horizontalLayout_2 = QtGui.QHBoxLayout(self.layoutWidget)
self.horizontalLayout_2.setMargin(0)
self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
self.verticalLayout_4 = QtGui.QVBoxLayout()
self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4"))
self.messageGB = QtGui.QGroupBox(self.layoutWidget)
self.messageGB.setMinimumSize(QtCore.QSize(411, 251))
self.messageGB.setMaximumSize(QtCore.QSize(411, 251))
self.messageGB.setObjectName(_fromUtf8("messageGB"))
self.verticalLayout = QtGui.QVBoxLayout(self.messageGB)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.messageTB = QtGui.QTextBrowser(self.messageGB)
self.messageTB.setObjectName(_fromUtf8("messageTB"))
self.verticalLayout.addWidget(self.messageTB)
self.verticalLayout_4.addWidget(self.messageGB)
self.inputGB = QtGui.QGroupBox(self.layoutWidget)
self.inputGB.setMinimumSize(QtCore.QSize(411, 181))
self.inputGB.setMaximumSize(QtCore.QSize(411, 181))
self.inputGB.setObjectName(_fromUtf8("inputGB"))
self.verticalLayout_2 = QtGui.QVBoxLayout(self.inputGB)
self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
self.inputTE = QtGui.QTextEdit(self.inputGB)
self.inputTE.setObjectName(_fromUtf8("inputTE"))
self.verticalLayout_2.addWidget(self.inputTE)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.quitPB = QtGui.QPushButton(self.inputGB)
self.quitPB.setObjectName(_fromUtf8("quitPB"))
self.horizontalLayout.addWidget(self.quitPB)
self.sendPB = QtGui.QPushButton(self.inputGB)
self.sendPB.setEnabled(False)
self.sendPB.setObjectName(_fromUtf8("sendPB"))
self.horizontalLayout.addWidget(self.sendPB)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.verticalLayout_4.addWidget(self.inputGB)
self.horizontalLayout_2.addLayout(self.verticalLayout_4)
self.userGB = QtGui.QGroupBox(self.layoutWidget)
self.userGB.setMinimumSize(QtCore.QSize(141, 441))
self.userGB.setMaximumSize(QtCore.QSize(141, 441))
self.userGB.setObjectName(_fromUtf8("userGB"))
self.verticalLayout_3 = QtGui.QVBoxLayout(self.userGB)
self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3"))
self.userLW = QtGui.QListWidget(self.userGB)
self.userLW.setAlternatingRowColors(True)
self.userLW.setObjectName(_fromUtf8("userLW"))
self.verticalLayout_3.addWidget(self.userLW)
self.horizontalLayout_2.addWidget(self.userGB)

self.retranslateUi(mainWidget)
QtCore.QMetaObject.connectSlotsByName(mainWidget)

def retranslateUi(self, mainWidget):
mainWidget.setWindowTitle(_translate("mainWidget", "Client", None))
self.messageGB.setTitle(_translate("mainWidget", "Message", None))
self.inputGB.setTitle(_translate("mainWidget", "Input", None))
self.quitPB.setText(_translate("mainWidget", "Quit", None))
self.sendPB.setText(_translate("mainWidget", "Send", None))
self.userGB.setTitle(_translate("mainWidget", "User", None))


if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mainWidget = QtGui.QWidget()
ui = Ui_mainWidget()
ui.setupUi(mainWidget)
mainWidget.show()
sys.exit(app.exec_())