Python網絡編程篇之socket


1 socket

插座?呵呵,想多了,翻譯過來意思是套接字!

A network socket is an internal endpoint for sending or receiving data at a single node in a computer network. Concretely, it is a representation of this endpoint in networking software (protocol stack), such as an entry in a table (listing communication protocol, destination, status, etc.), and is a form of system resource.

socket是一種進程間通信機制(inter process communication ,IPC),提供一種供應用程序訪問通信協議的操作系統調用,並且通過將socket與Unix系統文件描述符相整合,使得網絡讀寫數據(或者服務調用)和讀寫本地文件一樣容易。很顯然,這貨已經離插座越來越遠了,已經完全不再是硬件上的物件,而是一序列的“指令” ,按漢語的理解,已經具備了“套接”(建立網絡通訊或進程間通訊)和“字”(可交互的有序指令串)的概念。
2 socket address  :主機-端口對
如果一個套接字像一個電話插孔——允許通信的一些基礎設施,name主機名和端口號就像區號和電話號碼的組合。
有效的端口號范圍0-65535,盡管小於1024的端口號預留給了系統,Linux和mac os 可以在/etc/services文件中找到預留端口號的列表。
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.
有兩種類型的套接字:基於文件的和面向網絡的
AF:address family地址家族
AF_UNIX 面向文件
AF_INET面向網絡

參數一:地址簇

  socket.AF_INET IPv4(默認)
  socket.AF_INET6 IPv6

  socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信

參數二:類型

  socket.SOCK_STREAM  流式socket , for TCP (默認)
  socket.SOCK_DGRAM   數據報式socket , 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 可靠的連續數據包服務

參數三:協議

  0  (默認)與特定的地址家族相關的協議,如果是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議

3.面向連接的套接字也稱為虛擬電路或流套接字

面向連接的通信提供序列化的、可靠的和不重復的數據交付,並且沒有記錄邊界,實現這種連接的主要協議是TCP(傳輸控制協議)。

創建TCP套接字必須使用SOCK_STREAM作為套接字類型。

無連接的傳輸無法保證傳輸的內容的順序性、可靠性。無連接傳輸的優勢是沒有維護虛擬電路連接帶來的開銷,從而擁有更低的成本。實現無連接的主要協議是UDP(用戶數據報協議),創建UDP套接字必須使用SOCK_DGRAM作為套接字類型。

4.Socket 參數介紹

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()
  套接字的文件描述符

 

5.創建socket

socket.socket(AddressFamily, Type) 

函數 socket.socket 創建⼀個 socket, 返回該 socket 的描述符, 該函數帶有兩個參數:

Address Family: 可以選擇 AF_INET( ⽤於 Internet 進程間通信) 或者AF_UNIX( ⽤於同⼀台機器進程間通信) ,實際⼯作中常⽤AF_INET

Type: 套接字類型, 可以是 SOCK_STREAM( 流式套接字, 主要⽤於TCP 協議) 或者 SOCK_DGRAM( 數據報套接字, 主要⽤於 UDP 協議)創建⼀個tcp socket( tcp套接字)

5.1創建⼀個tcp sockettcp套接字)

TCP通信需要建立一個可靠連接的過程,而且通信雙方以流的形式發送數據。

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket Created'

tcp_server

# -*- coding: utf-8 -*-
# 2017/11/25 16:31
import socket
import threading
import time
def dealClient(sock, addr):
    #第四步:接收傳來的數據,並發送給對方數據
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Hello,I am server!')
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        print('-->>%s!' % data.decode('utf-8'))
        sock.send(('Loop_Msg: %s!' % data.decode('utf-8')).encode('utf-8'))
    #第五步:關閉套接字
    sock.close()
    print('Connection from %s:%s closed.' % addr)

if __name__=="__main__":
    #第一步:創建一個基於IPv4和TCP協議的Socket
    # 套接字綁定的IP(127.0.0.1為本機ip)與端口
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('127.0.0.1', 9999))
    #第二步:監聽連接
    s.listen(5)
    print('Waiting for connection...')
    while True:
        # 第三步:接受一個新連接:
        sock, addr = s.accept()
        # 創建新線程來處理TCP連接:
        t = threading.Thread(target=dealClient, args=(sock, addr))
        t.start()

tcp_client

# -*- coding: utf-8 -*-
# 2017/11/25 16:32
import socket
#初始化Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#連接目標的ip和端口
s.connect(('127.0.0.1', 9999))
# 接收消息
print('-->>'+s.recv(1024).decode('utf-8'))
# 發送消息
s.send(b'Hello,I am a client')
print('-->>'+s.recv(1024).decode('utf-8'))
s.send(b'exit')
#關閉套接字
s.close()

5.2創建⼀個udp socketudp套接字)

使用UDP協議時,不需要建立連接,只需要知道對方的ip和port,就可以直接發數據包,但是不關心是否能到達目的端。

UDP --- 用戶數據報協議, 是一個無連接的簡單的面向數據報的運輸層協議。
UDP不提供可靠性, 它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。
由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,傳輸速度很快。
UDP是一種面向無連接的協議, 每個數據報都是一個獨立的信息,包括完整的源地址或目的的地址,
它在網絡上以任何可能的路徑傳往目的地, 因此能否到達⽬的地, 到達目的地的時間以及內容的正確性都是不能被保證的。

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print 'Socket Created'

udp_server

# -*- coding: utf-8 -*-
# 2017/11/25 16:38
import socket
#創建Socket,綁定指定的ip和端口
#SOCK_DGRAM指定了這個Socket的類型是UDP。綁定端口和TCP一樣。
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 9999))
print('Bind UDP on 9999...')
while True:
    # 直接發送數據和接收數據
    data, addr = s.recvfrom(1024)
    print('Received from %s:%s.' %(addr,data))
    s.sendto(b'Hello',addr)

udp_client

# -*- coding: utf-8 -*-
# 2017/11/25 16:39
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Hello', b'World']:
    # 發送數據:
    s.sendto(data, ('127.0.0.1', 9999))
    # 接收數據:
    print(s.recv(1024).decode('utf-8'))
s.close()

一下均基於tcp開發:

一對一

server

#__author: greg
#date: 2017/9/16 16:11
import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)#最大排隊數,能開多少人
print ("服務端啟動...")
# conn,addr= sk.accept()
# while True:
#     client_data=conn.recv(1024)
#     if str(client_data,"utf8")=='exit':
#         break
#     print (str(client_data,"utf8"))
#     server_response=input(">>>")
#     conn.sendall(bytes(server_response,"utf8"))
# conn.close()

while True:
    conn,address = sk.accept()
    print(address)
    while True:
        try:
            client_data=conn.recv(1024)
        except:
            print("意外中斷")
            break
        print (str(client_data,"utf8"))
        server_response=input(">>>")
        conn.sendall(bytes(server_response,"utf8"))
    conn.close()

client

#__author: greg
#date: 2017/9/16 16:11
import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.connect(ip_port)
print ("客戶端啟動:")
# while True:
#     inp = input('>>>')
#     sk.sendall(bytes(inp,"utf8"))
#     if inp == 'exit':
#         break
#     server_response=sk.recv(1024)
#     print (str(server_response,"utf8"))
# sk.close()

while True:
    inp=input('>>>')
    if inp=="exit":
        break
    sk.send(bytes(inp,'utf8'))
    data=sk.recv(1024)
    print(str(data,"utf8"))
sk.close()

一對多,簡單並發

server

#__author: greg
#date: 2017/9/16 16:27
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        print ("服務端啟動...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:
                client_data=conn.recv(1024)
                print (str(client_data,"utf8"))
                print ("waiting...")
                conn.sendall(client_data)
            conn.close()
            
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer)
    server.serve_forever()

client

#__author: greg
#date: 2017/9/16 16:27

import socket

ip_port = ('127.0.0.1',8091)
sk = socket.socket()
sk.connect(ip_port)
print ("客戶端啟動:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    if inp == 'exit':
        break
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

聊天並發實例

server

#__author: greg
#date: 2017/9/16 20:55
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self): #handle父類有handle方法
        print ("服務端啟動...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:
                client_data=conn.recv(1024)
                print (str(client_data,"utf8"))
                print ("waiting...")
                server_response=input(">>>")
                conn.sendall(bytes(server_response,"utf8"))
                # conn.sendall(client_data)
            conn.close()
            # print self.request,self.client_address,self.server
            
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8098),MyServer)
    server.serve_forever()

client

#__author: greg
#date: 2017/9/16 20:54
import socket
ip_port = ('127.0.0.1',8098)
sk = socket.socket()
sk.connect(ip_port)
print ("客戶端啟動:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
    if inp == 'exit':
        break
sk.close()

應用:

傳送命令:

cmd_server:

 1 #__author: greg
 2 #date: 2017/9/16 22:21
 3 import socket
 4 ip_port = ('127.0.0.1', 8879)
 5 sk = socket.socket()
 6 sk.connect(ip_port)
 7 print("客戶端啟動:")
 8 while True:
 9     inp = input('cdm:>>>').strip()
10     if len(inp) == 0:
11         continue
12     if inp == "q":
13         break
14     sk.sendall(bytes(inp, "utf8"))
15     server_response = sk.recv(1024)
16     print(str(server_response, "gbk"))
17     print('receive data size', len(server_response))
18     if inp == 'exit':
19         break
20 sk.close()
View Code

cmd_client

 1 #__author: greg
 2 #date: 2017/9/16 22:21
 3 import socket
 4 ip_port = ('127.0.0.1', 8879)
 5 sk = socket.socket()
 6 sk.connect(ip_port)
 7 print("客戶端啟動:")
 8 while True:
 9     inp = input('cdm:>>>').strip()
10     if len(inp) == 0:
11         continue
12     if inp == "q":
13         break
14     sk.sendall(bytes(inp, "utf8"))
15     server_response = sk.recv(1024)
16     print(str(server_response, "gbk"))
17     print('receive data size', len(server_response))
18     if inp == 'exit':
19         break
20 sk.close()
View Code

sendall會把數據直接全部發送到客戶端,客戶端將所有的數據都放到緩沖區,每次recv多少字節取決於recv內的參數,理論不應該超過8k。

所以,並不能一次recv()無限大數據,所以這里我們應該通過循環去接收。

“粘包”, 即服務器端你調用時send 2次,但你send調用時,數據其實並沒有立刻被發送給客戶端,而是放到了系統的socket發送緩沖區里,等緩沖區滿了、或者數據等待超時了,數據才會被send到客戶端,這樣就把好幾次的小數據拼成一個大數據,統一發送到客戶端了,這么做的目地是為了提高io利用效率,一次性發送總比連發好幾次效率高嘛。 但也帶來一個問題,就是“粘包”,即2次或多次的數據粘在了一起統一發送。

我們在這里必須要想辦法把粘包分開, 因為不分開,你就沒辦法取出來服務器端返回的命令執行結果的大小。

首先你是沒辦法讓緩沖區強制刷新把數據發給客戶端的。

你能做的,只有一個。就是,讓緩沖區超時,超時了,系統就不會等緩沖區滿了,會直接把數據發走,因為不能一個勁的等后面的數據呀,等太久,會造成數據延遲了,那可是極不好的。so如果讓緩沖區超時呢?

  1. time.sleep(0.5),經多次測試,讓服務器程序sleep 至少0.5就會造成緩沖區超時
  2. 不用sleep,服務器端每發送一個數據給客戶端,就立刻等待客戶端進行回應,即調用 conn.recv(1024), 由於recv在接收不到數據時是阻塞的,這樣就會造成,服務器端接收不到客戶端的響應,就不會執行后面的conn.sendall(命令結果)的指令,收到客戶端響應后,再發送命令結果時,緩沖區就已經被清空了,因為上一次的數據已經被強制發到客戶端了。 
#__author: greg
#date: 2017/9/16 22:37
import socketserver
import subprocess
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            conn=self.request
            conn.sendall(bytes("歡迎登錄","utf8"))
            while True:
                client_bytes=conn.recv(1024)
                if not client_bytes:break
                client_str=str(client_bytes,"utf8")
                print(client_str)
                command=client_str
                result_str=subprocess.getoutput(command)
                result_bytes = bytes(result_str,encoding='utf8')
                info_str="info|%d"%len(result_bytes)
                conn.sendall(bytes(info_str,"utf8"))
                # conn.recv(1024)
                conn.sendall(result_bytes)
            conn.close()
if __name__=="__main__":
    server=socketserver.ThreadingTCPServer(("127.0.0.1",9998),Myserver)
    server.serve_forever()
big_server
#__author: greg
#date: 2017/9/16 22:36mysql
import socket
ip_port=("127.0.0.1",9998)
sk=socket.socket()
sk.connect(ip_port)
print("客戶端啟動...")
print(str(sk.recv(1024),"utf8"))
while True:
    inp=input(">>>").strip()
    sk.sendall(bytes(inp,"utf8"))
    basic_info_bytes=sk.recv(1024)
    print(str(basic_info_bytes,"utf8"))
    # sk.send(bytes('ok','utf8'))
    result_length=int(str(basic_info_bytes,"utf8").split("|")[1])
    print(result_length)
    has_received=0
    content_bytes=bytes()
    while has_received<result_length:
        fetch_bytes=sk.recv(1024)
        has_received+=len(fetch_bytes)
        content_bytes+=fetch_bytes
    cmd_result=str(content_bytes,"utf8")
    print(cmd_result)
sk.close()
big_client
#__author: greg
#date: 2017/9/17 0:14
import socket,os

ip_port=("127.0.0.1",8898)
sk=socket.socket()
sk.bind(ip_port)
sk.listen(5)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
while True:
    print("waiting connect")
    conn,addr=sk.accept()
    flag = True
    while flag:
            client_bytes=conn.recv(1024)
            func,file_byte_size,filename=str(client_bytes,"utf8").split("|",2)
            path=os.path.join(BASE_DIR,'upload',filename)
            has_received=0
            file_byte_size=int(file_byte_size)
            f=open(path,"wb")
            while has_received<file_byte_size:
                data=conn.recv(1024)
                f.write(data)
                has_received+=len(data)
            print("ending")
            f.close()
up_server
#__author: greg
#date: 2017/9/17 0:14
import socket
import re,os,sys
ip_port=("127.0.0.1",8898)
sk=socket.socket()
sk.connect(ip_port)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
print("客戶端啟動....")
while True:
    inp=input(">>>").strip() #post|1.jpg
    if inp.startswith("post"):
        method,local_path=inp.split("|",1)
        local_path=os.path.join(BASE_DIR,local_path)
        file_byte_size=os.stat(local_path).st_size
        file_name=os.path.basename(local_path)
        post_info="post|%s|%s"%(file_byte_size,file_name)
        sk.sendall(bytes(post_info,"utf8"))
        has_sent=0
        file_obj=open(local_path,"rb")
        while has_sent<file_byte_size:
            data=file_obj.read(1024)
            sk.sendall(data)
            has_sent+=len(data)
        file_obj.close()
        print("上傳成功")
up_client

 

下一篇:socketserver

 


免責聲明!

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



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