socket层内容详解二
生活随笔
收集整理的這篇文章主要介紹了
socket层内容详解二
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
?
tcp協(xié)議三次握手和四次揮手
所有的斷開都是單方面的
粘包現(xiàn)象
為什么會(huì)出現(xiàn)粘包現(xiàn)象:
本質(zhì):接收端不知道發(fā)送端發(fā)送的數(shù)據(jù)長度是多少
tcp協(xié)議本身的特點(diǎn)導(dǎo)致:
流式傳輸、無邊界
合包機(jī)制
緩存機(jī)制
拆包機(jī)制
如何解決這個(gè)問題?
自定義協(xié)議:先發(fā)送要傳遞數(shù)據(jù)的長度
用戶登錄驗(yàn)證協(xié)議
# 先創(chuàng)建一個(gè)文件,里面隨便放幾個(gè)用戶名及對(duì)應(yīng)的密文密碼,如:# userinfoalex|802165d8e6a7e0fbf11b0feca3913271 jane|48610593feea04340acf643a71621e32
?
# server.pyimport socket import json import hashlibsk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen()def get_md5_code(usr, pwd):md5 = hashlib.md5(usr.encode())md5.update(pwd.encode())return md5.hexdigest()conn,addr = sk.accept() msg = conn.recv(1024).decode() dic_msg = json.loads(msg)with open("userinfo") as f:for line in f:usr,pwd = line.strip().split("|")if usr == dic_msg["username"] and pwd == get_md5_code(dic_msg["username"], dic_msg["password"]):ret = {"code": 1}res_msg = json.dumps(ret).encode()conn.send(res_msg)breakelse:ret = {"code": 0}res_msg = json.dumps(ret).encode()conn.send(res_msg)conn.close() sk.close()?
# client.pyimport socket import jsonusr = input("username: ") pwd = input("password: ") dic = {"username": usr, "password": pwd, "operate": "login"} json_dic = json.dumps(dic) bytes_msg = json_dic.encode()sk = socket.socket() sk.connect(("127.0.0.1", 8080)) # 為什么要放在這里? # 因?yàn)楸热缬械挠脩舸蜷_客戶端要輸入用戶名的時(shí)候想了半天還沒想出來 # 但是客戶端卻一直開著,所以為了避免這種情況把上面兩行放這里 sk.send(bytes_msg)recv_msg = sk.recv(1024).decode() print(recv_msg) dic_code = json.loads(recv_msg) if dic_code["code"]:print("登錄成功") else:print("登錄失敗")sk.close()?
?
如何讓服務(wù)端與一個(gè)客戶端聊天時(shí),客戶端退出后,服務(wù)端還能繼續(xù)等待下一個(gè)客戶端聊天?# server.pyimport socketsk = socket.socket()sk.bind(('127.0.0.1',9000)) sk.listen()while True: # 和n個(gè)人聊天conn,addr = sk.accept()while True: # 和一個(gè)人聊n久send_msg = input('msg : ')conn.send(send_msg.encode())if send_msg == 'q':breakmsg = conn.recv(1024).decode()if msg == 'q':breakprint(msg)conn.close()sk.close()
?
# client.pyimport socketsk = socket.socket() sk.connect(('127.0.0.1',9000))while True:msg = sk.recv(1024).decode()if msg == 'q':breakprint(msg)send_msg = input('msg : ')sk.send(send_msg.encode())if send_msg == 'q':breaksk.close()# 這里注意,客戶端輸入q退出程序后,會(huì)發(fā)現(xiàn)服務(wù)端沒有關(guān)閉 # 這時(shí)只需重啟客戶端,相當(dāng)于另一個(gè)用戶進(jìn)入,就可繼續(xù)聊天?
?
完成一個(gè)上傳文件的程序
?
# server.pyimport struct import socket import jsonsk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen()conn, addr = sk.accept() bytes_len = conn.recv(4) info_len = struct.unpack("i", bytes_len)[0] json_info = conn.recv(info_len).decode() info_dic = json.loads(json_info) print(info_dic) with open(info_dic["filename"], "wb") as f:# 這里有問題,如果把這里的1024和client.py里的1024都改為2048# 發(fā)現(xiàn)運(yùn)行結(jié)果后查看該文件時(shí)文件大小變小了# 原因是這里雖然寫conn.recv(1024)# 但不代表每次一定就能接收到1024個(gè)字節(jié)# 比如最后還剩800多字節(jié),就會(huì)發(fā)生拆包現(xiàn)象# 也就是說,雖然寫的是接收1024,其實(shí)最后只有800多# 這樣就要把info_dic["filesize"] -= 1024的1024改掉while info_dic["filesize"]:content = conn.recv(1024)f.write(content)# info_dic["filesize"] -= 1024info_dic["filesize"] -= len(content)conn.close() sk.close()?
import socket import os import json import struct# 在上傳之前,應(yīng)該先向server端傳遞文件信息:文件大小、文件名、以及要做的操作,這里就要用到send,但是可能會(huì)與下面的send產(chǎn)生粘包現(xiàn)象 # 打開文件 # 讀文件 # 按照字節(jié)讀取 # 5000:1024 1024 1024 1024 904 # 這樣就要send五次,不用擔(dān)心粘包,只要傳過去5000字節(jié)就行# server端先接收4字節(jié) # 然后直到了文件信息的長度 # 按照長度接收文件信息 # 從文件信息中得到文件的大小 # 就知道要收取的文件的大小是多少 # 開始接收,直到收完文件大小這么多的數(shù)據(jù)# 在上傳之前,應(yīng)該先向server端傳遞文件信息:文件大小、文件名、以及要做的操作 file_path = r"G:\day01 視頻以及筆記\20181219_163449.mp4" # input("file_path") filesize = os.path.getsize(file_path) filename = os.path.basename(file_path) # print(filesize) # 143799118 # print(filename) # 20181219_163449.mp4 file_info = {"filesize": filesize, "filename": filename, "operate": "upload"} json_info = json.dumps(file_info) file_info_bytes = json_info.encode() bytes_len = len(file_info_bytes) sk = socket.socket() sk.connect(("127.0.0.1", 8080)) # 先發(fā)送文件信息的長度,再發(fā)送文件信息 sk.send(struct.pack("i", bytes_len)) sk.send(file_info_bytes)with open(file_path, "rb") as f:while filesize > 0:content = f.read(1024)filesize -= len(content)sk.send(content) # 這里已經(jīng)是bytes類型 sk.close()?
?
udp協(xié)議
?
# server.pyimport socketsk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(("127.0.0.1", 8080)) # 這里不用寫sk.listen(),沒有三次握手 msg, client_addr = sk.recvfrom(1024) print(msg) sk.sendto(b"hello", client_addr)sk.close()?
# client.pyimport socketsk = socket.socket(type=socket.SOCK_DGRAM)sk.sendto(b"hello", ("127.0.0.1", 8080)) msg, addr = sk.recvfrom(1024) print(msg)sk.close()?
?
使用udp協(xié)議實(shí)現(xiàn)多人聊天功能
?
# server.pyimport socketsk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(("127.0.0.1", 8080)) # 這里不用寫sk.listen(),沒有三次握手# 和一個(gè)人多次聊天 while True:msg, client_addr = sk.recvfrom(1024)print(msg.decode())content = input(">>>")sk.sendto(content.encode(), client_addr)sk.close()?
# client.pyimport socketsk = socket.socket(type=socket.SOCK_DGRAM)# 和一個(gè)人多次聊天 # 可以再創(chuàng)建一個(gè)client.py,內(nèi)容一模一樣,然后依次運(yùn)行 # 都可以跟server端聊天 while True:content = input(">>>")# 多人聊天時(shí),可以這樣區(qū)分誰跟server端聊天content = "%s : %s" % ("alex", content)sk.sendto(content.encode(), ("127.0.0.1", 8080))msg, addr = sk.recvfrom(1024)print(msg.decode())sk.close()?
?
使用socketserver來實(shí)現(xiàn)tcp協(xié)議socket的并發(fā)
?
# server.pyimport socketserver# socketserver是一個(gè)上層模塊,socket是一個(gè)下層模塊 # 因此 socketserver 可以實(shí)現(xiàn)并發(fā) # socket是基于socketserver的下層模塊 # socketserver是基于socket的上層模塊class Myserver(socketserver.BaseRequestHandler):# 注意:有多少個(gè)client端,就相當(dāng)于這里有多少個(gè)handledef handle(self):# print("連接上了")# print(self.request) # 這個(gè)就是conn,因此這樣寫# conn = self.request# conn.send(b"hello")# 可以與多個(gè)client端運(yùn)行,即并發(fā)while True:conn = self.requestconn.send(b"hello")# 這里不需要實(shí)例化一個(gè)對(duì)象 server = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), Myserver) server.serve_forever()# socketserver
# 寫一個(gè)socket的server端,能夠同時(shí)和多個(gè)client端進(jìn)行通信,互不干擾
# socketserver的server端不能用input,因?yàn)?span style="color:#75715e;">server端使用了并發(fā)機(jī)制
# 如何寫?
# 1.必須繼承socketserver.BaseRequestHandler
# 2.自定義一個(gè)類必須實(shí)現(xiàn)handler方法
# 3.自定義類中的self.request就是conn
# 4.啟動(dòng)程序的方法:
# socket.server.ThreadingTCPServer(ip, port),自定義類名)
# obj.server_forever()
?
# client.pyimport socketsk = socket.socket() sk.connect(("127.0.0.1", 8080))while True:msg = sk.recv(1024)print(msg)sk.close()?
# client1.pyimport socketsk = socket.socket() sk.connect(("127.0.0.1", 8080))while True:msg = sk.recv(1024)print(msg)sk.close()?
# client2.pyimport socketsk = socket.socket() sk.connect(("127.0.0.1", 8080))while True:msg = sk.recv(1024)print(msg)sk.close()?
1.兩個(gè)連續(xù)的send會(huì)產(chǎn)生粘包現(xiàn)象 2.用struct自定義協(xié)議可以解決粘包問題 3.文件傳輸時(shí)不用考慮粘包問題 4.自定義協(xié)議的進(jìn)階版本:先發(fā)送字符串的長度,再發(fā)送字符串先發(fā)送json的長度,再發(fā)送jsonjson的字典中包含著下一條信息的長度,然后按照長度接收tcp與udp協(xié)議 tcp面向連接的,可靠的,全雙工的,流式傳輸面向連接:同一時(shí)刻只能和一個(gè)客戶端通信要進(jìn)行三次握手,四次揮手可靠的:數(shù)據(jù)不丟失、慢全雙工:能夠雙向通信(send,recv誰先都可以)流式傳輸:粘包、無邊界 udp無連接的,面向數(shù)據(jù)包,不可靠的,快速無連接的:不需要accept/connect, 也沒有握手面向數(shù)據(jù)包的:不會(huì)粘包不可靠的:沒有自動(dòng)回復(fù)的機(jī)制快速的:沒有復(fù)雜的計(jì)算、保證數(shù)據(jù)傳輸?shù)臋C(jī)制?
# 再講 pickle 模塊import pickleclass Course:def __init__(self, name, period, price, teacher):self.name = nameself.period = periodself.price = priceself.teacher = teacherpython = Course("python", "six months", 19800, "alex") linux = Course("linux", "five months", 15800, "taibai") with open("course_info", "wb") as f:pickle.dump(python, f)pickle.dump(linux, f)with open("course_info", "rb") as f:while 1:try:# print(pickle.load(f).name) # python linuxobj = pickle.load(f)print(obj.__dict__)# {'name': 'python', 'period': 'six months',# 'price': 19800, 'teacher': 'alex'}# {'name': 'linux', 'period': 'five months',# 'price': 15800, 'teacher': 'taibai'}except EOFError:break# dumps str-->bytes # dump str-->文件里的bytes# 1.json能處理的數(shù)據(jù)類型有限,但是所有語言通用 # pickle支持python中幾乎所有對(duì)象,但是只能在python語言中使用 # 2.json的dumps的結(jié)果是str, pickle的dumps的結(jié)果是bytes # 3.json不能連續(xù)dump多個(gè)數(shù)據(jù)到文件中 # pickle可以?
# 驗(yàn)證客戶端的合法性# server.pyimport os import hmac import socketdef auth(conn):secret_key = b"jane"rand_b = os.urandom(32)conn.send(rand_b)obj = hmac.new(secret_key, rand_b)res1 = obj.digest()res2 = conn.recv(1024)cmp_res = hmac.compare_digest(res1, res2)return cmp_ressk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen()conn, addr = sk.accept() res = auth(conn) if res:print("是合法的客戶端")conn.send("您好".encode()) else:conn.close()sk.close()# 運(yùn)行后得出結(jié)果:是合法的客戶端 # 只要兩邊的secret_key的值不一樣,服務(wù)端就知道不是合法的客戶端?
# client.pyimport socket import hmacdef auth(sk):secret_key = b"jane"rand_b = sk.recv(32) # 注意這里obj = hmac.new(secret_key, rand_b)res2 = obj.digest()sk.send(res2)sk = socket.socket() sk.connect(("127.0.0.1", 8080)) auth(sk) msg = sk.recv(1024) print(msg.decode())sk.close()?
轉(zhuǎn)載于:https://www.cnblogs.com/shawnhuang/p/10309089.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的socket层内容详解二的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 常见的跑马灯效果,无缝连接。mcake官
- 下一篇: 模拟问路场景理解递归