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基本交互实验已完成。期待树莓派上应用效果。

你可能感兴趣的:(Python,Python,socket)