一、网络编程
计算机网络是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。
举个例子,当你使用浏览器访问新浪网时,你的计算机就和新浪的某台服务器通过互联网连接起来了,然后,新浪的服务器把网页内容作为数据通过互联网传输到你的电脑上。
网络通信是两台计算机上的两个进程之间的通信。比如,浏览器进程和新浪服务器上的某个Web服务进程在通信,而QQ进程是和腾讯的某个服务器上的某个进程在通信。网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,TCP编程和UDP编程是最主要的两种网络类型的编程。
二、Socket的定义
Socket又称 '套接字',是网络编程的一个抽象概念。通常我们用一个Socket表示 '打开了一个网络链接',而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
三、连接过程
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
1)服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3)连接确认:当服务器端套接字监听到或者接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。此时服务器端的套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
四、基于TCP协议的Socket编程
大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过三次握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。
端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个TCP报文来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。这也就是为什么在TCP编程和UDP编程中需要绑定地址的原因。
- 基于TCP协议的Socket通信流程
下面用实例来帮助大家理解基于TCP协议的socket通信过程。该程序由客户端远程向服务器端发送命令,服务器端执行并且将执行结果发给客户端。
- 服务器
#TCP服务器编程
import socket #导入 socket 模块
import os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个socket
s.bind(('0.0.0.0',1000)) #绑定地址(host,port)到套接字,在AF_INET下,以元组(host,port)的形式表示地址。
s.listen(5) #调用listen方法开始监听端口,传入的参数为操作系统可以挂起的最大连接数量,一般设为5
sock,addr=s.accept() #被动接受TCP客户端连接,等待连接的到来
for i in range(10):
cmd = sock.recv(1024) #接收TCP数据,数据以字符串形式返回;'1024'表示每次接收的最大数据量
print(cmd)
command = cmd.decode()
if command.startswith("cd"): #远程控制不能执行cd命令,需单独处理
os.chdir(command[2:].strip()) #切换路径
result=os.getcwd() #显示路径
elif command=='exit':
break
else:
result=os.popen(command).read() #如果接收的命令不是以'cd'开头的,则直接执行命令
if result:
sock.send(result.encode()) #如果执行的结果有返回值,则将结果返回给客户端
else:
sock.send(b'OK!') #如果执行的结果无返回值,则给客户端发送'OK'
s.close() #关闭连接
- 客户端
#TCP客户端编程--socket 作者:成纤纤
import socket #导入socket模块
import os
s=socket.socket() #创建一个socket对象
s.connect(("127.0.0.1",1000)) #主动初始化TCP服务器连接,一般以元组(host,port)的形式表示地址。
for i in range(10):
com=input("输入命令:")
if com=='exit':
s.send(b'exit') #发送TCP数据,将输入的命令发送到连接的套接字。
break
else:
s.send(com.encode())
d=s.recv(65536) #接收TCP数据,数据以字符串形式返回;'65536'为指定要接收的最大数据量。
print(d.decode(),'--->',len(d)) #打印从服务端发送回来的数据和长度
s.close() #关闭连接
- 运行结果:
对于服务器,要首先监听指定端口,然后,对每一个新的连接,创建一个线程或进程来处理。通常,服务器程序会无限运行下去。对于客户端,要主动连接服务器的IP和指定端口。所以我们在运行程序时,需先运行服务器端的程序,然后在运行客户端的程序,否则客户端的程序会报错。通俗一点来说,只有当目标主机(服务器端)活跃时,我们(客户端)才能连接它。
同一个端口,被一个Socket绑定了以后,就不能被别的Socket绑定了。
看完这个例子,大家有没有注意到,当我们接收数据时,需要调用decode()方法,发送数据时,需要调用encode()方法。这是为什么呢?
encode()和decode()都是字符串的函数,从英文意思上看,encode和decode分别指编码和解码,默认格式为UTF-8。数据在物理层上是以字节流(bytes)的形式传输的,所以在发送消息时,我们需要对字符串进行编码,获得bytes类型对象;接收数据时,就需要对其进行解码。
五、基于UDP协议的Socket编程
- 基于UDP协议的Socket通信流程
相对TCP,UDP则是面向无连接的协议。所以在使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
对于基于UDP协议的Socket通信,服务器不需要调用listen()
方法,而是直接接收来自任何客户端的数据。
下面的UDP编程实现的是一个简单的聊天过程!
- 服务器
#UDP服务器
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建一个socket
s.bind(("127.0.0.1",9999)) #绑定端口
while 1:
#recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。
sock,addr=s.recvfrom(1024)
message=sock.decode()
print(message,addr)
if message=='exit': #客户端先结束对话
break
data=input("I:")
s.sendto(data.encode(),addr) #从把数据从接收数据的端口发送出去
s.close()
- 客户端
客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect()
方法,直接通过sendto()
给服务器发数据:
#UDP客户端(访问端)
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建一个socket对象
while 1:
#发送数据
message=input("I:")
s.sendto(message.encode(),("127.0.0.1",9999))
if message=='exit':
break
#接收数据
sock,addr=s.recvfrom(1024)
print(sock.decode(),addr)
s.close()
- 运行结果:
小结:UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口可以各自绑定。