A network socket is an endpoint of a connection across a computer network. Today, most communication between computers is based on the Internet Protocol; therefore most network sockets are Internet sockets. More precisely, a socket is a handle (abstract reference) that a local program can pass to the networking application programming interface (API) to use the connection, for example "send this data on this socket".html
For example, to send "Hello, world!" via TCP to port 80 of the host with address 1.2.3.4, one might get a socket, connect it to the remote host, send the string, then close the socket.python
實現一個socket至少要分如下幾步,(僞代碼)linux
1
2
3
4
|
Socket socket
=
getSocket(
type
=
"TCP"
)
#設定好協議類型
connect(socket, address
=
"1.2.3.4"
, port
=
"80"
)
#鏈接遠程機器
send(socket,
"Hello, world!"
)
#發送消息
close(socket)
#關閉鏈接
|
A socket API is an application programming interface (API), usually provided by the operating system, that allows application programs to control and use network sockets. Internet socket APIs are usually based on the Berkeley sockets standard. In the Berkeley sockets standard, sockets are a form of file descriptor (a file handle), due to the Unix philosophy that "everything is a file", and the analogies between sockets and files: you can read, write, open, and close both. 緩存
A socket address is the combination of an IP address and a port number, much like one end of a telephone connection is the combination of a phone number and a particular extension. Sockets need not have an address (for example for only sending data), but if a program binds a socket to an address, the socket can be used to receive data sent to that address. Based on this address, internet sockets deliver incoming data packets to the appropriate application process or thread.服務器
socket.
AF_UNIX unix本機進程間通訊
網絡
socket.
AF_INET IPV4
多線程
socket.
AF_INET6 IPV6
併發
These constants represent the address (and protocol) families, used for the first argument to
socket()
. If the AF_UNIX
constant is not defined then this protocol is unsupported. More constants may be available depending on the system.app
socket.
SOCK_STREAM #for tcp
dom
socket.
SOCK_DGRAM #for udp
socket.
SOCK_RAW #原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠;其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。
socket.
SOCK_RDM #是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在須要執行某些特殊操做時使用,如發送ICMP報文。SOCK_RAM一般僅限於高級用戶或管理員運行的程序使用。
socket.
SOCK_SEQPACKET #廢棄了
These constants represent the socket types, used for the second argument to socket()
. More constants may be available depending on the system. (Only SOCK_STREAM
and SOCK_DGRAM
appear to be generally useful.)
socket.
socket
(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) 必會Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET
(the default), AF_INET6
, AF_UNIX
, AF_CAN
or AF_RDS
. The socket type should beSOCK_STREAM
(the default), SOCK_DGRAM
, SOCK_RAW
or perhaps one of the other SOCK_
constants. The protocol number is usually zero and may be omitted or in the case where the address family is AF_CAN
the protocol should be one of CAN_RAW
or CAN_BCM
. If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd()
, fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close()
.
socket.
socketpair
([family[, type[, proto]]])
Build a pair of connected socket objects using the given address family, socket type, and protocol number. Address family, socket type, and protocol number are as for the socket()
function above. The default family is AF_UNIX
if defined on the platform; otherwise, the default is AF_INET
.
socket.
create_connection
(address[, timeout[, source_address]])
Connect to a TCP service listening on the Internet address (a 2-tuple (host, port)
), and return the socket object. This is a higher-level function than socket.connect()
: if host is a non-numeric hostname, it will try to resolve it for both AF_INET
and AF_INET6
, and then try to connect to all possible addresses in turn until a connection succeeds. This makes it easy to write clients that are compatible to both IPv4 and IPv6.
Passing the optional timeout parameter will set the timeout on the socket instance before attempting to connect. If no timeout is supplied, the global default timeout setting returned by getdefaulttimeout()
is used.
If supplied, source_address must be a 2-tuple (host, port)
for the socket to bind to as its source address before connecting. If host or port are ‘’ or 0 respectively the OS default behavior will be used.
socket.
getaddrinfo
(host, port, family=0, type=0, proto=0, flags=0) #獲取要鏈接的對端主機地址 必會
sk.bind(address) 必會
s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog) 必會
開始監聽傳入鏈接。backlog指定在拒絕鏈接以前,能夠掛起的最大鏈接數量。
backlog等於5,表示內核已經接到了鏈接請求,但服務器尚未調用accept進行處理的鏈接個數最大爲5
這個值不能無限大,由於要在內核中維護鏈接隊列
sk.setblocking(bool) 必會
是否阻塞(默認True),若是設置False,那麼accept和recv時一旦無數據,則報錯。
sk.accept() 必會
接受鏈接並返回(conn,address),其中conn是新的套接字對象,能夠用來接收和發送數據。address是鏈接客戶端的地址。
接收TCP 客戶的鏈接(阻塞式)等待鏈接的到來
sk.connect(address) 必會
鏈接到address處的套接字。通常,address的格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤。
sk.connect_ex(address)
同上,只不過會有返回值,鏈接成功時返回 0 ,鏈接失敗時候返回編碼,例如:10061
sk.close() 必會
關閉套接字
sk.recv(bufsize[,flag]) 必會
接受套接字的數據。數據以字符串形式返回,bufsize指定最多能夠接收的數量。flag提供有關消息的其餘信息,一般能夠忽略。
sk.recvfrom(bufsize[.flag])
與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
sk.send(string[,flag]) 必會
將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容所有發送。
sk.sendall(string[,flag]) 必會
將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常。
內部經過遞歸調用send,將全部內容發送出去。
sk.sendto(string[,flag],address)
將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。
sk.settimeout(timeout) 必會
設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如 client 鏈接最多等待5s )
sk.getpeername() 必會
返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。
sk.getsockname()
返回套接字本身的地址。一般是一個元組(ipaddr,port)
sk.fileno()
套接字的文件描述符
socket.
sendfile
(file, offset=0, count=None)
發送文件 ,但目前多數狀況下並沒有什麼卵用。
前面講了這麼多,到底咋麼用呢?
上面的代碼的有一個問題, 就是SocketServer.py運行起來後, 接收了一次客戶端的data就退出了。。。, 但實際場景中,一個鏈接創建起來後,可能要進行屢次往返的通訊。
屢次的數據交互怎麼實現呢?
實現了屢次交互, 棒棒的, 但你會發現一個小問題, 就是客戶端一斷開,服務器端就進入了死循環,爲啥呢?
看客戶端斷開時服務器端的輸出
1
2
3
4
5
6
7
8
9
|
等待客戶端的鏈接...
新鏈接: (
'127.0.0.1'
,
62722
)
收到消息: b
'hey'
收到消息: b
'you'
收到消息: b''
#客<span style="color: #ff0000;">戶端一斷開,服務器端就收不到數據了,可是不會報錯,就進入了死循環模式。。</span>。
收到消息: b''
收到消息: b''
收到消息: b''
收到消息: b''
|
知道了緣由就好解決了,只須要加個判斷服務器接到的數據是否爲空就行了,爲空就表明斷了。。。
上面的代碼雖然實現了服務端與客戶端的屢次交互,可是你會發現,若是客戶端斷開了, 服務器端也會跟着馬上斷開,由於服務器只有一個while 循環,客戶端一斷開,服務端收不到數據 ,就會直接break跳出循環,而後程序就退出了,這顯然不是咱們想要的結果 ,咱們想要的是,客戶端若是斷開了,咱們這個服務端還能夠爲下一個客戶端服務,它不能斷,她接完一個客,擦完嘴角的遺留物,就要接下來勇敢的去接待下一個客人。 在這裏如何實現呢?
1
|
conn,addr
=
server.accept()
#接受並創建與客戶端的鏈接,程序在此處開始阻塞,只到有客戶端鏈接進來...
|
咱們知道上面這句話負責等待並接收新鏈接,對於上面那個程序,其實在while break以後,只要讓程序再次回到上面這句代碼這,就可讓服務端繼續接下一個客戶啦。
注意了, 此時服務器端依然只能同時爲一個客戶服務,其客戶來了,得排隊(鏈接掛起),不能玩 three some. 這時你說想,我就想玩3p,就想就想嘛,其實也能夠,多交錢嘛,繼續往下看,後面開啓新姿式後就能夠玩啦。。。
光只是簡單的發消息、收消息沒意思,乾點正事,能夠作一個極簡版的ssh,就是客戶端鏈接上服務器後,讓服務器執行命令,並返回結果給客戶端。
very cool , 這樣咱們就作了一個簡單的ssh , 但多試幾條命令你就會發現,上面的程序有如下2個問題。
在開始解決上面問題3以前,咱們要考慮,客戶端要循環接收服務器端的大量數據返回直到一條命令的結果所有返回爲止, 但問題是客戶端知道服務器端返回的數據有多大麼?答案是不知道,那既然不知道服務器的要返回多大的數據,那客戶端怎麼知道要循環接收多少次呢?答案是不知道,擦,那咋辦? 總不能靠猜吧?呵呵。。。 固然不能,那隻能讓服務器在發送數據以前主動告訴客戶端,要發送多少數據給客戶端,而後再開始發送數據,yes, 機智如我,搞起。
先簡單測試接收數據量大小
1
2
3
4
5
6
7
8
9
|
結果輸出:<br>
/
Library
/
Frameworks
/
Python.framework
/
Versions
/
3.5
/
bin
/
python3.
5
/
Users
/
jieli
/
PycharmProjects
/
python基礎
/
自動化day8socket
/
sock_client.py
>>:cat
/
var
/
log
/
system.log
getting cmd result , b
'3472816Sep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate image path: /var/vm/sleepimage\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: efi pagecount 65\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate_page_list_setall(preflight 1) start\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate_page_list_setall time: 211 ms\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: pages 1211271, wire 225934, act 399265, inact 4, cleaned 0 spec 97, zf 3925, throt 0, compr 218191, xpmapped 40000\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: could discard act 94063 inact 129292 purgeable 58712 spec 81788 cleaned 0\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: WARNING: hibernate_page_list_setall skipped 47782 xpmapped pages\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate_page_list_setall preflight pageCount 225934 est comp 41 setfile 421527552 min 1073741824\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: kern_open_file_for_direct_io(0)\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: kern_open_file_for_direct_io took 181 ms\nSep 9 '
Traceback (most recent call last):
File
"/Users/jieli/PycharmProjects/python基礎/自動化day8socket/sock_client.py"
, line
17
,
in
<module>
total_rece_size
=
int
(res_return_size)
ValueError: invalid literal
for
int
() with base
10
: b'
3472816Sep
9
09
:
06
:
37
Jies
-
MacBook
-
Air kernel[
0
]: hibernate image path:
/
var
/
vm
/
sleepimage\nSep
9
09
:
06
:
37
Jies
-
MacBook
-
Air kernel[
0
]: efi pagecount
65
\nSep
9
09
:
06
:
37
Jies
-
MacBook
-
Air kernel[
0
]:
Process finished with exit code
1
|
看程序執行報錯了, 我在客戶端本想只接服務器端命令的執行結果,但實際上卻連命令結果也跟着接收了一部分。 這是爲何呢???服務器不是隻send告終果的大小麼?不該該只是個數字麼?尼瑪命令結果不是第2次send的時候才發送的麼??,擦,擦,擦,價值觀都要崩潰了啊。。。。
哈哈,這裏就引入了一個重要的概念,「粘包」, 即服務器端你調用時send 2次,但你send調用時,數據其實並無馬上被髮送給客戶端,而是放到了系統的socket發送緩衝區裏,等緩衝區滿了、或者數據等待超時了,數據纔會被send到客戶端,這樣就把好幾回的小數據拼成一個大數據,統一發送到客戶端了,這麼作的目地是爲了提升io利用效率,一次性發送總比連發好幾回效率高嘛。 但也帶來一個問題,就是「粘包」,即2次或屢次的數據粘在了一塊兒統一發送了。就是咱們上面看到的狀況 。
咱們在這裏必需要想辦法把粘包分開, 由於不分開,你就沒辦法取出來服務器端返回的命令執行結果的大小呀。so ,那怎麼分開呢?首先你是沒辦法讓緩衝區強制刷新把數據發給客戶端的。 你能作的,只有一個。就是,讓緩衝區超時,超時了,系統就不會等緩衝區滿了,會直接把數據發走,由於不能一個勁的等後面的數據呀,等過久,會形成數據延遲了,那但是極很差的。so若是讓緩衝區超時呢?
答案就是:
The socketserver
module simplifies the task of writing network servers.
socketserver一共有這麼幾種類型
1
|
class
socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate
=
True
)
|
This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server.
1
|
class
socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate
=
True
)
|
This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer
.
1
2
|
class
socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate
=
True
)
class
socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate
=
True
)
|
These more infrequently used classes are similar to the TCP and UDP classes, but use Unix domain sockets; they’re not available on non-Unix platforms. The parameters are the same as for TCPServer
.
There are five classes in an inheritance diagram, four of which represent synchronous servers of four types:
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
建立一個socketserver 至少分如下幾步:
BaseRequestHandler
class and overriding its handle()
method; this method will process incoming requests. handle_request()
orserve_forever()
method of the server object to process one or many requests.server_close()
to close the socket.基本的socketserver代碼
但你發現,上面的代碼,依然不能同時處理多個鏈接,擦,那我搞這個幹嗎?別急,不是不能處理多併發,若是你想,你還要啓用多線程,多線程咱們如今還沒講,但你大致知道,有了多線程,就能同時讓cpu幹多件事了就行先。
讓你的socketserver併發起來, 必須選擇使用如下一個多併發的類
class socketserver.
ForkingTCPServer
class socketserver.
ForkingUDPServer
class socketserver.
ThreadingTCPServer
class socketserver.
ThreadingUDPServer
so 只須要把下面這句
1
|
server
=
socketserver.TCPServer((HOST, PORT), MyTCPHandler)
|
換成下面這個,就能夠多併發了,這樣,客戶端每連進一個來,服務器端就會分配一個新的線程來處理這個客戶端的請求
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
class socketserver.
BaseServer
(server_address, RequestHandlerClass) 主要有如下方法