@2018年12月26日
-
一、背景
上周通过树莓派简单实现了LED等控制,见《树莓派学习笔记1:python控制双色LED灯》(https:/blog.youkuaiyun.com/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.youkuaiyun.com/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()
对于上述服务端程序,有两种客户端方法与之交互。
- 命令:telnet localhost 8888,进入telnet界面后交互,目前可收到服务端的一个欢迎词‘Hello, Welcome to here…’,单输入只能输入一个字。提醒:如果是windows,可能默认不开,需手动启动telnet客户端功能。
- 程序:即后面的客户端程序。host 指定 localhost,port=8888
(三)访问自己的服务的客户端程序
这段客户端程序参考了csdn上一个博客,我加了一个异常处理、一段recv()程序,解决了与上述服务端程序不匹配的问题。
#!/usr/bin/python3
# 文件名:Socket_client_simp_test.py
#参考:https://blog.youkuaiyun.com/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基本交互实验已完成。期待树莓派上应用效果。