Socket

軟件開發(fā)的架構(gòu)

1况既、C/S架構(gòu)
C/S即:Client與Server 这溅,中文意思:客戶端與服務(wù)器端架構(gòu)组民,這種架構(gòu)也是從用戶層面(也可以是物理層面)來劃分的棒仍。

這里的客戶端一般泛指客戶端應(yīng)用程序EXE,程序需要先安裝后臭胜,才能運(yùn)行在用戶的電腦上莫其,對用戶的電腦操作系統(tǒng)環(huán)境依賴較大。


image.png

2耸三、B/S架構(gòu)
B/S即:Browser與Server,中文意思:瀏覽器端與服務(wù)器端架構(gòu)乱陡,這種架構(gòu)是從用戶層面來劃分的。

Browser瀏覽器仪壮,其實(shí)也是一種Client客戶端憨颠,只是這個客戶端不需要大家去安裝什么應(yīng)用程序,只需在瀏覽器上通過HTTP請求服務(wù)器端相關(guān)的資源(網(wǎng)頁資源)积锅,客戶端Browser瀏覽器就能進(jìn)行增刪改查爽彤。

image.png

Socket 概念

image.png

Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口缚陷。在設(shè)計模式中适篙,Socket其實(shí)就是一個門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面箫爷,對用戶來說嚷节,一組簡單的接口就是全部聂儒,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議硫痰。

tcp和udp協(xié)議

TCP(Transmission Control Protocol) 可靠的衩婚,面向連接的協(xié)議。應(yīng)用:web瀏覽器碍论,電子郵件谅猾,文件傳輸程序。

UDP(User Datagram Protocol)不可靠的鳍悠,無連接的服務(wù)税娜,傳輸效率高,一對一藏研、一對多敬矩、多對一、多對多蠢挡、面向報文弧岳,盡最大努力服務(wù),無擁塞控制业踏。應(yīng)用:視頻流禽炬,IP語音。

image.png
套接字工作流程

先從服務(wù)端說起勤家。服務(wù)器端先初始化Socket腹尖,然后與端口綁定(bind),對端口進(jìn)行監(jiān)聽(listen),調(diào)用accept阻塞伐脖,等待客戶端連接热幔。在這時如果有個客戶端初始化一個Socket,然后連接服務(wù)器(connect)讼庇,如果連接成功绎巨,這時客戶端與服務(wù)器端的連接就建立了∪渥模客戶端發(fā)送數(shù)據(jù)請求场勤,服務(wù)器端接收請求并處理請求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端歼跟,客戶端讀取數(shù)據(jù)和媳,最后關(guān)閉連接,一次交互結(jié)束嘹承。

基于TCP協(xié)議的套接字

tcp是基于鏈接的窗价,因此必須先啟動服務(wù)端,然后再啟動客戶端去鏈接服務(wù)端
server端

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))  #把地址綁定到套接字
sk.listen()          #監(jiān)聽鏈接
conn,addr = sk.accept() #接受客戶端鏈接
ret = conn.recv(1024)  #接收客戶端信息
print(ret)       #打印客戶端信息
conn.send(b'hi')        #向客戶端發(fā)送信息
conn.close()       #關(guān)閉客戶端套接字
sk.close()        #關(guān)閉服務(wù)器套接字(可選)

client端

import socket
sk = socket.socket()           # 創(chuàng)建客戶套接字
sk.connect(('127.0.0.1',8898))    # 嘗試連接服務(wù)器
sk.send(b'hello!')
ret = sk.recv(1024)         # 對話(發(fā)送/接收)
print(ret)
sk.close()            # 關(guān)閉客戶套接字

若在重啟服務(wù)端看到以下錯誤:


image.png

解決方案:

#加入一條socket配置叹卷,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它撼港,在bind前加
sk.bind(('127.0.0.1',8898))  #把地址綁定到套接字
sk.listen()          #監(jiān)聽鏈接
conn,addr = sk.accept() #接受客戶端鏈接
ret = conn.recv(1024)   #接收客戶端信息
print(ret)              #打印客戶端信息
conn.send(b'hi')        #向客戶端發(fā)送信息
conn.close()       #關(guān)閉客戶端套接字
sk.close()        #關(guān)閉服務(wù)器套接字(可選)

基于UDP的套接字

udp是無鏈接的坪它,先啟動哪一端都不會報錯
server端

1 ss = socket()   #創(chuàng)建一個服務(wù)器的套接字
2 ss.bind()       #綁定服務(wù)器套接字
3 inf_loop:       #服務(wù)器無限循環(huán)
4     cs = ss.recvfrom()/ss.sendto() # 對話(接收與發(fā)送)
5 ss.close()                         # 關(guān)閉服務(wù)器套接字

client端

cs = socket()   # 創(chuàng)建客戶套接字
comm_loop:      # 通訊循環(huán)
    cs.sendto()/cs.recvfrom()   # 對話(發(fā)送/接收)
cs.close()                      # 關(guān)閉客戶套接字

粘包現(xiàn)象

什么是粘包

須知:只有TCP有粘包現(xiàn)象,UDP永遠(yuǎn)不會粘包

首先要掌握一個socket收發(fā)消息的原理


image.png

發(fā)送端可以使1k1k的發(fā)送數(shù)據(jù)帝牡,而接收端的應(yīng)用程序可以2k2k地提走數(shù)據(jù)往毡,當(dāng)然也有可能一次提走3k或6k的數(shù)據(jù),或者一次提走幾個字節(jié)的數(shù)據(jù)靶溜,也就是說應(yīng)用程序所看到的數(shù)據(jù)是一個整體开瞭,或者是一個流(stream),一條消息有多少字節(jié)對應(yīng)用程序是不可見的罩息,因此TCP協(xié)議是面向流的協(xié)議嗤详,這也是容易出現(xiàn)粘包的原因。
而UDP是面向消息的協(xié)議瓷炮,每個udp都是一條消息葱色,應(yīng)用程序必須以消息為單位提取數(shù)據(jù),不能一次提取任意字節(jié)的數(shù)據(jù)娘香,這一點(diǎn)和TCP是很不同的苍狰。

所謂粘包問題主要還是接收方不知道消息之間的界限,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的烘绽。

此外淋昭,發(fā)送方因此的粘包是由TCP協(xié)議本身造成的,TCP為提高傳輸效率安接,發(fā)送方往往要收集到足夠多的數(shù)據(jù)才發(fā)送一個TCP段翔忽,若連續(xù)幾次需要send的數(shù)據(jù)都很少,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個TCP字段后一次發(fā)送出去赫段,這樣接收方就收到了粘包數(shù)據(jù)呀打。
1矢赁、TCP(transport control protocol糯笙,傳輸控制協(xié)議)是面向連接的,面向流的撩银,提供高可靠性服務(wù)给涕。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對的socket,因此额获,發(fā)送端為了將多個發(fā)往接收端的包更有效的發(fā)到對方够庙,使用了優(yōu)化算法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)抄邀,合并成一個大的數(shù)據(jù)塊耘眨,然后進(jìn)行封包。這樣接收端就很難分辨出來了境肾,必須提供科學(xué)的拆包機(jī)制剔难。即面向流的通信是無消息保護(hù)邊界的胆屿。

2、UDP(user datagram protocol偶宫,用戶數(shù)據(jù)報協(xié)議)是無連接的非迹,面向消息的,提供高效率服務(wù)纯趋。不會使用塊的合并優(yōu)化算法憎兽,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個到達(dá)的UDP包吵冒,在每個UDP包中就有了消息頭(消息來源地址纯命,端口等信息),這樣痹栖,對于接收端來說扎附,就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的结耀。

3留夜、tcp是基于數(shù)據(jù)流的,于是收發(fā)的消息不能為空图甜,這就需要在客戶端和服務(wù)端都添加空消息的處理機(jī)制碍粥,防止程序卡住,而udp是基于數(shù)據(jù)報的黑毅,即便是你輸入的是空內(nèi)容(直接回車)嚼摩,那也不是空消息,udp協(xié)議會幫你封裝上消息頭

兩種情況下會發(fā)生粘包

發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去矿瘦,造成粘包(發(fā)送數(shù)據(jù)時間間隔很短枕面,數(shù)據(jù)了很小,會合到一起缚去,產(chǎn)生粘包)
server端

#_*_coding:utf-8_*_

from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(10)
data2=conn.recv(10)

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()

服務(wù)端

client端

#_*_coding:utf-8_*_

import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello'.encode('utf-8'))
s.send('feng'.encode('utf-8'))

客戶端

接收端不及時接收緩沖區(qū)的包潮秘,造成多個包接收(客戶端發(fā)送了一段數(shù)據(jù),服務(wù)端只接受了一小部分易结,服務(wù)端下次再接收的時候還是從緩沖區(qū)拿上次遺留的數(shù)據(jù)枕荞,產(chǎn)生粘包)
server端

#_*_coding:utf-8_*_

from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(2) #一次沒有收完整
data2=conn.recv(10)#下次收的時候,會先取舊的數(shù)據(jù),然后取新的

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()

服務(wù)端

client端

#_*_coding:utf-8_*_

import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello feng'.encode('utf-8'))

客戶端

解決粘包的方法

struct模塊
image.png
import json,struct
#假設(shè)通過客戶端上傳1T:1073741824000的文件a.txt

#為避免粘包,必須自定制報頭
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數(shù)據(jù),文件路徑和md5值

#為了該報頭能傳送,需要序列化并且轉(zhuǎn)為bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并轉(zhuǎn)成bytes,用于傳輸

#為了讓客戶端知道報頭的長度,用struck將報頭長度這個數(shù)字轉(zhuǎn)成固定長度:4個字節(jié)
head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節(jié)里只包含了一個數(shù)字,該數(shù)字是報頭的長度

#客戶端開始發(fā)送
conn.send(head_len_bytes) #先發(fā)報頭的長度,4個bytes
conn.send(head_bytes) #再發(fā)報頭的字節(jié)格式
conn.sendall(文件內(nèi)容) #然后發(fā)真實(shí)內(nèi)容的字節(jié)格式

#服務(wù)端開始接收
head_len_bytes=s.recv(4) #先收報頭4個bytes,得到報頭長度的字節(jié)格式
x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度

head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式
header=json.loads(json.dumps(header)) #提取報頭

#最后根據(jù)報頭的內(nèi)容提取真實(shí)的數(shù)據(jù),比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)

struct詳細(xì)用法

#_*_coding:utf-8_*_
#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html

import struct
import binascii
import ctypes

values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')

print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Before : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)


s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)

print('After pack',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size))

s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('After pack',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0))

關(guān)于struct的詳細(xì)用法

server端,自定制報頭

import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它搞动,在bind前加

phone.bind(('127.0.0.1',8080))

phone.listen(5)

while True:
    conn,addr=phone.accept()
    while True:
        cmd=conn.recv(1024)
        if not cmd:break
        print('cmd: %s' %cmd)

        res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        err=res.stderr.read()
        print(err)
        if err:
            back_msg=err
        else:
            back_msg=res.stdout.read()


        conn.send(struct.pack('i',len(back_msg))) #先發(fā)back_msg的長度
        conn.sendall(back_msg) #在發(fā)真實(shí)的內(nèi)容

    conn.close()

服務(wù)端(自定制報頭)

client端(自定制報頭)

#_*_coding:utf-8_*_

import socket,time,struct

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))



    l=s.recv(4)
    x=struct.unpack('i',l)[0]
    print(type(x),x)
    # print(struct.unpack('I',l))
    r_s=0
    data=b''
    while r_s < x:
        r_d=s.recv(1024)
        data+=r_d
        r_s+=len(r_d)

    # print(data.decode('utf-8'))
    print(data.decode('gbk')) #windows默認(rèn)gbk編碼

客戶端(自定制報頭)

我們可以把報頭做成字典躏精,字典里包含將要發(fā)送的真實(shí)數(shù)據(jù)的詳細(xì)信息,然后json序列化鹦肿,然后用struck將序列化后的數(shù)據(jù)長度打包成4個字節(jié)(4個自己足夠用了)

發(fā)送時:

先發(fā)報頭長度

再編碼報頭內(nèi)容然后發(fā)送

最后發(fā)真實(shí)內(nèi)容

接收時:

先手報頭長度矗烛,用struct取出來

根據(jù)取出的長度收取報頭內(nèi)容,然后解碼箩溃,反序列化

從反序列化的結(jié)果中取出待取數(shù)據(jù)的詳細(xì)信息瞭吃,然后去取真實(shí)的數(shù)據(jù)內(nèi)容

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碌识,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子虱而,更是在濱河造成了極大的恐慌筏餐,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牡拇,死亡現(xiàn)場離奇詭異魁瞪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)惠呼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門导俘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人剔蹋,你說我怎么就攤上這事旅薄。” “怎么了泣崩?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵少梁,是天一觀的道長。 經(jīng)常有香客問我矫付,道長凯沪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任买优,我火速辦了婚禮妨马,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杀赢。我一直安慰自己烘跺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布脂崔。 她就那樣靜靜地躺著滤淳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脱篙。 梳的紋絲不亂的頭發(fā)上娇钱,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天伤柄,我揣著相機(jī)與錄音绊困,去河邊找鬼。 笑死适刀,一個胖子當(dāng)著我的面吹牛秤朗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笔喉,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼取视,長吁一口氣:“原來是場噩夢啊……” “哼硝皂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起作谭,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稽物,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后折欠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贝或,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年锐秦,在試婚紗的時候發(fā)現(xiàn)自己被綠了咪奖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡酱床,死狀恐怖羊赵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扇谣,我是刑警寧澤昧捷,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站罐寨,受9級特大地震影響料身,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜衩茸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一芹血、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧楞慈,春花似錦幔烛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聚霜,卻和暖如春狡恬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蝎宇。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工弟劲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姥芥。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓兔乞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子庸追,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)淡溯。读整。。咱娶。绘沉。。豺总。车伞。 SOCKET,TCP/UDP,HTTP,FTP (一)TCP/UDP,SOCKET,HTTP,...
    zeqinjie閱讀 3,291評論 1 53
  • 一: 網(wǎng)絡(luò)各個協(xié)議:TCP/IP喻喳、SOCKET另玖、HTTP 網(wǎng)絡(luò)七層由下往上分別為物理層、數(shù)據(jù)鏈路層表伦、網(wǎng)絡(luò)層谦去、傳輸層...
    iYeso閱讀 1,436評論 0 13
  • 1.1 TCP/IP協(xié)議組 TCP/IP協(xié)議(傳輸控制協(xié)議)由網(wǎng)絡(luò)層的IP協(xié)議和傳輸層的TCP協(xié)議組成 IP層負(fù)責(zé)...
    F麥子閱讀 2,788評論 0 25
  • 聽著我們的祖國,思考人生的意義局劲。我不會完整的唱這首歌勺拣,但聽歌是的感覺一直都在,而現(xiàn)在的他們鱼填,有多少人知道這首歌呢药有?...
    七月令閱讀 181評論 0 0
  • 六月底的一天,大晴天苹丸,下午兩點(diǎn)鐘愤惰, 正是一天中最熱的時候。華山北站廣場上的大理石地面反射著白晃晃的赘理、令人生畏的日光...
    舉個瘋子閱讀 355評論 0 3