目录
- 前言
- 网络编程
- 实质
- IP地址和端口
- 数据传输协议
- 协议
- Socket
- 概念
- 套接字
- socket对象方法
- 初步使用
- 功能
- 源码
- 运行结果
- 结语
前言
本系列博客是笔者学习Python Socket的过程笔记,目的在于记录。其中的解释都为自己的见解,仅供参考,如有错误,还望指出。本篇博客是对Python Socket的初步了解和使用,大佛请移驾。
网络编程
网络编程本质就是实现两个设备之间的数据交换(通信),通常这个设备指的是计算机,实际上任何能连接网络的硬件设备都能实现通信。也就是说我们的任何能连接网络的设备都是可通信的,比如我有一个 LED显示屏,我现在需要服务器控制这个 LED屏幕显示一段文字,一些轮播图片或者一个视频,并且控制设备什么时间播放什么内容,播放多长时间等命令。那么LED和我的服务器之间就需要通信,这个时候就不是计算机与计算机之间通信了。
在网络编程中,发起连接的一方被称作客户端(Client),等待连接的一方被称作服务器(Server)。服务端一般都需要一直启动,等待客户端来连接。连接一旦建立,双方就可以互相发送数据了。
网络通信和生活中的打电话类似,我要给某个人打电话我就得知道他的电话号码,在网络中也是如此,我要和服务器建立通信就需要知道服务器的在网络中的位置。计算机在网络中的位置由IP地址(具体概念查看IP百度百科)来区分标识。建议再看看私有IP和共有IP的区别。
一台计算机上(设备)可以运行多个程序(多个进程),为了区分这些程序就设计了端口(Port)的概念。设备最多有216=65536个端口,一个端口可以对应一个唯一程序,无论是服务端还是客户端,每个程序都对应一个或多个端口。其中0-1024之间的大多端口已经被操作系统占用,我们的程序一般就使用之后的端口号(仅仅针对计算机设备)。也就是说通过IP和端口号就可以实现两个设备的某个程序之间进行通信了。
对于建立了连接的两个设备以何种方式进行数据的传输呢,传输数据的方式无论是有线传输还是无线传输,一般就两种传输协议方式:
1. TCP(Transfer Control Protocol)
传输控制协议方式,该传输方式是一种稳定可靠的传送方式,类似于显示中的打电话。只需要建立一次连接,就可以多次传输数据。就像电话只需要拨一次号,就可以实现一直通话一样,如果你说的话不清楚,对方会要求你重复,保证传输的数据可靠。使用该种方式的优点是稳定可靠,缺点是建立连接和维持连接的代价高,传输速度不快。
2. UDP(User Datagram Protocol)
用户数据报协议方式,该传输方式不建立稳定的连接,类似于发短信息。每次发送数据都直接发送。发送多条短信,就需要多次输入对方的号码。该传输方式不可靠,数据有可能收不到,系统只保证尽力发送。使用该种方式的优点是开销小,传输速度快,缺点是数据有可能会丢失。
协议(Protocol)在网络里是一个重要的概念,平时所说的协议实际就是指网络协议,网络协议就是设备实现通信的规定,双方必须遵守这个规定才能获取到正确的通信信息。比如在建立连接的时候应该何种方式连接,怎么互相判别,传输的数据格式(也就是要按照一定的格式来发送和接收数据)是什么。只有遵守这个规定,设备之间才能相互通信交流。它的三要素是:语法、语义、时序。
为了使数据在网络上从源到达目的,网络通信的参与方必须遵循相同的规则,这套规则称为协议(protocol),它最终体现为在网络上传输的数据包的格式。
Socket
Socket即是套接字,Python将低级别的网络服务封装成了一个模块,通过socket就可以将网络中的两个设备的某一进程(应用程序)以TCP或者UDP协议方式建立连接,在建立连接过后就可以实现设备进程与设备进程之间的通讯了。
我们导入socket(使用import socket
)模块,使用s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
来创建返回一个基于IPv4地址流式TCP传输协议的套接字s,我们需要创建套接字最主要考虑前两个参数,第一个是IP地址簇,第二个是传输协议类型,下面列了常用的几个可选值,想要了解全部请前往Python3 socket。
可选值 | 描述 |
---|---|
socket.AF_INET | IPv4(默认) |
socket.AF_INET6 | IPv6 |
socket.AF_UNIX | 用于Unix系统进程间通信 |
socket.AF_… | … |
可选值 | 描述 |
---|---|
socket.SOCK_STREAM | 流式socket,TCP(默认) |
socket.SOCK_DGRAM | 数据报式socket,UDP |
socket.SOCK_… | … |
函数 | 类型 | 描述 |
---|---|---|
s.bind() | 服务端用 | 绑定地址和端口到套接字,参数address为元组形式的(host, port) |
s.listen() | 服务端用 | 开始监听绑定的地址端口程序(进程),参数backlog指定在拒绝连接之前,可以挂起的最大连接数量。 |
s.accept() | 服务端用 | 等待客户端的连接(阻塞式)。 |
s.connect() | 客户端用 | 主动去连接服务器,参数address为元组形式的(host, port),如果连接出错,返回socket.error错误。 |
s.connect_ex() | 客户端用 | 主动去连接服务器,参数address为元组形式的(host, port),出错时返回出错码,而不是抛出异常。 |
s.recv() | 公共使用 | 接收数据,返回字节型字符串数据,参数bufsize指定接收数据的最大长度。 |
s.send() | 公共使用 | 发送数据,返回发送成功的字节数,参数data是要发送的字节型字符串数据。 |
s.sendall() | 公共使用 | 完整发送数据。内部循环调用send直至数据发送完整,参数data是要发送的字节型字符串数据。 |
s.recvfrom() | 公共使用 | 接收数据,返回值是(data, address)。data是包含接收数据的字符串,address是发送数据的套接字地址。 |
s.sendto() | 公共使用 | 发送数据,将数据发送到套接字,address形式为(host, port)的元组,指定远程地址。返回值是发送字节数。 |
s.close() | 公共使用 | 关闭套接字 |
s.getpeername() | 公共使用 | 返回连接套接字的远程地址。返回值通常是元组(host, port)。 |
s.getsockname() | 公共使用 | 返回套接字自己的地址。通常是一个元组(host, port)。 |
s.setsockopt() | 公共使用 | 设置给定套接字选项的值。 |
s.getsockopt() | 公共使用 | 返回套接字选项的值。 |
s.settimeout() | 公共使用 | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。 |
s.gettimeout() | 公共使用 | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
s.fileno() | 公共使用 | 接收数据,返回字节型字符串数据,参数bufsize指定接收数据的最大长度。 |
s.setblocking() | 公共使用 | flag为0非阻塞,否则为阻塞模式(默认)。非阻塞模式调recv()或send()没有数据,将引起socket.error异常。 |
s.makefile() | 公共使用 | 创建一个与该套接字相关连的文件。 |
初步使用
由于要实现的是两个进程间的通信(同一个设备),那么需要两个程序,我们分别叫客户端程序和服务端程序。
在这个初级使用实例中,实现的是一台计算机中两个进程之间的通信(一个进程为等待客户端连接的服务端程序,一个进程为主动去连接服务端的客户端程序)。也就是说客户端和服务端程序都运行在本地的一台计算机上,服务端建立socket,监听本机IP的6688端口等待客户端来连接,一旦有一个客户端来连接了就打印连接信息,然后等待连接的客户端发送数据,接收到客户端传来的数据后,就反馈给客户端收到信息了;客户端程序先建立socket套接字,然后去连接本机的IP和6688端口进程,连接成功后等待控制台输入数据,接受到输入的数据后发送到服务端,然后结束连接。
先新建一个server.py文件,这个文件里写服务端功能程序,如下:
import socket
# 创建一个socket套接字,该套接字还没有建立连接
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定监听端口
server.bind(('localhost', 6688))
# 开始监听,并设置最大连接数
server.listen(5)
# 获取未建立连接的服务端的IP和端口信息
print(server.getsockname())
# 下面注释掉的是获取未建立连接的服务端套接字的远程IP和端口信息,执行下面语句会报错,原因就是现在还没有远程客户端程序连接
# print(server.getpeername())
print(u'waiting for connect...')
# 等待连接,一旦有客户端连接后,返回一个建立了连接后的套接字和连接的客户端的IP和端口元组
connect, (host, port) = server.accept()
# 现在建立连接就可以获取这两个信息了,注意server和connect套接字的区别,一个是未建立连接的套接字,一个是已经和客户端建立了连接的套接字
peer_name = connect.getpeername()
sock_name = connect.getsockname()
print(u'the client %s:%s has connected.' % (host, port))
print('The peer name is %s and sock name is %s' % (peer_name, sock_name))
# 接受客户端的数据
data = connect.recv(1024)
# 发送数据给客户端告诉他接收到了
connect.sendall(b'your words has received.')
print(b'the client say:' + data)
# 结束socket
server.close()
然后建立一个client.py文件,实现客户端程序,内容如下:
import socket
# 创建一个socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 主动去连接本机IP和端口号为6688的进程,localhost等效于127.0.0.1,也就是去连接本机端口为6688的进程
client.connect(('localhost', 6688))
# 接受控制台的输入
data = input()
# 对数据进行编码格式转换,不然报错
data = data.encode('utf-8')
# 发送数据
client.sendall(data)
# 接收服务端的反馈数据
rec_data = client.recv(1024)
print(b'form server receive:' + rec_data)
client.close()
其中左边为服务端运行结果,右边为客户端运行结果,右边的hello是手动输入然后回车。
结语
到此初步的使用已经差不多了,不过上述代码只能是一对一的通信,一对多不是本篇博客的内容,因为客户端一般都不会只有一个,所以这个一对一不能满足我们的常用需求,后面的博客将会介绍多线程实现多客户端连接服务器,与服务器通信。在下一篇博客会讲解和实现局域网内两台设备,广域网之间,局域网与广域网之间设备的通信。
下一篇:Python Socket网络编程(二)局域网内和局域网与广域网的持续通信