python入門系列:Python socket編程

引言

sockets的歷史悠久衅鹿,它們最早在 1971 年的 APPANET 中使用愕够,后來(lái)成為1983年發(fā)布的Berkeley Software Distribution(BSD)操作系統(tǒng)中的API,稱為Berkeley sockets浪讳。

Web服務(wù)器和瀏覽器并不是使用sockets的唯一程序缰盏,各種規(guī)模和類型的客戶端 - 服務(wù)器(client - server)應(yīng)用程序也得到了廣泛使用。

今天,盡管socket API使用的底層協(xié)議已經(jīng)發(fā)展多年口猜,而且已經(jīng)有新協(xié)議出現(xiàn)形葬,但是底層 API 仍然保持不變。

最常見(jiàn)的套接字應(yīng)用程序類型是客戶端 - 服務(wù)器(client - server)應(yīng)用程序暮的,其中一方充當(dāng)服務(wù)器并等待來(lái)自客戶端的連接笙以。

Socket API介紹

Python中的socket模塊提供了一個(gè)到Berkeley sockets API的接口,其中的主要接口函數(shù)如下:

socket()

bind()

listen()

accept()

connect()

connect_ex()

send()

recv()

close()

這些方便使用的接口函數(shù)和系統(tǒng)底層的功能調(diào)用相一致冻辩。

TCP Sockets

我們準(zhǔn)備構(gòu)建一個(gè)基于 TCP 協(xié)議的socket對(duì)象猖腕,為什么使用 TCP 呢,因?yàn)椋?/p>

可靠性:如果在傳輸過(guò)程中因?yàn)榫W(wǎng)絡(luò)原因?qū)е聰?shù)據(jù)包丟失恨闪,會(huì)有相關(guān)機(jī)制檢測(cè)到并且進(jìn)行重新傳輸

按序到達(dá):一方發(fā)送到另一方的數(shù)據(jù)包是按發(fā)送順序被接收的倘感。

對(duì)比之下,UDP 協(xié)議是不提供這些保證的咙咽,但是它的響應(yīng)效率更高老玛,資源消耗更少。

TCP 協(xié)議并不需要我們自己去實(shí)現(xiàn)钧敞,在底層都已經(jīng)實(shí)現(xiàn)好了蜡豹,我們只需要使用Python的socket模塊,進(jìn)行協(xié)議指定就可以了溉苛。socket.SOCK_STREAM表示使用 TCP 協(xié)議镜廉,socket.SOCK_DGRAM表示使用 UDP 協(xié)議

我們來(lái)看看基于 TCP 協(xié)議socket的 API 調(diào)用和數(shù)據(jù)傳送流程圖,右邊的一列是服務(wù)器端(server)愚战,左邊的一列是客戶端(client)娇唯。

要實(shí)現(xiàn)左邊的處于監(jiān)聽(tīng)狀態(tài)的server,我們需要按照順序調(diào)用這樣幾個(gè)函數(shù):

socket(): 創(chuàng)建一個(gè)socket對(duì)象

bind(): 關(guān)聯(lián)對(duì)應(yīng) ip 地址和端口號(hào)

listen(): 允許對(duì)象接收其他socket的連接

accept(): 接收其他socket的連接寂玲,返回一個(gè)元組(conn, addr)塔插,conn 是一個(gè)新的socket對(duì)象,代表這個(gè)連接拓哟,addr 是連接端的地址信息想许。

client調(diào)用connect()時(shí),會(huì)通過(guò) TCP 的三次握手彰檬,建立連接伸刃。當(dāng)client連接到server時(shí),server會(huì)調(diào)用accept()完成這次連接逢倍。

雙方通過(guò)send()和recv()來(lái)接收和發(fā)送數(shù)據(jù)捧颅,最后通過(guò)close()來(lái)關(guān)閉這次連接,釋放資源较雕。一般server端是不關(guān)閉的碉哑,會(huì)繼續(xù)等待其他的連接挚币。

Echo Client and Server

剛才我們弄清楚了server和client使用socket進(jìn)行通信的過(guò)程,我們現(xiàn)在要自己進(jìn)行一個(gè)簡(jiǎn)單的也是經(jīng)典的實(shí)現(xiàn):server復(fù)述從client接收的信息扣典。

Echo Server

import socket

HOST = '127.0.0.1' # Standard loopback interface address (localhost)

PORT = 65431 # Port to listen on (non-privileged ports are > 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

s.bind((HOST, PORT))

s.listen()

conn, addr = s.accept()

with conn:

print('Connected by', addr)

while True:

data = conn.recv(1024)

if not data:

break

conn.sendall(data)

socket.socket()創(chuàng)建了一個(gè)socket對(duì)象妆毕,它實(shí)現(xiàn)了上下文管理器協(xié)議,我們直接用?with?語(yǔ)句進(jìn)行創(chuàng)建即可贮尖,而且最后不需要調(diào)用close()函數(shù)笛粘。

socket()中的兩個(gè)參數(shù)指明了連接需要的?ip地址類型傳輸協(xié)議類型,socket.AF_INET 表示使用 IPv4的地址進(jìn)行連接湿硝,socket.SOCK_STREAM 表示使用 TCP 協(xié)議進(jìn)行數(shù)據(jù)的傳輸薪前。

bind()用來(lái)將socket對(duì)象和特定的網(wǎng)絡(luò)對(duì)象和端口號(hào)進(jìn)行關(guān)聯(lián),函數(shù)中的兩個(gè)參數(shù)是由創(chuàng)建socket對(duì)象時(shí)指定的 ip地址類型 決定的关斜,這里使用的是socket.AF_INET(IPv4)示括,因此,bind()函數(shù)接收一個(gè)元組對(duì)象作為參數(shù)(HOST, PORT)

host可以是一個(gè)主機(jī)名痢畜,IP地址垛膝,或者空字符串。如果使用的是 IP地址丁稀,host必須是 IPv4格式的地址字符串吼拥。127.0.0.1是本地環(huán)路的標(biāo)準(zhǔn)寫法,因此只有在主機(jī)上的進(jìn)程才能夠連接到server,如果設(shè)置為空字符串,它可以接受所有合法 IPv4地址的連接娇掏。

port應(yīng)該是從1 - 65535的一個(gè)整數(shù)(0被保留了)辕狰,它相當(dāng)于是一個(gè)窗口和其他的客戶端建立連接,如果想使用1 - 1024的端口唬复,一些系統(tǒng)可能會(huì)要求要有管理員權(quán)限矗积。

listen()使得server可以接受連接,它可以接受一個(gè)參數(shù):backlog敞咧,用來(lái)指明系統(tǒng)可以接受的連接數(shù)量棘捣,雖然同一時(shí)刻只能與一端建立連接,但是其他的連接請(qǐng)求可以被放入等待隊(duì)列中休建,當(dāng)前面的連接斷開(kāi)乍恐,后面的請(qǐng)求會(huì)依次被處理,超過(guò)這個(gè)數(shù)量的連接請(qǐng)求再次發(fā)起后测砂,會(huì)被server直接拒絕茵烈。

從Python 3.5開(kāi)始,這個(gè)參數(shù)是可選的砌些,如果我們不明確指明呜投,它就采用系統(tǒng)默認(rèn)值加匈。如果server端在同一時(shí)刻會(huì)收到大量的連接請(qǐng)求,通常要把這個(gè)值調(diào)大一些仑荐,在Linux中雕拼,可以在/proc/sys/net/core/somaxconn看到值的情況,詳細(xì)請(qǐng)參閱:

Will increasing net.core.somaxconn make a difference?

How TCP backlog works in Linux

accept()監(jiān)聽(tīng)連接的建立粘招,是一個(gè)阻塞式調(diào)用啥寇,當(dāng)有client連接之后,它會(huì)返回一個(gè)代表這個(gè)連接的新的socket對(duì)象和代表client地址信息的元組洒扎。對(duì)于 IPv4 的地址連接辑甜,地址信息是?(host, port),對(duì)于 IPv6 逊笆,(host, port, flowinfo, scopeid)

有一件事情需要特別注意栈戳,accept()之后,我們獲得了一個(gè)新的socket對(duì)象难裆,它和server以及client都不同子檀,我們用它來(lái)進(jìn)行和client的通信。

conn, addr = s.accept()

with conn:

print('Connected by', addr)

while True:

data = conn.recv(1024)

if not data:

break

conn.sendall(data)

conn是我們新獲得的socket對(duì)象乃戈,conn.recv()也是一個(gè)阻塞式調(diào)用褂痰,它會(huì)等待底層的 I/O 響應(yīng),直到獲得數(shù)據(jù)才繼續(xù)向下執(zhí)行症虑。外面的while循環(huán)保證server端一直監(jiān)聽(tīng)缩歪,通過(guò)conn.sendall將數(shù)據(jù)再發(fā)送回去。

Echo Client

import socket

HOST = '127.0.0.1' # The server's hostname or IP address

PORT = 65431 # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

s.connect((HOST, PORT))

s.sendall("Hello, world".encode("utf8"))

data = s.recv(1024)

print('Received', data.decode("utf8"))

和server相比谍憔,client更加簡(jiǎn)單匪蝙,先是創(chuàng)建了一個(gè)socket對(duì)象,然后將它和server連接习贫,通過(guò)s.sendall()將信息發(fā)送給server逛球,通過(guò)s.recv()獲得來(lái)自server的數(shù)據(jù),然后將其打印輸出苫昌。

在發(fā)送數(shù)據(jù)時(shí)颤绕,只支持發(fā)送字節(jié)型數(shù)據(jù),所以我們要將需要發(fā)送的數(shù)據(jù)進(jìn)行編碼祟身,在收到server端的回應(yīng)后奥务,將得到的數(shù)據(jù)進(jìn)行解碼,就能還原出我們能夠識(shí)別的字符串了袜硫。

啟動(dòng)程序

我們要先啟動(dòng)server端氯葬,做好監(jiān)聽(tīng)準(zhǔn)備,然后再啟動(dòng)client端父款,進(jìn)行連接溢谤。

這個(gè)信息是在client連接后打印出來(lái)的瞻凤。

可以使用netstat這個(gè)命令查看socket的狀態(tài),更詳細(xì)使用可以查閱幫助文檔世杀。

查看系統(tǒng)中處于監(jiān)聽(tīng)狀態(tài)的socket阀参,過(guò)濾出了使用 TCP協(xié)議 和 IPv4 地址的對(duì)象:

如果先啟動(dòng)了client,會(huì)有下面這個(gè)經(jīng)典的錯(cuò)誤:

造成的原因可能是端口號(hào)寫錯(cuò)了瞻坝,或者server根本就沒(méi)運(yùn)行蛛壳,也可能是在server端存在防火墻阻值了連接建立,下面是一些常見(jiàn)的錯(cuò)誤異常:

Exceptionerrno ConstantDescriptionBlockingIOErrorEWOULDBLOCKResource temporarily unavailable. For example, in non-blocking mode, when calling send() and the peer is busy and not reading, the send queue (network buffer) is full. Or there are issues with the network. Hopefully this is a temporary condition.OSErrorADDRINUSEAddress already in use. Make sure there’s not another process running that’s using the same port number and your server is setting the socket option SO_REUSEADDR: socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1).ConnectionResetErrorECONNRESETConnection reset by peer. The remote process crashed or did not close its socket properly (unclean shutdown). Or there’s a firewall or other device in the network path that’s missing rules or misbehaving.TimeoutErrorETIMEDOUTOperation timed out. No response from peer.ConnectionRefusedErrorECONNREFUSEDConnection refused. No application listening on specified port.

連接的建立

現(xiàn)在我們仔細(xì)看一下server和client是怎樣建通信的:

當(dāng)使用環(huán)路網(wǎng)絡(luò)((IPv4 address 127.0.0.1 or IPv6 address ::1))的時(shí)候所刀,數(shù)據(jù)沒(méi)有離開(kāi)過(guò)主機(jī)跑到外部的網(wǎng)絡(luò)衙荐。如圖所示,環(huán)路網(wǎng)絡(luò)是在主機(jī)內(nèi)部建立的浮创,數(shù)據(jù)就經(jīng)過(guò)它來(lái)發(fā)送忧吟,從主機(jī)上運(yùn)行的一個(gè)程序發(fā)送到另一個(gè)程序,從主機(jī)發(fā)到主機(jī)斩披。這就是為什么我們喜歡說(shuō)環(huán)路網(wǎng)絡(luò)和 IP地址 127.0.0.1(IPv4) 或 ::1(IPv6) 都表示主機(jī)

如果server使用的時(shí)其他的合法IP地址溜族,它就會(huì)通過(guò)以太網(wǎng)接口與外部網(wǎng)絡(luò)建立聯(lián)系:

如何處理多端連接

echo server最大的缺點(diǎn)就是它同一時(shí)間只能服務(wù)一個(gè)client,直到連接的斷開(kāi)垦沉,echo client同樣也有不足煌抒,當(dāng)client進(jìn)行如下操作時(shí),有可能s.recv()只返回了一個(gè)字節(jié)的數(shù)據(jù)厕倍,數(shù)據(jù)并不完整寡壮。

data = s.recv(1024)

這里所設(shè)定的參數(shù) 1024 表示單次接收的最大數(shù)據(jù)量,并不是說(shuō)會(huì)返回 1024 字節(jié)的數(shù)據(jù)讹弯。在server中使用的send()與之類似况既,調(diào)用后它有一個(gè)返回值,標(biāo)示已經(jīng)發(fā)送出去的數(shù)據(jù)量组民,可能是小于我們實(shí)際要發(fā)送的數(shù)據(jù)量坏挠,比如說(shuō)有 6666 字節(jié)的數(shù)據(jù)要發(fā)送,用上面的發(fā)送方式要發(fā)送很多此才行邪乍,也就是說(shuō)一次調(diào)用send()數(shù)據(jù)并沒(méi)有被完整發(fā)送,我們需要自己做這個(gè)檢查來(lái)確保數(shù)據(jù)完整發(fā)送了对竣。

因此庇楞,這里使用了sendall(),它會(huì)不斷地幫我們發(fā)送數(shù)據(jù)直到數(shù)據(jù)全部發(fā)送或者出現(xiàn)錯(cuò)誤否纬。

所以吕晌,目前有兩個(gè)問(wèn)題:

怎樣同時(shí)處理多個(gè)連接?

怎樣調(diào)用send()和recv()直到數(shù)據(jù)全部發(fā)送或接收临燃。

要實(shí)現(xiàn)并發(fā)睛驳,傳統(tǒng)方法是使用多線程烙心,最近比較流行的方法是使用在Python3.4中引入的異步IO模塊asyncio。

這里準(zhǔn)備用更加傳統(tǒng)乏沸,但是更容易理解的方式來(lái)實(shí)現(xiàn)淫茵,基于系統(tǒng)底層的一個(gè)調(diào)用:select(),Python中也提供了

對(duì)應(yīng)的模塊:selectors

select()通過(guò)了一種機(jī)制蹬跃,它來(lái)監(jiān)聽(tīng)操作發(fā)生情況匙瘪,一旦某個(gè)操作準(zhǔn)備就緒(一般是讀就緒或者是寫就緒),然后將需要進(jìn)行這些操作的應(yīng)用程序select出來(lái)蝶缀,進(jìn)行相應(yīng)的讀和寫操作丹喻。到這里,你可能會(huì)發(fā)現(xiàn)這并沒(méi)有實(shí)現(xiàn)并發(fā)翁都,但是它的響應(yīng)速度非嘲郏快,通過(guò)異步操作柄慰,足夠模擬并發(fā)的效果了鳍悠。

Muti-Connection Client and Server

Multi-Connection Server

import selectors

sel = selectors.DefaultSelector()

# ...

lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

lsock.bind((host, port))

lsock.listen()

print('listening on', (host, port))

lsock.setblocking(False)

sel.register(lsock, selectors.EVENT_READ, data=None)

和echo server最大的不同就在于,通過(guò)lsock.setblocking(False)先煎,將這個(gè)socket對(duì)象設(shè)置成了非阻塞形式贼涩,與sel.select()一起使用,就可以在一個(gè)或多個(gè)socket對(duì)象上等待事件薯蝎,然后在數(shù)據(jù)準(zhǔn)備就緒時(shí)進(jìn)行數(shù)據(jù)的讀寫操作遥倦。

sel.register()給server注冊(cè)了我們需要的事件,對(duì)server來(lái)說(shuō)占锯,我們需要 I/O 可讀袒哥,從而進(jìn)行client發(fā)送數(shù)據(jù)的讀入,因此消略,通過(guò)selector.EVENT_READ來(lái)指明堡称。

data用來(lái)存儲(chǔ)和socket有關(guān)的任何數(shù)據(jù),當(dāng)sel.select()返回結(jié)果時(shí)艺演,它也被返回却紧,我們用它作為一個(gè)標(biāo)志,來(lái)追蹤擁有讀入和寫入操作的socket對(duì)象胎撤。

接下來(lái)是事件循環(huán):

import selectors

sel = selectors.DefaultSelector()

# ...

while True:

events = sel.select(timeout=None)

for key, mask in events:

if key.data is None:

accept_wrapper(key.fileobj)

else:

service_connection(key, mask)

sel.select(timeout=None)是一個(gè)阻塞式調(diào)用晓殊,直到有socket對(duì)象準(zhǔn)備好了 I/O 操作,或者等待時(shí)間超過(guò)設(shè)定的timeout伤提。它將返回(key, events)這類元組構(gòu)成的一個(gè)列表巫俺,每一個(gè)對(duì)應(yīng)一個(gè)就緒的socket對(duì)象。

key是一個(gè)SeletorKey類型的實(shí)例肿男,它有一個(gè)fileobj的屬性介汹,這個(gè)屬性就是sokect對(duì)象却嗡。

mask是就緒操作的狀態(tài)掩碼。

如果key.data is None嘹承,我們就知道窗价,這是一個(gè)server對(duì)象,于是要調(diào)用accept()方法赶撰,用來(lái)等待client的連接舌镶。不過(guò)我們要調(diào)用我們自己的accept_wrapper()函數(shù),里面還會(huì)包含其他的邏輯豪娜。

如果key.data is not None餐胀,我們就知道,這是一個(gè)client對(duì)象瘤载,它帶著數(shù)據(jù)來(lái)建立連接啦否灾!然后我們要為它提供服務(wù),于是就調(diào)用service_connection(key, mask)鸣奔,完成所有的服務(wù)邏輯墨技。

def accept_wrapper(sock):

conn, addr = sock.accept() # Should be ready to read

print('accepted connection from', addr)

conn.setblocking(False)

data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')

events = selectors.EVENT_READ | selectors.EVENT_WRITE

sel.register(conn, events, data=data)

這個(gè)函數(shù)用來(lái)處理與client的連接,使用conn.setblocking(False)將該對(duì)象設(shè)置為非阻塞狀態(tài)挎狸,這正是我們?cè)谶@個(gè)版本的程序中所需要的扣汪,否則,整個(gè)server會(huì)停止锨匆,直到它返回崭别,這意味著其他socket對(duì)象進(jìn)入等待狀態(tài)。

然后恐锣,使用types.SimplleNamespace()構(gòu)建了一個(gè)data對(duì)象茅主,存儲(chǔ)我們想保存的數(shù)據(jù)和socket對(duì)象。

因?yàn)閿?shù)據(jù)的讀寫都是通過(guò)conn土榴,所以使用selectors.EVENT_READ | selectors.EVENT_WRITE诀姚,然后用sel.register(conn, events, data=data)進(jìn)行注冊(cè)。

def service_connection(key, mask):

sock = key.fileobj

data = key.data

if mask & selectors.EVENT_READ:

recv_data = sock.recv(1024) # Should be ready to read

if recv_data:

data.outb += recv_data

else:

print('closing connection to', data.addr)

sel.unregister(sock)

sock.close()

if mask & selectors.EVENT_WRITE:

if data.outb:

print('echoing', repr(data.outb), 'to', data.addr)

sent = sock.send(data.outb) # Should be ready to write

data.outb = data.outb[sent:]

這就時(shí)服務(wù)邏輯的核心玷禽,key中包含了socket對(duì)象和data對(duì)象赫段,mask是已經(jīng)就緒操作的掩碼。根據(jù)sock可以讀矢赁,將數(shù)據(jù)保存在data.outb中瑞佩,這也將成為寫出的數(shù)據(jù)。

if recv_data:

data.outb += recv_data

else:

print('closing connection to', data.addr)

sel.unregister(sock)

sock.close()

如果沒(méi)有接收到數(shù)據(jù)坯台,說(shuō)明client數(shù)據(jù)發(fā)完了,sock的狀態(tài)不再被追蹤瘫寝,然后關(guān)閉這次連接蜒蕾。

Multi-Connection Client

messages = [b'Message 1 from client.', b'Message 2 from client.']

def start_connections(host, port, num_conns):

server_addr = (host, port)

for i in range(0, num_conns):

connid = i + 1

print('starting connection', connid, 'to', server_addr)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.setblocking(False)

sock.connect_ex(server_addr)

events = selectors.EVENT_READ | selectors.EVENT_WRITE

data = types.SimpleNamespace(connid=connid,

msg_total=sum(len(m) for m in messages),

recv_total=0,

messages=list(messages),

outb=b'')

sel.register(sock, events, data=data)

使用connect_ex()而不是connect()稠炬,因?yàn)閏onnect()會(huì)立即引發(fā)BlockingIOError異常。connect_ex()只返回錯(cuò)誤碼 errno.EINPROGRESS咪啡,而不是在連接正在進(jìn)行時(shí)引發(fā)異常首启。連接完成后,socket對(duì)象就可以進(jìn)行讀寫撤摸,并通過(guò)select()返回毅桃。

連接建立完成后,我們使用了types.SimpleNamespace構(gòu)建出和socket對(duì)象一同保存的數(shù)據(jù)准夷,里面的messages對(duì)我們要發(fā)送的數(shù)據(jù)做了一個(gè)拷貝钥飞,因?yàn)樵诤罄m(xù)的發(fā)送過(guò)程中,它會(huì)被修改衫嵌。client需要發(fā)送什么读宙,已經(jīng)發(fā)送了什么以及已經(jīng)接收了什么都要進(jìn)行追蹤,總共要發(fā)送的數(shù)據(jù)字節(jié)數(shù)也保存在了data對(duì)象中楔绞。

def service_connection(key, mask):

sock = key.fileobj

data = key.data

if mask & selectors.EVENT_READ:

recv_data = sock.recv(1024) # Should be ready to read

if recv_data:

print('received', repr(recv_data), 'from connection', data.connid)

data.recv_total += len(recv_data)

if not recv_data or data.recv_total == data.msg_total:

print('closing connection', data.connid)

sel.unregister(sock)

sock.close()

if mask & selectors.EVENT_WRITE:

if not data.outb and data.messages:

data.outb = data.messages.pop(0)

if data.outb:

print('sending', repr(data.outb), 'to connection', data.connid)

sent = sock.send(data.outb) # Should be ready to write

data.outb = data.outb[sent:]

client要追蹤來(lái)自server的數(shù)據(jù)字節(jié)數(shù)结闸,如果收到的數(shù)據(jù)字節(jié)數(shù)和發(fā)送的相等,或者有一次沒(méi)有收到數(shù)據(jù)酒朵,說(shuō)明數(shù)據(jù)接收完成桦锄,本次服務(wù)目的已經(jīng)達(dá)成,就可以關(guān)閉這次連接了蔫耽。

data.outb用來(lái)維護(hù)發(fā)送的數(shù)據(jù)结耀,前面提到過(guò),一次發(fā)送不一定能將數(shù)據(jù)全部送出针肥,使用data.outb = data.outb[sent:]來(lái)更新數(shù)據(jù)的發(fā)送饼记。發(fā)送完畢后,再messages中取出數(shù)據(jù)準(zhǔn)備再次發(fā)送慰枕。

可以在這里看到最后的完整代碼:

server.py

client.py

最后的運(yùn)行效果如下:

還是要先啟動(dòng)server具则,進(jìn)入監(jiān)聽(tīng)狀態(tài),然后client啟動(dòng)具帮,與server建立兩條連接博肋,要發(fā)送的信息有兩條,這里分開(kāi)發(fā)送蜂厅,先將fist message分別發(fā)送到server匪凡,然后再發(fā)送second message。server端收到信息后進(jìn)行暫時(shí)保存掘猿,當(dāng)兩條信息都收到了才開(kāi)始進(jìn)行echo病游,client端收到完整信息后表示服務(wù)結(jié)束,斷開(kāi)連接。

注:喜歡python + qun:839383765 可以獲取Python各類免費(fèi)最新入門學(xué)習(xí)資料衬衬!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末买猖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滋尉,更是在濱河造成了極大的恐慌玉控,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狮惜,死亡現(xiàn)場(chǎng)離奇詭異高诺,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)碾篡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門虱而,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人耽梅,你說(shuō)我怎么就攤上這事薛窥。” “怎么了眼姐?”我有些...
    開(kāi)封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵诅迷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我众旗,道長(zhǎng)罢杉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任贡歧,我火速辦了婚禮滩租,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘利朵。我一直安慰自己律想,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布绍弟。 她就那樣靜靜地躺著技即,像睡著了一般。 火紅的嫁衣襯著肌膚如雪樟遣。 梳的紋絲不亂的頭發(fā)上而叼,一...
    開(kāi)封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音豹悬,去河邊找鬼葵陵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瞻佛,可吹牛的內(nèi)容都是我干的脱篙。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼涡尘!你這毒婦竟也來(lái)了忍弛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤考抄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蔗彤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體川梅,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年然遏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贫途。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡待侵,死狀恐怖丢早,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秧倾,我是刑警寧澤怨酝,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站那先,受9級(jí)特大地震影響农猬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜售淡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一斤葱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧揖闸,春花似錦揍堕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蹲嚣,卻和暖如春递瑰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背隙畜。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工抖部, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人议惰。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓慎颗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俯萎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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

  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會(huì)了寫python代碼夫啊,假如你寫了兩個(gè)python文件a.py和b.py函卒,分別去運(yùn)...
    go以恒閱讀 1,999評(píng)論 0 6
  • 說(shuō)明 本文 翻譯自 realpython 網(wǎng)站上的文章教程 Socket Programming in Pytho...
    keelii閱讀 2,111評(píng)論 0 16
  • 大綱 一.Socket簡(jiǎn)介 二.BSD Socket編程準(zhǔn)備 1.地址 2.端口 3.網(wǎng)絡(luò)字節(jié)序 4.半相關(guān)與全相...
    VD2012閱讀 2,303評(píng)論 0 5
  • Python socket編程 引言 sockets的歷史悠久,它們最早在 1971 年的 APPANET 中使用...
    MetaT1an閱讀 621評(píng)論 0 0
  • 今夜,我給已畫的兩幅和未來(lái)要畫的幾十幅熊榛,取名叫花畫锚国。 手法依然笨拙,但感覺(jué)有兩筆有了一點(diǎn)點(diǎn)呼吸玄坦。 在極度煩躁的...
    好日子天天有閱讀 391評(píng)論 0 3