国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Socket 編程實(shí)戰(zhàn)

TNFE / 932人閱讀

摘要:本文原發(fā)于個(gè)人博客在英文中的含義為連接兩個(gè)物品的凹槽,像,意為眼窩,此外還有插座的意思。協(xié)議,所是用的傳輸協(xié)議,目前有三種。,也稱為無連接的,使用協(xié)議。當(dāng)函數(shù)返回時(shí),意味著對(duì)端已經(jīng)關(guān)閉。

本文原發(fā)于個(gè)人博客

Socket 在英文中的含義為“(連接兩個(gè)物品的)凹槽”,像the eye socket,意為“眼窩”,此外還有“插座”的意思。在計(jì)算機(jī)科學(xué)中,socket 通常是指一個(gè)連接的兩個(gè)端點(diǎn),這里的連接可以是同一機(jī)器上的,像unix domain socket,也可以是不同機(jī)器上的,像network socket。

本文著重介紹現(xiàn)在用的最多的 network socket,包括其在網(wǎng)絡(luò)模型中的位置、API 的編程范式、常見錯(cuò)誤等方面,最后用 Python 語言中的 socket API 實(shí)現(xiàn)幾個(gè)實(shí)際的例子。Socket 中文一般翻譯為“套接字”,不得不說這是個(gè)讓人摸不著頭腦的翻譯,我也沒想到啥“信達(dá)雅”的翻譯,所以本文直接用其英文表述。本文中所有代碼均可在 socket.py 倉庫中找到。

概述

Socket 作為一種通用的技術(shù)規(guī)范,首次是由 Berkeley 大學(xué)在 1983 為 4.2BSD Unix 提供的,后來逐漸演化為 POSIX 標(biāo)準(zhǔn)。Socket API 是由操作系統(tǒng)提供的一個(gè)編程接口,讓應(yīng)用程序可以控制使用 socket 技術(shù)。Unix 哲學(xué)中有一條一切皆為文件,所以 socketfile 的 API 使用很類似:可以進(jìn)行readwrite、openclose等操作。

現(xiàn)在的網(wǎng)絡(luò)系統(tǒng)是分層的,理論上有OSI模型,工業(yè)界有TCP/IP協(xié)議簇。其對(duì)比如下:

每層上都有其相應(yīng)的協(xié)議,socket API 不屬于TCP/IP協(xié)議簇,只是操作系統(tǒng)提供的一個(gè)用于網(wǎng)絡(luò)編程的接口,工作在應(yīng)用層與傳輸層之間:

我們平常瀏覽網(wǎng)站所使用的http協(xié)議,收發(fā)郵件用的smtp與imap,都是基于 socket API 構(gòu)建的。

一個(gè) socket,包含兩個(gè)必要組成部分:

地址,由 ip 與 端口組成,像192.168.0.1:80。

協(xié)議,socket 所是用的傳輸協(xié)議,目前有三種:TCP、UDP、raw IP。

地址與協(xié)議可以確定一個(gè)socket;一臺(tái)機(jī)器上,只允許存在一個(gè)同樣的socket。TCP 端口 53 的 socket 與 UDP 端口 53 的 socket 是兩個(gè)不同的 socket。

根據(jù) socket 傳輸數(shù)據(jù)方式的不同(使用協(xié)議不同),可以分為以下三種:

Stream sockets,也稱為“面向連接”的 socket,使用 TCP 協(xié)議。實(shí)際通信前需要進(jìn)行連接,傳輸?shù)臄?shù)據(jù)沒有特定的結(jié)構(gòu),所以高層協(xié)議需要自己去界定數(shù)據(jù)的分隔符,但其優(yōu)勢(shì)是數(shù)據(jù)是可靠的。

Datagram sockets,也稱為“無連接”的 socket,使用 UDP 協(xié)議。實(shí)際通信前不需要連接,一個(gè)優(yōu)勢(shì)時(shí) UDP 的數(shù)據(jù)包自身是可分割的(self-delimiting),也就是說每個(gè)數(shù)據(jù)包就標(biāo)示了數(shù)據(jù)的開始與結(jié)束,其劣勢(shì)是數(shù)據(jù)不可靠。

Raw sockets,通常用在路由器或其他網(wǎng)絡(luò)設(shè)備中,這種 socket 不經(jīng)過TCP/IP協(xié)議簇中的傳輸層(transport layer),直接由網(wǎng)絡(luò)層(Internet layer)通向應(yīng)用層(Application layer),所以這時(shí)的數(shù)據(jù)包就不會(huì)包含 tcp 或 udp 頭信息。

Python socket API

Python 里面用(ip, port)的元組來表示 socket 的地址屬性,用AF_*來表示協(xié)議類型。
數(shù)據(jù)通信有兩組動(dòng)詞可供選擇:send/recvread/write。read/write 方式也是 Java 采用的方式,這里不會(huì)對(duì)這種方式進(jìn)行過多的解釋,但是需要注意的是:

read/write 操作的具有 buffer 的“文件”,所以在進(jìn)行讀寫后需要調(diào)用flush方法去真正發(fā)送或讀取數(shù)據(jù),否則數(shù)據(jù)會(huì)一直停留在緩沖區(qū)內(nèi)。

TCP socket

TCP socket 由于在通向前需要建立連接,所以其模式較 UDP socket 負(fù)責(zé)些。具體如下:

每個(gè)API 的具體含義這里不在贅述,可以查看手冊(cè),這里給出 Python 語言的實(shí)現(xiàn)的 echo server。

# echo_server.py
# coding=utf8
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 設(shè)置 SO_REUSEADDR 后,可以立即使用 TIME_WAIT 狀態(tài)的 socket
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("", 5500))
sock.listen(5)
def handler(client_sock, addr):
    print("new client from %s:%s" % addr)
    msg = client_sock.recv(1024)
    client_sock.send(msg)
    client_sock.close()
    print("client[%s:%s] socket closed" % addr)

if __name__ == "__main__":
    while 1:
        client_sock, addr = sock.accept()
        handler(client_sock, addr)
# echo_client.py
# coding=utf8
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("", 5500))
sock.send("hello socket world")
print sock.recv(1024)

上面簡單的echo server 代碼中有一點(diǎn)需要注意的是:server 端的 socket 設(shè)置了SO_REUSEADDR為1,目的是可以立即使用處于TIME_WAIT狀態(tài)的socket,那么TIME_WAIT又是什么意思呢?后面在講解 tcp 狀態(tài)變更圖時(shí)再做詳細(xì)介紹。

UDP socket

UDP socket server 端代碼在進(jìn)行bind后,無需調(diào)用listen方法。

# udp_echo_server.py
# coding=utf8
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 設(shè)置 SO_REUSEADDR 后,可以立即使用 TIME_WAIT 狀態(tài)的 socket
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("", 5500))
# 沒有調(diào)用 listen

if __name__ == "__main__":
    while 1:
        data, addr = sock.recvfrom(1024)

        print("new client from %s:%s" % addr)
        sock.sendto(data, addr)

# udp_echo_client.py
# coding=utf8
import socket

udp_server_addr = ("", 5500)

if __name__ == "__main__":
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    data_to_sent = "hello udp socket"
    try:
        sent = sock.sendto(data_to_sent, udp_server_addr)
        data, server = sock.recvfrom(1024)
        print("receive data:[%s] from %s:%s" % ((data,) + server))
    finally:
        sock.close()
常見陷阱 忽略返回值

本文中的 echo server 示例因?yàn)槠拗?,也忽略了返回值。網(wǎng)絡(luò)通信是個(gè)非常復(fù)雜的問題,通常無法保障通信雙方的網(wǎng)絡(luò)狀態(tài),很有可能在發(fā)送/接收數(shù)據(jù)時(shí)失敗或部分失敗。所以有必要對(duì)發(fā)送/接收函數(shù)的返回值進(jìn)行檢查。本文中的 tcp echo client 發(fā)送數(shù)據(jù)時(shí),正確寫法應(yīng)該如下:

total_send = 0
content_length = len(data_to_sent)
while total_send < content_length:
    sent = sock.send(data_to_sent[total_send:])
    if sent == 0:
        raise RuntimeError("socket connection broken")
    total_send += total_send + sent

send/recv操作的是網(wǎng)絡(luò)緩沖區(qū)的數(shù)據(jù),它們不必處理傳入的所有數(shù)據(jù)。

一般來說,當(dāng)網(wǎng)絡(luò)緩沖區(qū)填滿時(shí),send函數(shù)就返回了;當(dāng)網(wǎng)絡(luò)緩沖區(qū)被清空時(shí),recv 函數(shù)就返回。
當(dāng) recv 函數(shù)返回0時(shí),意味著對(duì)端已經(jīng)關(guān)閉。

可以通過下面的方式設(shè)置緩沖區(qū)大小。

s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, buffer_size)
認(rèn)為 TCP 具有 framing

TCP 不提供 framing,這使得其很適合于傳輸數(shù)據(jù)流。這是其與 UDP 的重要區(qū)別之一。UDP 是一個(gè)面向消息的協(xié)議,能保持一條消息在發(fā)送者與接受者之間的完備性。


代碼示例參考:framing_assumptions

TCP 的狀態(tài)機(jī)

在前面echo server 的示例中,提到了TIME_WAIT狀態(tài),為了正式介紹其概念,需要了解下 TCP 從生成到結(jié)束的狀態(tài)機(jī)器。(圖片來源)

這個(gè)狀圖轉(zhuǎn)移圖非常非常關(guān)鍵,也比較復(fù)雜,我自己為了方便記憶,對(duì)這個(gè)圖進(jìn)行了拆解,仔細(xì)分析這個(gè)圖,可以得出這樣一個(gè)結(jié)論,連接的打開與關(guān)閉都有被動(dòng)(passive)與主動(dòng)(active)兩種,主動(dòng)關(guān)閉時(shí),涉及到的狀態(tài)轉(zhuǎn)移最多,包括FIN_WAIT_1、FIN_WAIT_2、CLOSING、TIME_WAIT。

此外,由于 TCP 是可靠的傳輸協(xié)議,所以每次發(fā)送一個(gè)數(shù)據(jù)包后,都需要得到對(duì)方的確認(rèn)(ACK),有了上面這兩個(gè)知識(shí)后,再來看下面的圖:(圖片來源)

在主動(dòng)關(guān)閉連接的 socket 調(diào)用 close方法的同時(shí),會(huì)向被動(dòng)關(guān)閉端發(fā)送一個(gè) FIN

對(duì)端收到FIN后,會(huì)向主動(dòng)關(guān)閉端發(fā)送ACK進(jìn)行確認(rèn),這時(shí)被動(dòng)關(guān)閉端處于 CLOSE_WAIT 狀態(tài)

當(dāng)被動(dòng)關(guān)閉端調(diào)用close方法進(jìn)行關(guān)閉的同時(shí)向主動(dòng)關(guān)閉端發(fā)送 FIN 信號(hào),接收到 FIN 的主動(dòng)關(guān)閉端這時(shí)就處于 TIME_WAIT 狀態(tài)

這時(shí)主動(dòng)關(guān)閉端不會(huì)立刻轉(zhuǎn)為 CLOSED 狀態(tài),而是需要等待 2MSL(max segment life,一個(gè)數(shù)據(jù)包在網(wǎng)絡(luò)傳輸中最大的生命周期),以確保被動(dòng)關(guān)閉端能夠收到最后發(fā)出的 ACK。如果被動(dòng)關(guān)閉端沒有收到最后的 ACK,那么被動(dòng)關(guān)閉端就會(huì)重新發(fā)送 FIN,所以處于TIME_WAIT的主動(dòng)關(guān)閉端會(huì)再次發(fā)送一個(gè) ACK 信號(hào),這么一來(FIN來)一回(ACK),正好是兩個(gè) MSL 的時(shí)間。如果等待的時(shí)間小于 2MSL,那么新的socket就可以收到之前連接的數(shù)據(jù)。

前面 echo server 的示例也說明了,處于 TIME_WAIT 并不是說一定不能使用,可以通過設(shè)置 socket 的 SO_REUSEADDR 屬性以達(dá)到不用等待 2MSL 的時(shí)間就可以復(fù)用socket 的目的,當(dāng)然,這僅僅適用于測試環(huán)境,正常情況下不要修改這個(gè)屬性。

實(shí)戰(zhàn) HTTP UA

http 協(xié)議是如今萬維網(wǎng)的基石,可以通過 socket API 來簡單模擬一個(gè)瀏覽器(UA)是如何解析 HTTP 協(xié)議數(shù)據(jù)的。

#coding=utf8
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
baidu_ip = socket.gethostbyname("baidu.com")
sock.connect((baidu_ip, 80))
print("connected to %s" % baidu_ip)

req_msg = [
    "GET / HTTP/1.1",
    "User-Agent: curl/7.37.1",
    "Host: baidu.com",
    "Accept: */*",
]
delimiter = "
"

sock.send(delimiter.join(req_msg))
sock.send(delimiter)
sock.send(delimiter)

print("%sreceived%s" % ("-"*20, "-"*20))
http_response = sock.recv(4096)
print(http_response)

運(yùn)行上面的代碼可以得到下面的輸出

--------------------received--------------------
HTTP/1.1 200 OK
Date: Tue, 01 Nov 2016 12:16:53 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
Content-Length: 81
Cache-Control: max-age=86400
Expires: Wed, 02 Nov 2016 12:16:53 GMT
Connection: Keep-Alive
Content-Type: text/html



http_response是通過直接調(diào)用recv(4096)得到的,萬一真正的返回大于這個(gè)值怎么辦?我們前面知道了 TCP 協(xié)議是面向流的,它本身并不關(guān)心消息的內(nèi)容,需要應(yīng)用程序自己去界定消息的邊界,對(duì)于應(yīng)用層的 HTTP 協(xié)議來說,有幾種情況,最簡單的一種時(shí)通過解析返回值頭部的Content-Length屬性,這樣就知道body的大小了,對(duì)于 HTTP 1.1版本,支持Transfer-Encoding: chunked傳輸,對(duì)于這種格式,這里不在展開講解,大家只需要知道, TCP 協(xié)議本身無法區(qū)分消息體就可以了。對(duì)這塊感興趣的可以查看 CPython 核心模塊 http.client

Unix_domain_socket

UDS 用于同一機(jī)器上不同進(jìn)程通信的一種機(jī)制,其API適用與 network socket 很類似。只是其連接地址為本地文件而已。

代碼示例參考:uds_server.py、uds_client.py

ping

ping 命令作為檢測網(wǎng)絡(luò)聯(lián)通性最常用的工具,其適用的傳輸協(xié)議既不是TCP,也不是 UDP,而是 ICMP,利用 raw sockets,我們可以適用純 Python 代碼來實(shí)現(xiàn)其功能。

代碼示例參考:ping.py

netstat vs ss

netstat 與 ss 是類 Unix 系統(tǒng)上查看 Socket 信息的命令。
netstat 是比較老牌的命令,我常用的選擇有

-t,只顯示 tcp 連接

-u,只顯示 udp 連接

-n,不用解析hostname,用 IP 顯示主機(jī),可以加快執(zhí)行速度

-p,查看連接的進(jìn)程信息

-l,只顯示監(jiān)聽的連接

ss 是新興的命令,其選項(xiàng)和 netstat 差不多,主要區(qū)別是能夠進(jìn)行過濾(通過stateexclude關(guān)鍵字)。

$ ss -o state time-wait -n | head
Recv-Q Send-Q             Local Address:Port               Peer Address:Port
0      0                 10.200.181.220:2222              10.200.180.28:12865  timer:(timewait,33sec,0)
0      0                      127.0.0.1:45977                 127.0.0.1:3306   timer:(timewait,46sec,0)
0      0                      127.0.0.1:45945                 127.0.0.1:3306   timer:(timewait,6.621ms,0)
0      0                 10.200.181.220:2222              10.200.180.28:12280  timer:(timewait,12sec,0)
0      0                 10.200.181.220:2222              10.200.180.28:35045  timer:(timewait,43sec,0)
0      0                 10.200.181.220:2222              10.200.180.28:42675  timer:(timewait,46sec,0)
0      0                      127.0.0.1:45949                 127.0.0.1:3306   timer:(timewait,11sec,0)
0      0                      127.0.0.1:45954                 127.0.0.1:3306   timer:(timewait,21sec,0)
0      0               ::ffff:127.0.0.1:3306           ::ffff:127.0.0.1:45964  timer:(timewait,31sec,0)

這兩個(gè)命令更多用法可以參考:

SS Utility: Quick Intro

10 basic examples of linux netstat command

總結(jié)

我們的生活已經(jīng)離不開網(wǎng)絡(luò),平時(shí)的開發(fā)也充斥著各種復(fù)雜的網(wǎng)絡(luò)應(yīng)用,從最基本的數(shù)據(jù)庫,到各種分布式系統(tǒng),不論其應(yīng)用層怎么復(fù)雜,其底層傳輸數(shù)據(jù)的的協(xié)議簇是一致的。Socket 這一概念我們很少直接與其打交道,但是當(dāng)我們的系統(tǒng)出現(xiàn)問題時(shí),往往是對(duì)底層的協(xié)議認(rèn)識(shí)不足造成的,希望這篇文章能對(duì)大家編程網(wǎng)絡(luò)方面的程序有所幫助。

參考

Five pitfalls of Linux sockets programming

Programming Linux sockets, Part 1: Using TCP/IP

http://stackoverflow.com/ques...

What’s The Difference Between The OSI Seven-Layer Network Model And TCP/IP?

TCP 的那些事兒(上)

Coping with the TCP TIME-WAIT state on busy Linux servers

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/44225.html

相關(guān)文章

  • 計(jì)算機(jī)常識(shí) - 收藏集 - 掘金

    摘要:使用簡記后端掘金全稱為即消息隊(duì)列。優(yōu)測優(yōu)社區(qū)干貨精選老司機(jī)亂談編輯器之神掘金前言是一種信仰,我自從年有了這個(gè)信仰,已經(jīng)個(gè)年頭了。 PHP 程序員進(jìn)階學(xué)習(xí)書籍參考指南 - 后端 - 掘金PHP程序員進(jìn)階學(xué)習(xí)書籍參考指南 @heiyeluren lastmodify: 2016/2/18 ... 當(dāng)我們?cè)谡務(wù)撉岸思用軙r(shí),我們?cè)谡勑┦裁?- 前端 - 掘金潘建旭,豈安科技(www.bigse...

    Yi_Zhi_Yu 評(píng)論0 收藏0
  • 大前端2018現(xiàn)在上車還還得及么

    摘要:面向?qū)ο笕筇卣骼^承性多態(tài)性封裝性接口。第五階段封裝一個(gè)屬于自己的框架框架封裝基礎(chǔ)事件流冒泡捕獲事件對(duì)象事件框架選擇框架。核心模塊和對(duì)象全局對(duì)象,,,事件驅(qū)動(dòng),事件發(fā)射器加密解密,路徑操作,序列化和反序列化文件流操作服務(wù)端與客戶端。 第一階段: HTML+CSS:HTML進(jìn)階、CSS進(jìn)階、div+css布局、HTML+css整站開發(fā)、 JavaScript基礎(chǔ):Js基礎(chǔ)教程、js內(nèi)置對(duì)...

    stormgens 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<