Python學習筆記1:套接字編程-服務和客戶端

@2018年12月26日

  • 一、背景
    上週通過樹莓派簡單實現了LED等控制,見《樹莓派學習筆記1:python控制雙色LED燈》(https:/blog.csdn.net/weixin_44230447/article/details/85223640),這個控制是本地控制LED。下一步希望能遠程控制,初步學習,目前可通過WEB網頁、Socket經Wifi、藍牙、物聯網卡等遠程連接。本着循序漸進原則,考慮先實現最簡單方式,即本地局域網+wifi+socket方式。
    十多年前,在IBM AS400用C編寫過一個socket 程序與另一臺IBM 主機間傳輸文件,感受到c的強大。現在python 也支持socket,本文先概念驗證一下。有空在樹莓派上移植服務端程序。

  • 二、基本原理
    無論哪種語言,Socket 基本原理是一樣。 客戶端和服務端socket編程的基本流程如下:

客戶端 服務端
創建套接字 創建套接字
連接服務端 監聽連接
創建客戶端連接
收發數據 收發數據
關閉連接 關閉連接
  • 三、Python 實現

    自己實現過程中,參考了2篇文章:
    

1.流程和案例如《Python黑帽編程2.8 套接字編程》https://zhuanlan.zhihu.com/p/22040287
2.函數如《Python Socket (套接字)詳細解釋以及簡單的小例子》https://blog.csdn.net/qq_25406669/article/details/80576770

(一)連接網站的客戶端程序
上述黑帽編程講的比較細,但有幾點注意。
1.可能其python 爲2,某些語法與3不兼容,需修改一下,例如send()/recv() 的數據類型爲byte,需進行相應的encode()/decode(),即編碼和解碼。
2.例子中host=‘www.zhihu.com’,但發送"GET / HTTP/1.1\r\n\r\n" 請求時,對端返回 400 錯誤。但換爲baidu就返回 200 OK。估計zhihu 不支持http,需https。因本次爲主要掌握原理,沒有進一步研究解決方法。如知道的朋友,告訴我用socket怎麼連接https 網址。

# Socket client example in python 3.6
# 參考:https://zhuanlan.zhihu.com/p/22040287 ,由python 2 轉爲3.5。
# 其中except 部分、sendall() recv() 中內容需要編碼和解碼。http get 請求www.zhihu.com失敗,但baidu可以。
import socket  # for sockets
import sys  # for exit

try:
    # create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error as msg:
    print('Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1])
    sys.exit()

print('Socket Created')

host = 'www.baidu.com'  

#host = 'http://www.zhihu.com' #  Hostname could not be resolved
#host = 'localhost'    # Ip address of localhost is 127.0.0.1

port = 80  # http

try:
    remote_ip = socket.gethostbyname(host)
except socket.gaierror:
    # could not resolve
    print('Hostname could not be resolved. Exiting')
    sys.exit()

print('Ip address of ' + host + ' is ' + remote_ip)

# Connect to remote server
s.connect((remote_ip, port))

print('Socket Connected to ' + host + ' on ip ' + remote_ip)

# Send some data to remote server
send_msg = "GET / HTTP/1.1\r\n\r\n"

#發送字符串數據 "GET / HTTP/1.1\r\n\r\n" ,這是一個http請求,用來獲取網站首頁的內容

try:
    # Set the whole string
    ''' 
    s.sendall(send_msg)
    #TypeError: a bytes - like object is required, not 'str' 即應該是bytes類型,而不是str類型
    str通過 encode()編碼方法可以編碼爲指定的bytes, 反過來是decode()解碼。可用type()查看。
    '''
    s.sendall(send_msg.encode())
    '''   
   baidu HTTP/1.1 200 OK, zhihu and sohu reply is 400 bad
    '''
except socket.error:
    # Send failed
    print('Send failed')
    sys.exit()

print('Message send successfully')

# Now receive data
reply = s.recv(200)
print("reply data is ",reply.decode())

s.close()
#----------------------------------------------------------------------------------------------

返回結果

reply data is HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 14615
Content-Type: text/html
Date: Wed, 26 Dec 2018 01:42:09 GMT

(二)服務端程序
網站是一個服務端,現在編寫自己的服務端,也是參考了上述黑帽編程的代碼。但我用windows 的telnet 作爲客戶端訪問時,總是隻能輸入一個字符,不能輸入一句話,而用後邊(三)寫的一個客戶端程序,可以發送一個字符串。推測,原文telnet是linux的?
修改後的服務端程序如下:

# Socket Server example in python 3.6
# 文件名:Socket_server_test.py
# 參考:https://zhuanlan.zhihu.com/p/22040287 ,由python 2 轉爲3.5。
import socket
import sys

HOST = ''  # Symbolic name meaning all available interfaces
PORT = 8888  # Arbitrary non-privileged port

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

try:
    s.bind((HOST, PORT))
except socket.error as msg:
    print('Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1])
    sys.exit()

print('Socket bind complete')

s.listen(10)
print('Socket now listening')

while 1:
    # wait to accept a connection - blocking call
    conn, addr = s.accept()
    print('Connected with ' + addr[0] + ':' + str(addr[1]))
    welcome_str='Hello, Welcome to here.. \r\n'
    conn.sendall(welcome_str.encode())

    data = conn.recv(1024)
    print("received data is ",data.decode())
    reply = data
    if not data:
        print(" have break!")
        break

    conn.sendall(reply)

conn.close()
print("This Connection ",addr[0]," has disconnected.")

s.close()

對於上述服務端程序,有兩種客戶端方法與之交互。

  1. 命令:telnet localhost 8888,進入telnet界面後交互,目前可收到服務端的一個歡迎詞‘Hello, Welcome to here…’,單輸入只能輸入一個字。提醒:如果是windows,可能默認不開,需手動啓動telnet客戶端功能。
  2. 程序:即後面的客戶端程序。host 指定 localhost,port=8888

(三)訪問自己的服務的客戶端程序
這段客戶端程序參考了csdn上一個博客,我加了一個異常處理、一段recv()程序,解決了與上述服務端程序不匹配的問題。

#!/usr/bin/python3
# 文件名:Socket_client_simp_test.py
#參考:https://blog.csdn.net/qq_25406669/article/details/80576770

# 導入 socket、sys 模塊
import socket
import sys

# 創建 socket 對象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 獲取本地主機名
#host = socket.gethostname()
host='localhost'
# 設置端口號
port = 8888

# 連接服務,指定主機和端口
try:
    print("This connection will connect to host:", host)
    s.connect((host, port))
except IOError as e:
    print("Failed to connect host.: %s: %s\n" % (e.errno, e.strerror))
    sys.exit()

# 接收小於 1024 字節的數據
msg1 = s.recv(1024)
print('Server Say: ',msg1.decode('utf-8'))
s.sendall("客戶端發來的消息2。\r\n".encode('utf-8'))

# 接收小於 1024 字節的數據
msg2 = s.recv(1024)
print('Client Say: ',msg2.decode('utf-8'))

s.close()

至此,相關socket基本交互實驗已完成。期待樹莓派上應用效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章