C/S模式
由于網絡課需要實現Socket網絡編程,所以簡單實現了一下,C/S模式分別用TCP/IP協議與UDP協議實現,下面將分別講解。
TCP/IP協議
TCP/IP協議是面向連接的,即客戶端與服務器需要先建立連接后才能傳輸數據,以下是服務器端的代碼實現。
服務端:
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
|
import socket from threading import Thread def deal(sock,addr): print ( 'Accept new connection from {}:{}' . format (addr[ 0 ],addr[ 1 ])) sock.send( '與服務器連接成功!' .encode( 'utf-8' )) while True : data = sock.recv( 1024 ).decode( 'utf-8' ) #1024為接收數據的最大大小 print ( 'receive from {}:{} :{}' . format (addr[ 0 ],addr[ 1 ],data)) sock.send( '信息已成功收到' .encode( 'utf-8' )) ##創建tcp/IPV4協議的socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #為socket綁定端口 s.bind(( '127.0.0.1' , 10240 )) #監聽端口,參數5為等待的最大連接量 s.listen( 5 ) print ( "Waiting for connection..." ) while True : sock,addr = s.accept() t1 = Thread(target = deal,args = (sock,addr)) t1.start() #斷開與該客戶端的連接 sock.close() s.close() |
需要注意的是,服務器在等待客戶端連接時,即accept()函數這里是阻塞的,如下代碼每次只能接受一個客戶端的連接。
1
2
3
4
5
6
7
8
9
10
11
12
|
while True : #接受一個新連接,accept等待并返回一個客戶端連接 sock,addr = s.accept() print ( 'Accept new connection from {}:{}' . format (addr[ 0 ],addr[ 1 ])) #給客戶端發送消息 sock.send( '連接成功!' .encode( 'utf-8' )) while True : data = sock.recv( 1024 ).decode( 'utf-8' ) #1024為接收數據的最大大小 print ( 'receive from {}:{} :{}' . format (addr[ 0 ],addr[ 1 ],data)) sock.send( '信息已成功收到' .encode( 'utf-8' )) #斷開與該客戶端的連接 sock.close() |
也就是說如果采用以上方式,一個客戶端與服務器建立連接后,服務器就會進入一個死循環去收發該客戶端的信息,因此需要引入多線程,每與一個客戶端建立連接,就為其創建一個線程用于控制信息的收發,這樣便可以接受多個客戶端的連接了。
客戶端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ##建立連接 s.connect(( '127.0.0.1' , 10240 )) #接收客戶端連接成功服務器發來的消息 print (s.recv( 1024 ).decode( 'utf-8' )) while True : data = input ( '發送給服務器:' ) if len (data)> 0 : s.send(data.encode( 'utf-8' )) print ( 'form sever:{}' . format (s.recv( 1024 ).decode( 'utf-8' ))) s.close() |
客戶端是比較簡單的,需要與服務器建立連接后,再進行收發信息,這里不再贅述了。
UDP協議
UDP協議是面向無連接的,即服務器與客戶端不需要提前建立連接,只需要向指定的端口直接發送數據即可。
服務端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import socket #為服務器創建socket并綁定端口 SOCK_DGRAM指定了socket的類型為udp s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) s.bind(( '127.0.0.1' , 7890 )) print ( 'Waiting for data...' ) #upd無需監聽 while True : data,addr = s.recvfrom( 1024 ) print ( 'Recevie from {}:{} :{}' . format (addr[ 0 ],addr[ 1 ],data.decode( 'utf-8' ))) #sendto的另一個參數為客戶端socket地址 s.sendto( '信息已成功收到!' .encode( 'utf-8' ),addr) |
客戶端
1
2
3
4
5
6
7
8
9
|
import socket #為服務器創建socket并綁定端口 SOCK_DGRAM指定了socket的類型為udp s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True : data = input ( '發送給服務器:' ) s.sendto(data.encode( 'utf-8' ),( '127.0.0.1' , 7890 )) print ( 'Receive from sever:{}' . format (s.recv( 1024 ).decode( 'utf-8' ))) |
可以看到UDP協議是非常簡單的,由于不需要建立連接,所以也不需要創建線程來管理數據的收發。
C/S模式的應用程序
使用PyQt5對以上的程序進行封裝,這是基于TCP/IP協議實現的。
服務端
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
|
from PyQt5.QtWidgets import (QApplication,QPushButton, QWidget,QLineEdit,QTextEdit) import sys import socket from threading import Thread import datetime class UI(QWidget): def __init__( self ): super ().__init__() self .initUI() def initUI( self ): #控件 self .clear_btn = QPushButton( '清空內容' , self ) self .text = QTextEdit( self ) #布局 self .clear_btn.setGeometry( 150 , 400 , 100 , 40 ) self .text.setGeometry( 20 , 20 , 360 , 370 ) self .text.setReadOnly( True ) #信號連接 self .clear_btn.clicked.connect( self .commit) #初始化socket self .s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ##建立連接 self .s.bind(( '127.0.0.1' , 10240 )) self .s.listen( 5 ) self .text.setText( "Waiting for connection..." ) self .t = Thread(target = self .recv,args = ()) self .t.start() #主窗口布局 self .setGeometry( 300 , 300 , 400 , 450 ) self .setWindowTitle( 'Server' ) self .show() def commit( self ): self .text.clear() def recv( self ): while True : sock,addr = self .s.accept() t1 = Thread(target = self .deal,args = (sock,addr)) t1.start() sock.close() def deal( self ,sock,addr): #sock,addr = s.accept() self .text.append( 'Accept new connection from {}:{}' . format (addr[ 0 ],addr[ 1 ])) sock.send( '與服務器連接成功!' .encode( 'utf-8' )) while True : data = sock.recv( 1024 ).decode( 'utf-8' ) #1024為接收數據的最大大小 self .text.append( '[{}] receive from {}:{} :{}' . format (datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S" ),addr[ 0 ],addr[ 1 ],data)) sock.send( '信息已成功收到' .encode( 'utf-8' )) sock.close() def closeEvent( self ,event): self .s.close() event.accept() if __name__ = = '__main__' : app = QApplication(sys.argv) ex = UI() sys.exit(app.exec_()) |
這里需要注意的是,由于Qt的主程序本身一直處于循環,如果直接阻塞等待客戶端連接會導致程序崩潰,因此需要在Qt初始化時創建一個線程用于等待客戶端的連接,要想同時多個客戶端訪問服務器,還需要在連接成功后再創建一個線程單獨用于接收該客戶端的數據。
客戶端
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
|
from PyQt5.QtWidgets import (QApplication,QPushButton, QWidget,QLineEdit,QTextEdit) import sys import socket from threading import Thread import datetime class UI(QWidget): def __init__( self ): super ().__init__() self .initUI() def initUI( self ): #控件 self .edit = QLineEdit( self ) self .commit_btn = QPushButton( '發送' , self ) self .text = QTextEdit( self ) #布局 self .edit.setGeometry( 20 , 410 , 280 , 30 ) self .commit_btn.setGeometry( 310 , 410 , 70 , 30 ) self .text.setGeometry( 20 , 20 , 360 , 380 ) self .text.setReadOnly( True ) #信號連接 self .commit_btn.clicked.connect( self .commit) #初始化socket self .s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ##建立連接 self .s.connect(( '127.0.0.1' , 10240 )) self .text.setText( '服務器 [{}]:{}\n' . format (datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S" ), self .s.recv( 1024 ).decode( 'utf-8' ))) #主窗口布局 self .setGeometry( 300 , 300 , 400 , 450 ) self .setWindowTitle( 'Client' ) self .show() def commit( self ): if len ( self .edit.text()): text = self .edit.text() self .s.send(text.encode( 'utf-8' )) self .text.append( '本機 [{}]:{}\n' . format (datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S" ),text)) self .text.append( '服務器 [{}]:{}\n' . format (datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S" ), self .s.recv( 1024 ).decode( 'utf-8' ))) self .edit.clear() def closeEvent( self ,event): self .s.close() event.accept() def recv( self ): while True : pass if __name__ = = '__main__' : app = QApplication(sys.argv) ex = UI() sys.exit(app.exec_()) |
客戶端還是比較簡單,不需要創建線程,在發送按紐點擊時觸發事件,向服務器發送數據,并將發送的數據與服務器返回的數據顯示在textEdit上。
P2P模式
老師說P2P模式就是用兩個服務器相互連接通信(我以為是要客戶端發送給服務器,服務器再轉發給另一個客戶端),為了實現方便,直接采用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
|
from PyQt5.QtWidgets import (QApplication,QPushButton, QWidget,QLineEdit,QTextEdit,QLabel) import sys import socket from threading import Thread import datetime class UI(QWidget): def __init__( self ): super ().__init__() self .initUI() def initUI( self ): #控件 self .edit = QLineEdit( self ) self .commit_btn = QPushButton( '發送' , self ) self .text = QTextEdit( self ) self .host_label = QLabel( 'ip地址:' , self ) self .host = QLineEdit( self ) self .dst_port_label = QLabel( '目標端口:' , self ) self .dst_port_edit = QLineEdit( self ) self .src_port_label = QLabel( '本機端口:' , self ) self .src_port_edit = QLineEdit( self ) self .que_ren_btn = QPushButton( '確認' , self ) #self.host_label.setStyleSheet("QLabel{font-size:25px}") #self.dst_port_label.setStyleSheet("QLabel{font-size:25px}") #self.src_port_label.setStyleSheet("QLabel{font-size:25px}") #布局 self .edit.setGeometry( 20 , 480 , 280 , 30 ) self .commit_btn.setGeometry( 310 , 480 , 70 , 30 ) self .text.setGeometry( 20 , 90 , 360 , 380 ) self .host_label.setGeometry( 20 , 20 , 65 , 25 ) self .host.setGeometry( 90 , 20 , 110 , 25 ) self .dst_port_label.setGeometry( 205 , 20 , 65 , 25 ) self .dst_port_edit.setGeometry( 275 , 20 , 110 , 25 ) self .src_port_label.setGeometry( 20 , 55 , 65 , 25 ) self .src_port_edit.setGeometry( 90 , 55 , 110 , 25 ) self .que_ren_btn.setGeometry( 205 , 55 , 70 , 25 ) self .text.setReadOnly( True ) #信號連接 self .commit_btn.clicked.connect( self .commit) self .que_ren_btn.clicked.connect( self .que_ren) #初始化socket self .s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #主窗口布局 self .setGeometry( 300 , 300 , 400 , 520 ) self .setWindowTitle( 'Client' ) self .show() def commit( self ): if len ( self .edit.text()): text = self .edit.text() self .s.sendto(text.encode( 'utf-8' ),( '127.0.0.1' , self .dst_port)) self .text.append( '本機 [{}]:\n{}\n' . format (datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S" ),text)) self .edit.clear() def closeEvent( self ,event): self .s.close() event.accept() def recv( self ): while True : data,addr = self .s.recvfrom( 1024 ) self .text.append( '{}:{}[{}]:\n{}\n' . format (addr[ 0 ],addr[ 1 ],datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S" ),data.decode( 'utf-8' ))) def que_ren( self ): self .src_port = int ( self .src_port_edit.text()) self .dst_port = int ( self .dst_port_edit.text()) #綁定ip地址與端口 self .s.bind(( '127.0.0.1' , self .src_port)) #開啟接收消息的線程 self .t = Thread(target = self .recv,args = ()) self .t.start() if __name__ = = '__main__' : app = QApplication(sys.argv) ex = UI() sys.exit(app.exec_()) |
首先需要輸入要傳送信息的IP地址,以及端口號,以及設置自己的端口號(IP地址沒有用到,我設置了是127.0.0.1),點擊確定按鈕時觸發事件,會為socket綁定端口號,并且創建一個用于接收消息的線程,在點擊發送按鈕時會觸發另一個事件用于發送消息,發送與接收的消息最后會顯示在TextEdit上。
注意
這里要統一說明一下,在使用Qt封裝后程序會一直循環運行,導致關閉程序時socket也沒有關閉(因為我也剛學,不清楚不關閉的后果,可能會占用這個端口一段時間吧),因此需要重寫Qt的closeEvent函數,在該函數中進行關閉。
總結
到此這篇關于python Socket網絡編程實現C/S模式和P2P的文章就介紹到這了,更多相關python Socket C/S模式和P2P內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/asfsfsdc/article/details/106872717