[Python]-17-socket编程

引言

这篇文章介绍python内置socket模块,使用这个模块模拟一个HTTP GET请求,并将服务器返回的数据保存到本地。

文章目录

0×1.使用TCP传输数据

在互联网上,客户机与服务器通信,实际上是两台机器上的两个不同进程之间的通信,而我们经常听说的socket,只是一个抽象的概念,它通常表示一台机器上所打开的一个连接到目标的"网络接口",这个网络接口包含了目标机器的IP地址,端口号,以及双方使用的协议类型;

下面这段代码使用socket向本站所在服务器发送了一个HTTP GET请求,然后将服务器返回的HTML数据保存到本地的qingsword.html文件中:

					#!/usr/bin/env python3
					#coding=utf-8

					#导入socket模块
					import socket
					
					#初始化一个socket对象,AF_INET表示使用IPv4协议(AF_INET6表示IPv6协议),SOCK_STREAM表示这个socket使用TCP通信(UTP为SOCK_DGRAM)
					s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

					#连接到www.qingsword.com的80端口,括号中是一个元组
					s.connect(("www.qingsword.com",80))

					#发送GET请求,请求服务器/目录,请求协议版本HTTP1.1,在请求完成后关闭连接
					s.send("GET / HTTP/1.1\r\nHost: www.qingsword.com\r\nConnection: Close\r\n\r\n".encode("utf-8"))

					#上面的语句可以分开写成下面的样子,在字符串前使用b前缀和在字符串后使用.encode("utf-8")都能将字符串转码成可用于网络传输的字节码(在要发送的字符串中包含中文时,推荐使用.encode("utf-8")转码)
					#s.send(b"GET / HTTP/1.1\r\n") 
					#s.send(b"Host:www.qingsword.com\r\n")
					#s.send(b"Connection:Close\r\n\r\n")
					
					#初始化一个列表用于接收服务器返回的数据
					buffer=[]
					while True:
					    #每次读取1024个字节
					    d=s.recv(1024)
					    if d: #如果还有数据,将其添加到列表中
					        buffer.append(d)
					    else:
					        break

					#将列表中的数据使用空字符串连接成一个整体
					data="".encode("utf-8").join(buffer)
					s.close() #关闭socket

					#使用split将data分割一次(遇到第一个"\r\n\r\n"分割成前后两部分)
					header,html=data.split("\r\n\r\n".encode("utf-8"),1)

					#将第一部分服务器返回的HTTP头部信息解码后打印出来
					print(header.decode("utf-8"))

					#将第二部分HTML信息保存到当前脚本所在目录中的qingsword.html文件中
					with open("qingsword.html","wb") as f1:
					    f1.write(html)
					

0×2.socket客户端&服务端模型

下面是一个简单的客户端到服务端socket通信模型实例:

服务端:

					#!/usr/bin/env python3
					#coding=utf-8
					from multiprocessing import Process
					import socket,time
					
					#用于处理新的连接,接收新连接的socket和IP:port
					def Hello_Socket(sock,addr):
					    #addr包含了客户端主机的IP和端口号
					    print("创建新的客户端连接成功,%s:%s"%addr)
					    #发送一个欢迎信息给客户端
					    sock.send("欢迎!".encode("utf-8"))
					    while True:
					        #从客户端读取消息,如果消息为空或读取到exit,退出循环
					        data=sock.recv(1024)
					        time.sleep(1) #模拟网络延迟
					        if not data or data.decode("utf-8")=="exit":
					            break
					        #在读取到的消息前添加Hello,再发送回客户端
					        sock.send(("Hello %s"%data.decode("utf-8")).encode("utf-8"))
					    #关闭连接
					    sock.close()
					    print("%s:%s连接断开..."%addr)

					if __name__=="__main__":
					    so=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
					    Local_IP_Addr="127.0.0.1"
					    Local_Port=8899
					    #服务端打开一个socket,并且绑定本地网卡IP和端口号,客户端通过服务端这个IP以及端口和服务器通信
					    so.bind((Local_IP_Addr,Local_Port))
					    #监听端口,最多同时支持10个客户端连接
					    so.listen(10) 
					    print("在本地接口%s:%s等待客户端连接...."%(Local_IP_Addr,Local_Port))
					    while True:
					        #accept()方法会阻塞程序,程序在这里等待客户端连接,如果有多个连接,accept()按先后顺序每次取一个,并返回这个连接的socket以及客户端IP和port,分别保存到前面的两个变量中
					        sock,addr=so.accept()

					        #创建一个新的进程来处理传入的连接,将连接对应的socket和IP:port传递给这个进程处理程序
					        p1=Process(target=Hello_Socket,args=(sock,addr))
					        p1.start() #启动进程
					

客户端:

					#!/usr/bin/env python3
					#coding=utf-8
					import socket
					
					#新建一个socket,连接到服务端IP的对应端口
					so=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
					so.connect(("127.0.0.1",8899))
					#接收服务端的欢迎信息
					print(so.recv(1024).decode("utf-8"))

					#将列表中的四个元素发送给服务端
					for d in ["A","B","C","D"]:
					    so.send(d.encode("utf-8"))
					    print(so.recv(1024).decode("utf-8"))

					#发送exit,断开连接
					so.send("exit".encode("utf-8"))
					so.close()
					

首先启动服务端程序,然后再打开客户端程序,输出如下:

					#服务端
					在本地接口127.0.0.1:8899等待客户端连接....
					创建新的客户端连接成功,127.0.0.1:45552
					127.0.0.1:45552连接断开...

					#客户端
					欢迎!
					Hello A
					Hello B
					Hello C
					Hello D
					

0×3.使用UDP传输数据

UDP传输数据时,双方不需要建立连接,在客户端只需要知道服务端的IP和UDP端口,在服务端只需要监听一个UDP端口来接收数据即可,UDP本身并不保证数据的可靠到达,所以数据的传输速度会比TCP快很多,下面是一个服务端和客户端使用UDP通信的实例:

服务端:

					#!/usr/bin/env python3
					#coding=utf-8
					import socket
					        
					if __name__=="__main__":
					    #SOCK_DGRAM表示这是一个UDP socket
					    s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
					    Local_IP_Address="127.0.0.1"
					    Local_Port=20086

					    #UDP的socket只需要在对应网卡接口绑定本地监听端口即可接收客户端的信息
					    s.bind((Local_IP_Address,Local_Port))
					    print("在本地接口%s:%s接收客户端消息..."%(Local_IP_Address,Local_Port))
					    while True:
					        #recvfrom()方法返回两个值,第一个为从socket接收到的信息,第二个为客户端的(IP,port)元组
					        data,addr=s.recvfrom(1024)
					        print("从%s:%s接收到消息:%s"%(addr[0],addr[1],data.decode("utf-8")))
					        #sendto函数接受两个必选参数,语法如下
					        #sendto(发送的数据,(接收端ip,port))
					        s.sendto("Hello %s".encode("utf-8")%(data),addr)
					

客户端:

					#!/usr/bin/env python3
					#coding=utf-8
					import socket
					
					#客户端只需要初始化一个UDP socket,然后使用sendto方法将数据发送给服务端即可,接收服务端的返回数据时使用recv方法(因为已经直到服务器的ip和固定端口,所以不需要使用recvfrom方法来获取服务端的IP和端口号了),并将接收的数据解码后打印出来
					so=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
					for d in ["A","B","C","D"]:
					    so.sendto(d.encode("utf-8"),("127.0.0.1",20086))
					    print(so.recv(1024).decode("utf-8"))
					so.close()
					

首先启动服务端,然后启动客户端,输出如下:

					#服务端
					在本地接口127.0.0.1:20086接收客户端消息...
					从127.0.0.1:58172接收到消息:A
					从127.0.0.1:58172接收到消息:B
					从127.0.0.1:58172接收到消息:C
					从127.0.0.1:58172接收到消息:D

					#客户端
					Hello A
					Hello B
					Hello C
					Hello D