网络编程 TCP
一、TCP協(xié)議
1. TCP協(xié)議的特點(diǎn)
1.TCP是面向連接的運(yùn)輸層協(xié)議。這就意味著,在使用該協(xié)議之前,必須建立TCP連接。在傳輸數(shù)據(jù)完畢后,必須釋放已經(jīng)建立的TCP連接。2.每一條TCP連接只能有兩個(gè)端點(diǎn),每一條TCP連接只能是點(diǎn)對(duì)點(diǎn)的(一對(duì)一)。 3.TCP提供可靠交付的服務(wù)。通過TCP連接傳送的數(shù)據(jù),無差錯(cuò)、不丟失、不重復(fù)、并且按序到達(dá)。 4.TCP提供全雙工通信。TCP允許通信雙方的應(yīng)用進(jìn)程在任何時(shí)候都能發(fā)送數(shù)據(jù)。 TCP連接的兩端都設(shè)有發(fā)送緩存和接收緩存,用來臨時(shí)存放雙向通信的數(shù)據(jù)。 在發(fā)送時(shí),應(yīng)用程序在把數(shù)據(jù)傳送給TCP的緩存后,就可以做自己的事,而TCP在合適的時(shí)候把數(shù)據(jù)發(fā)出去。 在接收時(shí),TCP把收到的數(shù)據(jù)放入緩存,上層的應(yīng)用進(jìn)程在合適的時(shí)候讀取緩存中的數(shù)據(jù)。 5.面向字節(jié)流。雖然應(yīng)用程序和TCP的交互試一次一個(gè)數(shù)據(jù)塊(注意:大小是不等的), 但TCP把應(yīng)用程序交下來的數(shù)據(jù)僅僅看成是一連串的無結(jié)構(gòu)的字節(jié)流。TCP并不知道所傳送的字節(jié)流的含義。2.三次握手、四次揮手
3.代碼實(shí)現(xiàn)
3.1 使用TCP協(xié)議實(shí)現(xiàn)簡單通信
3.2 使用TCP協(xié)議實(shí)現(xiàn)通信循環(huán)
# 客戶端: import socketclient = socket.socket() client.connect(('127.0.0.1', 8080)) while True: msg = input(">>>") if msg == 'q': break client.send(msg.encode('utf-8')) data = client.recv(1024) print(data) client.close() # 服務(wù)端 import socket server = socket.socket() # 實(shí)例化sever對(duì)象-->買手機(jī) server.bind(('127.0.0.1', 8080)) # 綁定ip和端口-->手機(jī)卡 server.listen(5) # 監(jiān)聽 -->打開手機(jī) conn, addr = server.accept() # 等待建立連接 while True: try: data = conn.recv(1024) # 接收數(shù)據(jù) --> 打電話 print(data) conn.send(b"received data!!") except ConnectionResetError: break conn.close() # 關(guān)閉連接-->掛電話 server.close() # 關(guān)閉服務(wù)器 -->關(guān)機(jī)3.3 使用TCP協(xié)議實(shí)現(xiàn)鏈接循環(huán)
# 客戶端 import socketclient = socket.socket() client.connect(('127.0.0.1', 8080)) while True: msg = input(">>>") if msg == 'q': break client.send(msg.encode('utf-8')) data = client.recv(1024) print(data) client.close() # 服務(wù)端 import socket server = socket.socket() # 實(shí)例化sever對(duì)象-->買手機(jī) server.bind(('127.0.0.1', 8080)) # 綁定ip和端口-->手機(jī)卡 server.listen(5) # 監(jiān)聽 -->打開手機(jī) (半連接池) while True: conn, addr = server.accept() # 等待建立連接 while True: try: data = conn.recv(1024) # 接收數(shù)據(jù) --> 打電話 print(data.decode('utf-8')) conn.send(b"received data!!") except ConnectionResetError: break conn.close() # 關(guān)閉連接-->掛電話 server.close() # 關(guān)閉服務(wù)器 -->關(guān)機(jī)3.4 使用TCP協(xié)議實(shí)現(xiàn)粘包問題
# 客戶端 import socket import struct import jsonclient = socket.socket() client.connect(('127.0.0.1', 8090)) while True: msg = input(">>>").encode('utf-8') if len(msg) == 0: continue client.send(msg) header = client.recv(4) # 對(duì)頭進(jìn)行解包,獲取真實(shí)數(shù)據(jù)的長度 head_len = struct.unpack('i', header)[0] head_dic = json.loads(client.recv(head_len).decode('utf-8')) print(head_dic) # 對(duì)需要接收的數(shù)據(jù)進(jìn)行循環(huán)接收 total_size = head_dic['len'] recv_size = 0 res = b'' while recv_size < total_size: data = client.recv(1024) res += data recv_size += len(data) print(res.decode("gbk")) # 服務(wù)端 import socket import subprocess import struct import json server = socket.socket() # 實(shí)例化sever對(duì)象-->買手機(jī) server.bind(('127.0.0.1', 8090)) # 綁定ip和端口-->手機(jī)卡 server.listen(5) # 監(jiān)聽 -->打開手機(jī) (半連接池) while True: conn, addr = server.accept() # 等待建立連接 while True: try: data = conn.recv(1024).decode('utf-8') # 接收數(shù)據(jù) --> 打電話 if len(data) == 0: break # 針對(duì)mac和Linux,客戶端異常斷開反復(fù)收空的情況 # 生成一個(gè)能夠讀取輸出、錯(cuò)誤流的對(duì)象 obj = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = obj.stdout.read() # 獲得輸出流 stderr = obj.stderr.read() # 獲取錯(cuò)誤流 # 打印輸出流+輸入流的長度 print(len(stdout+stderr)) # 制作一個(gè)文件頭 header_dic = { 'filename': 'cls.avi', 'len': len(stderr+stdout) # 錯(cuò)誤流 或者 輸出流 的長度 } # 序列化文件頭字典,得到文件頭的二進(jìn)制文件 header_bytes = json.dumps(header_dic).encode('utf-8') # 制作報(bào)頭 header = struct.pack('i', len(header_bytes)) # 將要發(fā)送給客戶端的數(shù)據(jù)打包成固定4個(gè)字節(jié) conn.send(header) conn.send(header_bytes) conn.send(stdout+stderr) except ConnectionResetError: break conn.close() # 關(guān)閉連接-->掛電話 server.close() # 關(guān)閉服務(wù)器 -->關(guān)機(jī)3.5 解決粘包問題用到的 struct
1.說明struct模塊中最重要的三個(gè)函數(shù)是pack(), unpack(), calcsize()pack(fmt, v1, v2, ...) 按照給定的格式(fmt),把數(shù)據(jù)封裝成字符串(實(shí)際上是類似于c結(jié)構(gòu)體的字節(jié)流)unpack(fmt, string) 按照給定的格式(fmt)解析字節(jié)流string,返回解析出來的tuplecalcsize(fmt) 計(jì)算給定的格式(fmt)占用多少字節(jié)的內(nèi)存struct中支持的格式如下表: 網(wǎng)址 : https://www.cnblogs.com/gala/archive/2011/09/22/2184801.html 2.代碼實(shí)現(xiàn)(例子) import struct data = 'datafa' # 服務(wù)端 res = struct.pack('i', len(data)) print(res) # b'\x06\x00\x00\x00' print(len(res)) # 43.6 解決粘包問題用到的 subprocess
1.說明 subprocess 模塊允許你生成新的進(jìn)程,連接它們的輸入、輸出、錯(cuò)誤管道,并且獲取它們的返回碼。class subprocess.Popen(args, bufsize=-1, executable=None,stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None, text=None) 2.代碼實(shí)現(xiàn)(例子): cmd = r'dir file_list' obj = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout = obj.stdout.read() # 來接住stdout流 print('stdout', stdout.decode('gbk')) # 打印進(jìn)程列表 stderr = obj.stderr.read() print('stderr', stderr.decode('gbk')) # 當(dāng)上面的命令出錯(cuò)時(shí),接收錯(cuò)誤流 # stderr 'tasklist1' 不是內(nèi)部或外部命令,也不是可運(yùn)行的程序或批處理文件。 # stdout 和 stderr同時(shí)存在的時(shí)候,有一個(gè)接收到了數(shù)據(jù)另一個(gè)就不會(huì)停止等待二、UDP協(xié)議
1. UDP協(xié)議的特點(diǎn)
1.UDP是無連接的,即發(fā)送數(shù)據(jù)之前不需要建立連接(發(fā)送數(shù)據(jù)結(jié)束時(shí)也沒有連接可釋放),因此減少了開銷和發(fā)送數(shù)據(jù)之前的時(shí)延。2.UDP使用盡最大努力交付,即不保證可靠交付,因此主機(jī)不需要維持復(fù)雜的連接狀態(tài)表。3.UDP是面向報(bào)文的。發(fā)送方的UDP對(duì)應(yīng)用程序交下來的報(bào)文,在添加首部后就向下交付IP層。UDP對(duì)應(yīng)用層交下來的報(bào)文,既不合并,也不拆分,而是保留這些報(bào)文的邊界。 4.UDP沒有擁塞控制,因此網(wǎng)絡(luò)出現(xiàn)的擁塞不會(huì)使源主機(jī)的發(fā)送速率降低。 5.UDP支持一對(duì)一,一對(duì)多,多對(duì)一和多對(duì)多的交互通信。 6.UDP的首部開銷小,只有8個(gè)字節(jié),比TCP的20個(gè)字節(jié)的首部要短。2. 代碼實(shí)現(xiàn)
2.1 使用UDP協(xié)議實(shí)現(xiàn)簡單通信
2.2 使用UDP協(xié)議實(shí)現(xiàn)極簡版QQ
# 客戶端 import socketclient = socket.socket(type=socket.SOCK_DGRAM) server_addr = ('127.0.0.1', 8080)while True: data = input(">>>:").strip() client.sendto(data.encode('utf-8'), server_addr) msg, addr = client.recvfrom(1024) print(msg.decode('utf-8')) # 服務(wù)端 import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1', 8080)) while True: data, addr = server.recvfrom(1024) print(addr) print(data.decode('utf-8')) re_msg = input(">>>:") server.sendto(re_msg.encode('utf-8'), addr)2.3 socketServer模塊可以使TCP通信達(dá)到并發(fā)效果
# 客戶端 import socketclient = socket.socket() client.connect(('127.0.0.1', 8080))while True: msg = input(">>>:") client.send(msg.encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8')) # 服務(wù)端 import socketserver server_addr = ('127.0.0.1', 8080) class MySock(socketserver.BaseRequestHandler): def handle(self): # 通信循環(huán) while True: data = self.request.recv(1024) # 接收數(shù)據(jù) print(data.decode('utf-8')) re_msg = input(">>>:") self.request.send(re_msg.encode('utf-8')) if __name__ == '__main__': server = socketserver.ThreadingTCPServer(server_addr, MySock) server.serve_forever()轉(zhuǎn)載于:https://www.cnblogs.com/wangtenghui/p/10940535.html
總結(jié)
- 上一篇: 基于静态URL的微信分享自定义缩略图及标
- 下一篇: [PKUSC2018]真实排名——线段树