socket()模塊和套接字對象的內建方法


一、socket()模塊函數

要使用socket.socket()函數來創建套接字,其語法如下:

socket(socket_family,socket_type,protocol=0)

如上所述,scoket_family不是AF_UNIX就是AF_INET,scoket_type可以是SOCK_STREAM或SOCK_DGRAM,protocol一般不填,默認值為0.

創建一個TCP/IP套接字,你要這樣調用socket.socket():

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

同樣的,創建一個UDP/IP的套接字,你要這樣:

udpsock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

由於socket有太多屬性,我們一般使用from import socket * 語句,將所有屬性導入命名空間。

 

二、套接字對象內建方法

下面是一些套接字對象常用的方法。

服務器端套接字函數
函數 描述
s.bind() 綁定地址(主機名、端口號對)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶端連接,(阻塞式)等待連接的到來

 

 

 

 

 

客戶端套接字函數
函數 描述
s.connect() 主動初始化TCP服務器連接
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

 

 

 

 

公共用途的套接字函數
函數 描述
s.recv() 接受TCP數據
s.send() 發送TCP數據
s.sendall() 完整發送TCP數據
s.recvfrom() 接受UDP數據
s.sendto() 發送UDP數據
s.getpeername() 連接到當前套接字的遠端地址(TCP連接)
s.getsockname() 當前套接字的地址
s.getsockopt() 返回指定套接字的參數
s.setsockopt() 設定指定套接字的參數
s.close() 關閉套接字

 

 

 

 

 

 

 

 

 

 

面向模塊的套接字函數
函數 描述
s.settimeout() 設置阻塞套接字操作的超時時間
s.gettimeout() 得到阻塞套接字操作的超時時間
s.setblocking() 設置套接字的阻塞與非阻塞模式

 

 

 

 

 

面向文件的套接字函數
函數 描述
s.fileno() 套接字的文件描述符
s.makefile() 創建一個與該套接字關聯的文件對象

 

 

 

 

提示:在運行網絡應用程序時,最好在不同的電腦上執行服務器和客戶端的程序。

 

三、創建TCP服務器和TCP客戶端

根據上面的介紹,現在我們應該能創建一個完整的通信模型了。下面是理論上的偽代碼:

1.套接字理論模型

先來創建一個TCP服務器

#創建一個TCP服務器
  ss = socket()    #創建服務器套接字
  ss.bind()           #把地址綁定到套接字上
  ss.listen()         #監聽連接
  inf_loop:           #服務器無線循環
      cone,addr = ss.accept()    #接收客戶端連接
  comm_loop:      #通信循環
      cone.recv()/cs.send()    #對話(接受與發送)
  cone.close()      #關閉客戶端套接字
ss.close()          #關閉服務器套接字(可選)

所有的套接字都用socket.socket()函數來創建。服務器需要“坐在某個端口上”等待請求。所以它們必須要綁定到一個本地的地址上。

由於TCP是一個面向連接的通信系統,在TCP服務器可以開始工作之前,要先完成一些設置。

TCP服務器必須監聽(進來的)連接,設置完成之后,服務器就可以進入無線循環了。

一個簡單的(單線程的)服務器會調用accept()函數等待連接的到來,

默認情況下,accept()函數是阻塞式的,即程序在連接到來之前會處於掛起狀態。套接字也支持非阻塞模式。

一旦接收到一個連接,accept·()函數就會返回一個單獨的客戶端套接字用於后續的通信。

使用新的客戶端套接字就像把客戶的電話轉給一個客戶服務人員。當一個客戶打電話進來的時候,總機接了電話,然后把電話轉到合適的人那里來處理客戶的需求。

這樣就可以空出主機,也就是最初的那個服務器套接字。

當客戶端連接關閉后,服務器繼續等待下一個客戶端的連接。代碼的最后一行會把服務器套接字關閉,由於是無限循環也許用不到。

 

再來創建一個TCP客戶端

#創建一個TCP客戶端
ss = socket()   #創建一個客戶端套接字
ss.connect()    #嘗試連接服務器
comm_loop:     #通信循環
    cs.send()/cs.recv()    #對話(接受或發送)
cs.close()         #關閉客戶端套接字

在客戶端有了套接字之后,馬上就可以調用connect()函數去連接服務器。連接建立之后,就可以與服務器開始對話了,對話結束后,客戶端就可以關閉套接字,結束連接

 

2.建立一個單一的連接

#服務器端
from socket import *
cs = socket(AF_INET,SOCK_STREAM)
cs.bind(('127.0.0.1',8888))
cs.listen(5)

print("Wait for......")
anne,addr = cs.accept()
print(anne)
print(addr)
anne.close()
#客戶端
from
socket import * cl = socket(AF_INET,SOCK_STREAM) cl.connect(("127.0.0.1",8888)) cl.send("Hello,world".encode("utf-8")) cl.close()

先啟動服務器:

Wait for......   #accept處於等待狀態

然后執行客戶端,看服務器端的變化:

<socket.socket fd=384, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8888), raddr=('127.0.0.1', 54883)>
('127.0.0.1', 54883)  #客戶端IP地址和端口號

 

上面的的代碼有點單一,有多個客戶端同時訪問該如何?這就該用到后面的多線程,稍后會講,這里有另外的折中代碼,可以一直訪問,雖然一次只能訪問一個。

#服務器端
from socket import *
cs = socket(AF_INET,SOCK_STREAM)
cs.bind(("127.0.0.1",8888))
cs.listen(5)
print("Have Listen")

while True:
    cone,addr = cs.accept()
    while True:
        data = cone.recv(1024)
        if len(data) == 0:break   #如果收到TCP消息,則關閉客戶端套接字
        print(data.decode("utf-8"))
        cone.send(data.upper())
    cone.close()
cs.close()

#客戶端
from socket import *
cs = socket(AF_INET,SOCK_STREAM)
cs.connect(("127.0.0.1",8888))
while True:
    ssg = input(">>>").strip()
    if not ssg:continue   #避免空格造成的停頓
    cs.send(ssg.encode("utf-8"))  #
    data = cs.recv(1024)
    print(data.decode("utf-8"))  #
cs .close()

 

下面是在linux下的版本測試:

#服務端
#!/usr/bin/env python
#coding:utf-8

from socket import *
import time

HOST = '192.168.43.131'
PORT = 8808
BUFSIZ = 1024
ADDR = (HOST,PORT)

tcpser = socket(AF_INET,SOCK_STREAM)
tcpser.bind(ADDR)
tcpser.listen(5)

while True:
    print "等待連接......"
    anne,addr = tcpser.accept()
    print "...連接:",addr
    while True:
        data = anne.recv(BUFSIZ)
        if not data:
            break
        anne.send('[%s] %s' % (time.strftime('%c'),data))
        anne.close()
tcpser.close()

#客戶端
#!/usr/bin/env python

from socket import *

HOST = '192.168.43.131'
PORT = 8088
BUFSIZ = 1024
ADDR = (HOST,PORT)

tcpcli = socket(AF_INET,SOCK_STREAM)
tcpcli.connect(ADDR)

while True:
    data = input(">>>")
    if not data:
        continue
    tcpcli.send(data.encode("utf-8"))
    data = tcpcli.recv(BUFSIZ)
    if not data:
        break
    print(data.decode("utf-8"))
tcpcli.close()

    

 

四、創建UDP服務器和UDP客戶端

由於UDP服務器不是面向連接的,所以不用像TCP服務器那樣做那么多設置工作。

創建一個UDP服務器

#創建UDP服務器
ss = socket()  #創建一個服務器套接字
ss.bind()       #綁定服務器套接字
inf_loop:       #服務器無限循環
    cs = ss.recvfrom()/ss.sendto()   #對話(接收與發送)
ss.close()      #關閉服務器套接字

從偽代碼中可以看出,使用的還是那套先創建套接字然后綁定到本地地址(主機/端口)的方法,無限循環中包含了從客戶接受消息。

創建一個TCP服務器

#創建一個UDP服務器
cs = socket()     #創建客戶端套接字
comm_loop:      #通訊循環
    cs.sendto()/cs.recvfrom()   #對話(發送/接收)
cs.close()   #關閉客戶端套接字

 

創建一個真實的案例:

#創建一個服務器
from socket import *

HOST = "127.0.0.1"
PORT = 8989
BUFSIZ = 1024
ADDR = (HOST,PORT)

udpser = socket(AF_INET,SOCK_DGRAM)
udpser.bind(ADDR)

while True:
    print("等待請求......")
    conn,addr = udpser.recvfrom(BUFSIZ)  #接收到的消息無需轉交
    udpser.sendto( conn.upper(),addr)   #需要的話返回一個結果就可以了
    print("...來自",addr)

udpser.close()

UDP和TCP服務器的另一個重要的區別是,由於數據報套接字是無連接的,所以無法把客戶端連接交給另外的套接字進行后續的通訊。

這些服務器只是接收消息,需要的話,給客戶端返回一個結果就可以了。

#創建一個客戶端服務器
from socket import *

HOST = "127.0.0.1"
PORT = 8989
BUFSIZ = 1024
ADDR = (HOST,PORT)

udpcli = socket(AF_INET,SOCK_DGRAM)

while True:
    data = input(">>>")
    if not data:
        break
    udpcli.sendto(data.encode("utf-8"),ADDR)
    data,ADDR = udpcli.recvfrom(BUFSIZ)
    if not data:
        continue
    print(data.decode("utf-8"))

udpcli.close()

UDP客戶端的循環基本上與TCP客戶端的完全一樣。唯一的區別就是,我們不用先去跟UDP服務器建立連接,而是直接把消息發送出去,然后等待服務器的回復。

還可以,創建多個客戶端,UDP不同於TCP需要建立連接。

#服務端
from socket import *
server = socket(AF_INET,SOCK_DGRAM)
server.bind(('127.0.0.1',9100))
while True:
    conn,addr = server.recvfrom(1024)
    print("訪問來自%s,端口號是:%s" % (addr[0],addr[1]))
    server.sendto(conn.upper(),addr)   #返回消息的時候,必須指定端口號和ip

#客戶端1
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
while True:
    data = input(">>>")   #發送空格也行,不會報錯,一次發送,也不會占用資源
    client.sendto(data.encode("utf-8"),('127.0.0.1',9100))
    conn,addr = client.recvfrom(1024)
    print(conn.decode('utf-8'))

#客戶端2
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
while True:
    data = input(">>>")
    client.sendto(data.encode("utf-8"),('127.0.0.1',9100))
    conn,addr = client.recvfrom(1024)
    print(conn.decode('utf-8'))

執行結果:

訪問來自127.0.0.1,端口號是:60715
訪問來自127.0.0.1,端口號是:60716

 

小結:

總的來說,UDP和TCP服務的流程相同,有兩點:UDP無需提前連接直接發送消息,UDP服務器無法把客戶端的連接轉交出去。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM