day28 socket网络编程
?
一? socket 套接字
二? 粘包問題
?
一? socket 套接字
1.1 為何學(xué)習(xí)socket一定要先學(xué)習(xí)互聯(lián)網(wǎng)協(xié)議:
1.首先:網(wǎng)絡(luò)編程目標(biāo)就是教會你如何基于socket編程,來開發(fā)一款自己的C/S架構(gòu)軟件
2.其次:C/S架構(gòu)的軟件(軟件屬于應(yīng)用層)是基于網(wǎng)絡(luò)進(jìn)行通信的
3.然后:網(wǎng)絡(luò)的核心即一堆協(xié)議,協(xié)議即標(biāo)準(zhǔn),你想開發(fā)一款基于網(wǎng)絡(luò)通信的軟件,就必須遵循這些標(biāo)準(zhǔn)。
4.最后:從這些標(biāo)準(zhǔn)開始研究,開啟socket編程
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
1.2 socket層
在上圖中,我們沒有看到Socket,我們用下圖來繼續(xù)說明.?
?
1.3 socket是什么
Socket是應(yīng)用層與TCP/IP協(xié)議通信的中間軟件抽象層,它是一組接口。在設(shè)計(jì)模式中,Socket其實(shí)就是一個(gè)門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。
所以,我們無需深入理解tcp/udp協(xié)議,socket已經(jīng)為我們封裝好了,我們只需要遵循socket的規(guī)定去編程,寫出的程序自然就是遵循tcp/udp標(biāo)準(zhǔn)的。
1.4? 套接字發(fā)展史及分類
套接字起源于 20 世紀(jì) 70 年代加利福尼亞大學(xué)伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時(shí)人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設(shè)計(jì)用在同 一臺主機(jī)上多個(gè)應(yīng)用程序之間的通訊。這也被稱進(jìn)程間通訊,或 IPC。套接字有兩種(或者稱為有兩個(gè)種族),分別是基于文件型的和基于網(wǎng)絡(luò)型的。?
基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調(diào)用的就是底層的文件系統(tǒng)來取數(shù)據(jù),兩個(gè)套接字進(jìn)程運(yùn)行在同一機(jī)器,可以通過訪問同一個(gè)文件系統(tǒng)間接完成通信
基于網(wǎng)絡(luò)類型的套接字家族套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個(gè)平臺,要么就是已經(jīng)被廢棄,或者是很少被使用,或者是根本沒有實(shí)現(xiàn),所有地址家族中,AF_INET是使用最廣泛的一個(gè),python支持很多種地址家族,但是由于我們只關(guān)心網(wǎng)絡(luò)編程,所以大部分時(shí)候我么只使用AF_INET)
1.5 套接字工作流程
???????一個(gè)生活中的場景。你要打電話給一個(gè)朋友,先撥號,朋友聽到電話鈴聲后提起電話,這時(shí)你和你的朋友就建立起了連接,就可以講話了。等交流結(jié)束,掛斷電話結(jié)束此次交談。?生活中的場景就解釋了這工作原理。
? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖3 ? ? ??
先從服務(wù)器端說起。服務(wù)器端先初始化Socket,然后與端口綁定(bind),對端口進(jìn)行監(jiān)聽(listen),調(diào)用accept阻塞,等待客戶端連接。在這時(shí)如果有個(gè)客戶端初始化一個(gè)Socket,然后連接服務(wù)器(connect),如果連接成功,這時(shí)客戶端與服務(wù)器端的連接就建立了。客戶端發(fā)送數(shù)據(jù)請求,服務(wù)器端接收請求并處理請求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端,客戶端讀取數(shù)據(jù),最后關(guān)閉連接,一次交互結(jié)束
socket()模塊函數(shù)用法
1 import socket2 socket.socket(socket_family,socket_type,protocal=0)3 socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默認(rèn)值為 0。4 5 獲取tcp/ip套接字6 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)7 8 獲取udp/ip套接字9 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 10 11 由于 socket 模塊中有太多的屬性。我們在這里破例使用了'from module import *'語句。使用 'from socket import *',我們就把 socket 模塊里的所有屬性都帶到我們的命名空間里了,這樣能 大幅減短我們的代碼。 12 例如tcpSock = socket(AF_INET, SOCK_STREAM)?
服務(wù)端套接字函數(shù)s.bind() 綁定(主機(jī),端口號)到套接字
s.listen() 開始TCP監(jiān)聽
s.accept() 被動接受TCP客戶的連接,(阻塞式)等待連接的到來
客戶端套接字函數(shù)
s.connect() 主動初始化TCP服務(wù)器連接
s.connect_ex() connect()函數(shù)的擴(kuò)展版本,出錯時(shí)返回出錯碼,而不是拋出異常
公共用途的套接字函數(shù)
s.recv() 接收TCP數(shù)據(jù)
s.send() 發(fā)送TCP數(shù)據(jù)(send在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時(shí),數(shù)據(jù)丟失,不會發(fā)完)
s.sendall() 發(fā)送完整的TCP數(shù)據(jù)(本質(zhì)就是循環(huán)調(diào)用send,sendall在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時(shí),數(shù)據(jù)不丟失,循環(huán)調(diào)用send直到發(fā)完)
s.recvfrom() 接收UDP數(shù)據(jù)
s.sendto() 發(fā)送UDP數(shù)據(jù)
s.getpeername() 連接到當(dāng)前套接字的遠(yuǎn)端的地址
s.getsockname() 當(dāng)前套接字的地址
s.getsockopt() 返回指定套接字的參數(shù)
s.setsockopt() 設(shè)置指定套接字的參數(shù)
s.close() 關(guān)閉套接字
面向鎖的套接字方法
s.setblocking() 設(shè)置套接字的阻塞與非阻塞模式
s.settimeout() 設(shè)置阻塞套接字操作的超時(shí)時(shí)間
s.gettimeout() 得到阻塞套接字操作的超時(shí)時(shí)間
面向文件的套接字的函數(shù)
s.fileno() 套接字的文件描述符
s.makefile() 創(chuàng)建一個(gè)與該套接字相關(guān)的文件
?讀者勿看:socket實(shí)驗(yàn)推演流程
1.6 基于TCP的套接字
tcp是基于鏈接的,必須先啟動服務(wù)端,然后再啟動客戶端去鏈接服務(wù)端
tcp服務(wù)端
1 ss = socket() #創(chuàng)建服務(wù)器套接字 2 ss.bind() #把地址綁定到套接字 3 ss.listen() #監(jiān)聽鏈接 4 inf_loop: #服務(wù)器無限循環(huán) 5 cs = ss.accept() #接受客戶端鏈接 6 comm_loop: #通訊循環(huán) 7 cs.recv()/cs.send() #對話(接收與發(fā)送) 8 cs.close() #關(guān)閉客戶端套接字 9 ss.close() #關(guān)閉服務(wù)器套接字(可選)?
tcp客戶端
1 cs = socket() # 創(chuàng)建客戶套接字 2 cs.connect() # 嘗試連接服務(wù)器 3 comm_loop: # 通訊循環(huán) 4 cs.send()/cs.recv() # 對話(發(fā)送/接收) 5 cs.close() # 關(guān)閉客戶套接字?
?
socket通信流程與打電話流程類似,我們就以打電話為例來實(shí)現(xiàn)一個(gè)low版的套接字通信
?服務(wù)端 ?客戶端加上鏈接循環(huán)與通信循環(huán)
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' import socket ip_port=('127.0.0.1',8081)#電話卡 BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機(jī) s.bind(ip_port) #手機(jī)插卡 s.listen(5) #手機(jī)待機(jī)while True: #新增接收鏈接循環(huán),可以不停的接電話conn,addr=s.accept() #手機(jī)接電話# print(conn)# print(addr)print('接到來自%s的電話' %addr[0])while True: #新增通信循環(huán),可以不斷的通信,收發(fā)消息msg=conn.recv(BUFSIZE) #聽消息,聽話# if len(msg) == 0:break #如果不加,那么正在鏈接的客戶端突然斷開,recv便不再阻塞,死循環(huán)發(fā)生print(msg,type(msg))conn.send(msg.upper()) #發(fā)消息,說話conn.close() #掛電話s.close() #手機(jī)關(guān)機(jī) ?客戶端改進(jìn)版?
問題:在重啟服務(wù)端時(shí)可能會遇到
這個(gè)是由于你的服務(wù)端仍然存在四次揮手的time_wait狀態(tài)在占用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務(wù)器高并發(fā)情況下會有大量的time_wait狀態(tài)的優(yōu)化方法)
解決方法:
?方法一 ?方法二 1.7?基于UDP的套接字udp是無鏈接的,先啟動哪一端都不會報(bào)錯
udp服務(wù)端
1 ss = socket() #創(chuàng)建一個(gè)服務(wù)器的套接字 2 ss.bind() #綁定服務(wù)器套接字 3 inf_loop: #服務(wù)器無限循環(huán) 4 cs = ss.recvfrom()/ss.sendto() # 對話(接收與發(fā)送) 5 ss.close() # 關(guān)閉服務(wù)器套接字?
udp客戶端
cs = socket() # 創(chuàng)建客戶套接字 comm_loop: # 通訊循環(huán)cs.sendto()/cs.recvfrom() # 對話(發(fā)送/接收) cs.close() # 關(guān)閉客戶套接字?
udp套接字簡單示例
?udp服務(wù)端 ?udp客戶端qq聊天(由于udp無連接,所以可以同時(shí)多個(gè)客戶端去跟服務(wù)端通信)
?udp服務(wù)端 ?udp客戶端1 ?udp客戶端2服務(wù)端運(yùn)行結(jié)果
客戶端1運(yùn)行結(jié)果
客戶端2運(yùn)行結(jié)果
時(shí)間服務(wù)器
?ntp服務(wù)端 ?ntp客戶端九 粘包現(xiàn)象
讓我們基于tcp先制作一個(gè)遠(yuǎn)程執(zhí)行命令的程序(1:執(zhí)行錯誤命令 2:執(zhí)行l(wèi)s 3:執(zhí)行ifconfig)
注意注意注意:
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
的結(jié)果的編碼是以當(dāng)前所在的系統(tǒng)為準(zhǔn)的,如果是windows,那么res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼
且只能從管道里讀一次結(jié)果
注意:命令ls -l ; lllllll ; pwd 的結(jié)果是既有正確stdout結(jié)果,又有錯誤stderr結(jié)果
?服務(wù)端 ?客戶端上述程序是基于tcp的socket,在運(yùn)行時(shí)會發(fā)生粘包
?
讓我們再基于udp制作一個(gè)遠(yuǎn)程執(zhí)行命令的程序
?服務(wù)端 ?客戶端上述程序是基于udp的socket,在運(yùn)行時(shí)永遠(yuǎn)不會發(fā)生粘包
十 什么是粘包
須知:只有TCP有粘包現(xiàn)象,UDP永遠(yuǎn)不會粘包,為何,且聽我娓娓道來
首先需要掌握一個(gè)socket收發(fā)消息的原理
?
發(fā)送端可以是一K一K地發(fā)送數(shù)據(jù),而接收端的應(yīng)用程序可以兩K兩K地提走數(shù)據(jù),當(dāng)然也有可能一次提走3K或6K數(shù)據(jù),或者一次只提走幾個(gè)字節(jié)的數(shù)據(jù),也就是說,應(yīng)用程序所看到的數(shù)據(jù)是一個(gè)整體,或說是一個(gè)流(stream),一條消息有多少字節(jié)對應(yīng)用程序是不可見的,因此TCP協(xié)議是面向流的協(xié)議,這也是容易出現(xiàn)粘包問題的原因。而UDP是面向消息的協(xié)議,每個(gè)UDP段都是一條消息,應(yīng)用程序必須以消息為單位提取數(shù)據(jù),不能一次提取任意字節(jié)的數(shù)據(jù),這一點(diǎn)和TCP是很不同的。怎樣定義消息呢?可以認(rèn)為對方一次性write/send的數(shù)據(jù)為一個(gè)消息,需要明白的是當(dāng)對方send一條信息的時(shí)候,無論底層怎樣分段分片,TCP協(xié)議層會把構(gòu)成整條消息的數(shù)據(jù)段排序完成后才呈現(xiàn)在內(nèi)核緩沖區(qū)。
例如基于tcp的套接字客戶端往服務(wù)端上傳文件,發(fā)送時(shí)文件內(nèi)容是按照一段一段的字節(jié)流發(fā)送的,在接收方看了,根本不知道該文件的字節(jié)流從何處開始,在何處結(jié)束
所謂粘包問題主要還是因?yàn)榻邮辗讲恢老⒅g的界限,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的。
此外,發(fā)送方引起的粘包是由TCP協(xié)議本身造成的,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一個(gè)TCP段。若連續(xù)幾次需要send的數(shù)據(jù)都很少,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個(gè)TCP段后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)。
udp的recvfrom是阻塞的,一個(gè)recvfrom(x)必須對唯一一個(gè)sendinto(y),收完了x個(gè)字節(jié)的數(shù)據(jù)就算完成,若是y>x數(shù)據(jù)就丟失,這意味著udp根本不會粘包,但是會丟數(shù)據(jù),不可靠
tcp的協(xié)議數(shù)據(jù)不會丟,沒有收完包,下次接收,會繼續(xù)上次繼續(xù)接收,己端總是在收到ack時(shí)才會清除緩沖區(qū)內(nèi)容。數(shù)據(jù)是可靠的,但是會粘包。
兩種情況下會發(fā)生粘包。
發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去,造成粘包(發(fā)送數(shù)據(jù)時(shí)間間隔很短,數(shù)據(jù)了很小,會合到一起,產(chǎn)生粘包)
?服務(wù)端 ?客戶端接收方不及時(shí)接收緩沖區(qū)的包,造成多個(gè)包接收(客戶端發(fā)送了一段數(shù)據(jù),服務(wù)端只收了一小部分,服務(wù)端下次再收的時(shí)候還是從緩沖區(qū)拿上次遺留的數(shù)據(jù),產(chǎn)生粘包)?
?服務(wù)端 ?客戶端?
拆包的發(fā)生情況
當(dāng)發(fā)送端緩沖區(qū)的長度大于網(wǎng)卡的MTU時(shí),tcp會將這次發(fā)送的數(shù)據(jù)拆成幾個(gè)數(shù)據(jù)包發(fā)送出去。
補(bǔ)充問題一:為何tcp是可靠傳輸,udp是不可靠傳輸
基于tcp的數(shù)據(jù)傳輸請參考我的另一篇文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,tcp在數(shù)據(jù)傳輸時(shí),發(fā)送端先把數(shù)據(jù)發(fā)送到自己的緩存中,然后協(xié)議控制將緩存中的數(shù)據(jù)發(fā)往對端,對端返回一個(gè)ack=1,發(fā)送端則清理緩存中的數(shù)據(jù),對端返回ack=0,則重新發(fā)送數(shù)據(jù),所以tcp是可靠的
而udp發(fā)送數(shù)據(jù),對端是不會返回確認(rèn)信息的,因此不可靠
補(bǔ)充問題二:send(字節(jié)流)和recv(1024)及sendall
recv里指定的1024意思是從緩存里一次拿出1024個(gè)字節(jié)的數(shù)據(jù)
send的字節(jié)流是先放入己端緩存,然后由協(xié)議控制將緩存內(nèi)容發(fā)往對端,如果待發(fā)送的字節(jié)流大小大于緩存剩余空間,那么數(shù)據(jù)丟失,用sendall就會循環(huán)調(diào)用send,數(shù)據(jù)不會丟失
十一 解決粘包的low比處理方法
問題的根源在于,接收端不知道發(fā)送端將要傳送的字節(jié)流的長度,所以解決粘包的方法就是圍繞,如何讓發(fā)送端在發(fā)送數(shù)據(jù)前,把自己將要發(fā)送的字節(jié)流總大小讓接收端知曉,然后接收端來一個(gè)死循環(huán)接收完所有數(shù)據(jù)
low版本的解決方法
?服務(wù)端 ?客戶端為何low:
程序的運(yùn)行速度遠(yuǎn)快于網(wǎng)絡(luò)傳輸速度,所以在發(fā)送一段字節(jié)前,先用send去發(fā)送該字節(jié)流長度,這種方式會放大網(wǎng)絡(luò)延遲帶來的性能損耗
十二 峰哥解決粘包的方法
為字節(jié)流加上自定義固定長度報(bào)頭,報(bào)頭中包含字節(jié)流長度,然后一次send到對端,對端在接收時(shí),先從緩存中取出定長的報(bào)頭,然后再取真實(shí)數(shù)據(jù)
struct模塊?
該模塊可以把一個(gè)類型,如數(shù)字,轉(zhuǎn)成固定長度的bytes
>>> struct.pack('i',1111111111111)
。。。。。。。。。
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個(gè)是范圍
?
import json,struct #假設(shè)通過客戶端上傳1T:1073741824000的文件a.txt#為避免粘包,必須自定制報(bào)頭 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數(shù)據(jù),文件路徑和md5值#為了該報(bào)頭能傳送,需要序列化并且轉(zhuǎn)為bytes head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并轉(zhuǎn)成bytes,用于傳輸#為了讓客戶端知道報(bào)頭的長度,用struck將報(bào)頭長度這個(gè)數(shù)字轉(zhuǎn)成固定長度:4個(gè)字節(jié) head_len_bytes=struct.pack('i',len(head_bytes)) #這4個(gè)字節(jié)里只包含了一個(gè)數(shù)字,該數(shù)字是報(bào)頭的長度#客戶端開始發(fā)送 conn.send(head_len_bytes) #先發(fā)報(bào)頭的長度,4個(gè)bytes conn.send(head_bytes) #再發(fā)報(bào)頭的字節(jié)格式 conn.sendall(文件內(nèi)容) #然后發(fā)真實(shí)內(nèi)容的字節(jié)格式#服務(wù)端開始接收 head_len_bytes=s.recv(4) #先收報(bào)頭4個(gè)bytes,得到報(bào)頭長度的字節(jié)格式 x=struct.unpack('i',head_len_bytes)[0] #提取報(bào)頭的長度head_bytes=s.recv(x) #按照報(bào)頭長度x,收取報(bào)頭的bytes格式 header=json.loads(json.dumps(header)) #提取報(bào)頭#最后根據(jù)報(bào)頭的內(nèi)容提取真實(shí)的數(shù)據(jù),比如 real_data_len=s.recv(header['file_size']) s.recv(real_data_len) ?關(guān)于struct的詳細(xì)用法?
?服務(wù)端(自定制報(bào)頭) ?客戶端(自定制報(bào)頭)我們可以把報(bào)頭做成字典,字典里包含將要發(fā)送的真實(shí)數(shù)據(jù)的詳細(xì)信息,然后json序列化,然后用struck將序列化后的數(shù)據(jù)長度打包成4個(gè)字節(jié)(4個(gè)自己足夠用了)
發(fā)送時(shí):
先發(fā)報(bào)頭長度
再編碼報(bào)頭內(nèi)容然后發(fā)送
最后發(fā)真實(shí)內(nèi)容
?
接收時(shí):
先手報(bào)頭長度,用struct取出來
根據(jù)取出的長度收取報(bào)頭內(nèi)容,然后解碼,反序列化
從反序列化的結(jié)果中取出待取數(shù)據(jù)的詳細(xì)信息,然后去取真實(shí)的數(shù)據(jù)內(nèi)容
?服務(wù)端:定制稍微復(fù)雜一點(diǎn)的報(bào)頭 ?客戶端?
?
FTP作業(yè):上傳下載文件
?服務(wù)端 import socket import struct import json import osclass MYTCPClient:address_family = socket.AF_INETsocket_type = socket.SOCK_STREAMallow_reuse_address = Falsemax_packet_size = 8192coding='utf-8'request_queue_size = 5def __init__(self, server_address, connect=True):self.server_address=server_addressself.socket = socket.socket(self.address_family,self.socket_type)if connect:try:self.client_connect()except:self.client_close()raisedef client_connect(self):self.socket.connect(self.server_address)def client_close(self):self.socket.close()def run(self):while True:inp=input(">>: ").strip()if not inp:continuel=inp.split()cmd=l[0]if hasattr(self,cmd):func=getattr(self,cmd)func(l)def put(self,args):cmd=args[0]filename=args[1]if not os.path.isfile(filename):print('file:%s is not exists' %filename)returnelse:filesize=os.path.getsize(filename)head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}print(head_dic)head_json=json.dumps(head_dic)head_json_bytes=bytes(head_json,encoding=self.coding)head_struct=struct.pack('i',len(head_json_bytes))self.socket.send(head_struct)self.socket.send(head_json_bytes)send_size=0with open(filename,'rb') as f:for line in f:self.socket.send(line)send_size+=len(line)print(send_size)else:print('upload successful')client=MYTCPClient(('127.0.0.1',8080))client.run()
Socket(套接字)
127.0.0.1本機(jī)回還地址
只能自己識別自己 其他人無法訪問
send與recv對應(yīng)
不要出現(xiàn)兩邊都是相同的情況
recv是跟內(nèi)存要數(shù)據(jù)
至于數(shù)據(jù)的來源 你無需考慮
TCP特點(diǎn)
會將數(shù)據(jù)量比較小的并且時(shí)間間隔比較短的數(shù)據(jù)
一次性打包發(fā)送給對方
socket最簡單版本
解決粘包問題的最復(fù)雜版本
from socket import SOL_SOCKET,SO_REUSEADDR
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
解決粘包問題
服務(wù)端
1.先制作一個(gè)發(fā)送給客戶端的字典
2.制作字典的報(bào)頭
3.發(fā)送字典的報(bào)頭
4.發(fā)送字典
5.再發(fā)真實(shí)數(shù)據(jù)
客戶端
1.先接受字典的報(bào)頭
2.解析拿到字典的數(shù)據(jù)長度
3.接受字典
4.從字典中獲取真實(shí)數(shù)據(jù)的長度
5.接受真實(shí)數(shù)據(jù)
寫一個(gè)上傳電影的功能
1.循環(huán)打印某一個(gè)文件夾下面的所有的文件
2.用戶選取想要上傳的文件
3.將用戶選擇的文件上傳到服務(wù)端
4.服務(wù)端保存該文件
1.直接獲取數(shù)據(jù)1024
2.制作一個(gè)數(shù)據(jù)的報(bào)頭
3.先發(fā)個(gè)字典 然后再發(fā)真實(shí)數(shù)據(jù)
?
轉(zhuǎn)載于:https://www.cnblogs.com/Ryan-Yuan/p/11317724.html
總結(jié)
以上是生活随笔為你收集整理的day28 socket网络编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: day22-面向对象之封装
- 下一篇: 雷林鹏分享:C# 多态性