原文链接:http://www.juzicode.com/archives/819
在前面的系列文章中涉及的内容都是在本地进行的,接下来介绍的内容会更有趣,会进入到一个更广阔的世界。Python中内置的socket模块可以实现多台电脑间通过网口进行通信,互相收发消息,这篇文章将从最底层的socket模块开始介绍,而更上层的ftplib,telnetlib等都是基于socket封装的更高层应用的模块。
提到socket通信,必然绕不开C/S结构,在C/S结构中,其中一台电脑作为server端,等待其他的client端连接、通信。比如一个Ftp服务器启动后,会等待其他的客户端连接、传输文件。一个Telnet服务端也是这样,开启telnet服务后,等待其他的客户端连接、登录、发送命令。
1、TCP连接
利用socket编程的模型和C/S结构的特点有关,在服务端:打开某个端口,监听来自客户端的请求连接,完成连接后就可以等待客户端发消息或者像客户端发送消息。在客户端:在某个端口去连接服务端,如果连接成功,就可以向服务端发送消息或者等待服务端发送消息。
服务端
在服务端首先创建一个ipv4的tcp连接实例,参数socket.AF_INET, socket.SOCK_STREAM分别代表IPV4和TCP连接:
#创建socket服务端实例
skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
接下来绑定ip和端口,这里我们使用本机地址127.0.0.1进行实验,端口可以选择非周知端口,这里选择19200,
#绑定端口
skt.bind(('127.0.0.1',19200))
然后在该端口上开始监听,接收来自客户端的消息,listen方法的入参100表示最多可以同时接收100个客户端进行连接:
#监听端口
skt.listen(100)
完成前面3个步骤,就可以用accept方法等待客户端进行连接,如果有客户端连接上,就会创建一个针对该客户端的connet实例,后面与客户端的通信就使用该 connet 实例完成,需要注意的是这里调用send,recv方法不再是前面创建服务端socket实例,而是accept方法创建的 connet 实例。在这个例子中约定了服务端等待客户端发送消息,并将客户端发来的消息发送回去,如果客户端关闭了连接,这个 connet 实例也将关闭:
while True:
#等待连接
print('\n等待连接......')
connet, address = skt.accept()
print('客户端地址:%s,建立连接' % str(address))
print('connet实例: ',connet)
while True:
#等待消息
data = connet.recv(1024)
msg = data.decode('utf8')
print('客户端地址:%s,消息内容:%s' %(str(address),msg) )
if not data:
break
#回显消息给客户端
connet.send(data)
time.sleep(0.5)
#关闭连接
connet.close()
print('客户端地址:%s,连接关闭' % str(address))
客户端
在客户端也是一样首先创建一个socket实例 :
#创建socket实例
skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
第二步是根据ip和端口连接服务器:
# 建立连接:
skt.connect(('127.0.0.1', 19200))
接下来就是根据和服务端约定的通信流程,客户端每次发送一条消息,并等待服务端返回一条消息,当客户端发送结束后,关闭该socket连接:
current_time=time.asctime()
msg_list = [current_time,'juzicode.com', '微信公众号:桔子code', 'Socket通信']
for msg in msg_list:
# 发送消息:
data_tx = bytes(msg,encoding='utf8')
skt.send(data_tx)
# 接收消息:
data_rx = skt.recv(1024)
print('接收到消息:%s'%( data_rx.decode('utf8')))
time.sleep(0.5)
#退出
skt.close()
注意在客户端的send和recv方法直接使用的是创建的socket实例,而不是像服务端那样使用的是accept方法创建的连接实例。
在cmd下先运行server端程序,再运行client端程序,效果是这样的:
从上图可以看出,客户端每次通信的端口是不确定的,使用的端口号实际由系统分配空闲的端口号。
2、UDP连接
udp连接是一种面向无连接的通信方式,所以udp方式的通信相对于tcp通信,在服务端不需要使用listen方法在端口上连接,也不需要accept方法等待客户端发起连接,而是直接使用recvfrom方法接收消息或者sendto方法发送消息。
同样在客户端,也不需要使用connnet方法先与服务端建立连接,而是可以直接使用sendto或者recvfrom方法收发消息。
服务端
创建socket实例时,使用socket.SOCK_DGRAM参数创建UDP连接实例,并使用bind绑定端口:
#创建socket服务端实例
skt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#绑定端口
skt.bind(('127.0.0.1',19200))
使用recvfrom方法,可以返回获取到的消息和对端的地址信息,该地址信息就能用于sendto方法传入ip地址和端口号。因为udp不是面向连接的通信方法,所以其socket实例创建好之后并没有包含地址信息,这样不能直接使用send方法发送消息。当然仍然可以使用recv方法接收消息,这一般用于客户端,这时候客户端是已知服务端的ip地址和端口号的,并不需要像服务端那样使用recvfrom方法来获取对端通信的ip地址和端口号。
while True:
#等待消息
data,address = skt.recvfrom(1024)
msg = data.decode('utf8')
print('客户端地址:%s,消息内容:%s' %(str(address),msg) )
if not data:
break
#回显消息给客户端
skt.sendto(data,address)
time.sleep(0.5)
因为不需要建立连接,sever端发送和接收数据的使用的是socket实例,而不是像tcp连接那样使用的是connet连接实例。
客户端
创建socket实例的方法同服务端一样:
#创建socket实例
skt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
仍然是约定客户端发送一条消息,等待服务端返回一条消息,这里不需要connect方法和服务端“建立连接”。
current_time=time.asctime()
msg_list = [current_time,'juzicode.com', '微信公众号:桔子code', 'Socket通信']
for msg in msg_list:
# 发送消息:
data_tx = bytes(msg,encoding='utf8')
skt.sendto(data_tx, ('127.0.0.1',19200))
# 接收消息:
data_rx = skt.recv(1024)
print('接收到消息:%s'%( data_rx.decode('utf8')))
time.sleep(0.5)
#退出
skt.close()
启动sever端后,运行client端:
这篇文章介绍了利用socket编写服务端和客户端完成简单通信的例子,利用socket编程,需要用户设计应用层协议,更加灵活但是难度也更大。tcp方式通信是面向连接的,所以创建连接后每次发送数据并不需要指定对方的ip地址和端口号,而udp方式通信每次发送数据必须指定对方的ip地址和端口号。