在当今的互联网中,Socket协议是最重要的基础之一。本文涵盖了Python中Socket编程的所有领域。为什么使用套接字套接字是构成当今网络的各种通信协议,可以在两个不同的程序或设备之间传输信息。例如,当我们打开浏览器时,我们作为客户端会创建到服务器的连接以传输信息。在深入探讨这个通信原理之前,让我们先搞清楚什么是Sockets。什么是Sockets一般来说,Socket是为发送和接收数据而建立的内部应用协议。单个网络将有两个套接字,每个通信设备或程序一个,这些套接字是IP地址和端口的组合。根据使用的端口号,单个设备可以有“n”个Socket,不同的端口可以用于不同类型的协议。下图是一些常用的端口号及相关协议信息:协议端口号Python库应用HTTP80httplib,urllib,请求网页,网站FTP20ftplib文件传输NNTP119nttplib消息传输SMTP25smtplib发送邮件Telnet23telnetlib命令行POP3110poplib接收邮件Gopher70gopherlib文件传输现在我们了解了了解了Sockets的概念之后,我们就来看看Python的Socket模块是如何在Python中实现Socket编程的。在Python中实现Socket编程,需要导入socket模块。该模块的一些重要方法如下:方法说明socket.socket()用于创建套接字(服务端和客户端都需要创建)socket.accept()用于接受连接。它返回一对值(conn,address),其中conn是用来发送或接收数据的新socket对象,address是连接另一端的socket地址。socket.bind()用于绑定到作为参数指定的地址套接字。close()用于关闭套接字socket.connect()用于连接到作为参数指定的远程地址socket.listen()使服务器能够接受连接在Python中构建服务器和客户端。什么是服务器服务器可以是程序、计算机,也可以是专用于管理网络资源的设备。服务器可以在同一设备或计算机上,本地连接到其他设备和计算机,甚至可以远程连接。服务器有多种类型,如数据库服务器、Web服务器、打印服务器等,服务器通常使用socket.socket()、socket.bind()、socket.listen()等建立连接并绑定到客户端,现在让我们编写一个程序来创建服务器。importsockets=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.bind((socket.gethostname(),1234))#端口号可以是0-65535之间的任何值(我们通常指定非特权端口>1023)s.listen(5)whileTrue:clt,adr=s.accept()print(f"Connectionto{adr}established")#f字符串是以f为前缀的文字字符串,#containspython表达式在大括号clt内。send(bytes("SocketProgramminginPython","utf-8"))#发送信息给clientsocket创建socket的第一个必要条件是导入相关模块。然后使用socket.socket()方法创建服务器端套接字。AF_INET指的是一个来自互联网的地址,它需要一对(host,port),其中host可以是一个URL或者它对特定网站的地址,port是一个整数。SOCK_STREAM用于创建TCP协议。bind()方法接受两个参数作为元组(主机、端口)。这里需要注意的是,最好使用4位的端口号,因为较低的端口号通常被系统占用或保留。listen()方法允许服务器接受连接,5是同时接受多个连接的队列。这里可以指定的最小值是0,如果没有指定参数,则使用默认合适的参数。while循环让连接永远被接受,clt和adr是客户端对象和地址,print语句简单的打印出客户端socket的地址和端口号,最后clt.send用于按字节发送数据。现在我们的服务器已经设置好了,让我们转到客户端。什么是客户端客户端是从服务器接收信息或服务的计算机或软件。在客户端-服务器模型中,客户端向服务器请求服务。最好的例子就是GoogleChrome、Firefox等网络浏览器,它们会根据用户的指令请求网络服务器提供所需的网页和服务。其他的例子还有在线游戏,在线聊天等。现在,让我们看看如何用Python编程语言编写一个客户端程序:(),2346))msg=s.recv(1024)print(msg.decode("utf-8"))首先还是导入socket模块,然后像创建服务器时一样创建socket。然后要创建客户端服务器之间的连接,您需要通过指定(host,port)使用connect()方法。注意:当客户端和服务器在同一台机器上时,gethostname是必需的。(LAN–localip/WAN–publicip)在这里,客户端想要从服务器接收一些信息,为此,我们需要使用recv()方法,信息存储在另一个变量msg中。应该注意的是,传输的信息将以字节为单位。在上述程序的客户端中,一次传输最多可以接收1024字节(缓冲区大小)。根据传输的信息量,它可以指定为任意数字。最后,它解码并打印正在传输的消息。现在我们已经了解了如何创建客户端-服务器程序,让我们看看它们需要如何执行。客户端-服务器交互要执行这些程序,需要打开命令程序,进入创建客户端和服务器程序的文件夹,然后输入:pyserver.py#这里server.py是服务器的文件名.不出意外,服务器开始运行了。执行client,需要再打开一个cmd窗口,然后输入:pyclient.py接下来我们把buffersize减小到7,看看同样的程序是什么样子的,如图,传输7个字节后,Connection终止。实际上这是一个问题,因为我们还没有收到完整的消息,但是连接被过早关闭了,让我们来解决这个问题。多次通信为了继续连接直到客户端收到完整的消息,我们可以使用while循环importsockets=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((socket.gethostname(),2346))whileTrue:msg=s.recv(7)print(msg.decode("utf-8"))这样修改后,每次传输都会收到7个字节的完整消息。但这引入了另一个问题,连接永远不会终止,你永远不知道它什么时候会终止。此外,如果我们实际上不知道客户端将从服务器接收到的消息或信息有多大怎么办。对于这种情况,我们需要继续改进代码complete_info=''whileTrue:msg=s.recv(7)iflen(msg)<=0:breakcomplete_info+=msg.decode("utf-8")print(complete_info)在服务器端,使用close()方法如下:clt.close()输出如下:程序检查信息的大小并一次打印两个字节到缓冲区,然后完成Close连接后的连接。传递Python对象到目前为止我们只掌握了传递字符串的方法,但是Python中的Socket编程同样可以让我们传递Python对象。这些对象可以是集合、元组、字典等。为此,使用了Python的pickle模块。Pythonpickle模块Pythonpickle模块是我们在Python中实际序列化或反序列化对象的时候用到的。让我们看一个小例子importpicklemylist=[1,2,'abc']mymsg=pickle.dumps(mylist)print(mymsg)Output:b'x80x03]qx00(Kx01Kx02Xx03x00x00x00abcqx01e.'在上面的程序中,mylist是使用pickle模块的dumps()函数进行序列化。另外注意输出以ab开头表示已经转换为bytes。在socket编程中,可以实现这个模块来在client和server之间传递python对象。如何使用pickle模块传输Python对象当我们使用pickle和sockets时,我们可以通过网络传输任何东西,我们先看服务端代码Server-Side:importsocketimportpicklea=10s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.bind((socket.gethostname(),2133))#bindingtuples.listen(5)whileTrue:clt,adr=s.accept()print(f"Connectionto{adr}established")m={1:"Client",2:"Server"}mymsg=pickle.dumps(m)#我们稍后要打印的消息mymsg={len(mymsg):{a}}"utf-8")+mymsgclt.send(mymsg)这里,mi这是一个字典,它基本上是一个需要从服务器发送到客户端的Python对象。这是通过首先使用dumps()序列化对象然后将其转换为字节来完成的。现在,让我们记下客户端:Client-Side:importsocketimportpicklea=10s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((socket.gethostname(),2133))whileTrue:complete_info=b''rec_msg=TruewhileTrue:mymsg=s.recv(10)ifrec_msg:print(f"Thelengthofmessage={mymsg[:a]}")x=int(mymsg[:a])rec_msg=Falsecomplete_info+=mymsgiflen(complete_info)-a==x:print("收到完整信息")print(complete_info[a:])m=pickle.loads(complete_info[a:])print(m)rec_msg=Truecomplete_info=b''print(complete_info)第一个while循环将帮助我们跟踪完整的消息(complete_info)和缓冲区接收到的消息(rec_msg)。然后,当收到消息时,我们所做的就是打印消息的每一位,并将其放入大小为10的缓冲区中以进行接收。这个尺寸可以是任意尺寸,看个人选择。然后,如果接收到的消息等于完整消息,我们只需将消息打印为接收到的完整消息,然后使用loads()反序列化消息。输出如下:
