最近在看《Python核心编程(第3版)》这本书,第二章网络编程看完原理是懂了,但具体每一行代码都干了些什么还是有点懵逼,结果就是,脱离了这本书就完全不会写了,或者写完运行各种报错。所以自己还是各种百度,各种问题各种解决,也算是遇到了书中没有说明的问题,也学到了好多书中没有提及的东西。写个博客分享一下,也是为了记录一下避免以后又搞不懂了。代码只有30来行,我将尽可能详细的说说,好了,面对疾风吧 !

python的tcp套接字获取端口号 python中tcp_客户端


————————————————————————————————————————————————

环境:python 3.7

套接字Socket:

什么是socket
——网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket 。
——所谓socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过“套接字”向网络发出请求或应答网络请求。
——socket起源于Uinx,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open–>读写write/read–>关闭close”模式来操作,socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写 IO,打开,关闭) Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
——Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电,有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务

——————————————————————————————————————————————

首先贴上《Python核心编程(第3版)》书中的socket内置方法,下图均截自书中:

python的tcp套接字获取端口号 python中tcp_客户端_02


python的tcp套接字获取端口号 python中tcp_TCP_03


相信各位都是大佬,TCP原理不多说,直接上代码逐行分析,简单的直接代码注释,要多说的标号后面说:

TCP服务器端:

from socket import *                                         #导入socket库
from time import ctime                                       #导入ctime函数
ss=socket(AF_INET,SOCK_STREAM )                              #分配TCP套接字服务器 [1]
ip_port=('127.0.0.1',9998)                                   #本机的9998端口
ss.bind(ip_port)                                             #绑定地址和端口
ss.listen(5)                                                 #设置并启动TCP监听器,最大连接数为5

try:                                                         # 异常捕获 [2]
    while True:                                              #开启循环 [3]
        print('---wating for connection---')                 #提示信息  
        con,add=ss.accept()                                  #被动接受客户端连接,connection,address
        con.send('---first connection---'.encode('utf-8'))   #发送消息到客户端 [4]
        print("connection from:%s at %s" %(add,ctime()))     #格式化输出
        flag=True                                            #用于循环跳出,跟用break跳出一样        
        while flag:
            data=con.recv(1024)                              #接收客户端消息,设定缓存区为1KB,这个容量可根据性能和程序需要修改
            print('【%s】%s' %(ctime(),data.decode('utf-8'))) #格式化输出收到的客户端消息 [5]
            if data =='exit':
                flag=False                                   #跳出循环,相当于break
            con.send('数据交换成功'.encode('utf-8'))           #给客户端发送提示信息
        con.close()                                          #关闭连接
except ConnectionResetError:                                 #捕获异常类型 [6]
    print('连接断开,数据传输结束')                             #异常处理

————————————————————————————————————————————————

说明:

注解[1] : ss=socket(AF_INET,SOCK_STREAM ) #分配TCP套接字服务器 :

~~这里分配TCP套接字服务器名为ss(serversocket),对于括号中的AF_INET和SOCK_STREAM,依照《Python核心编程(第3版)》书中的说法,AF_INET是python支持的四个套接字地址家族之一,另外三个是AF_UNIX、AF_NETLINK、AF_TIPC,针对网络编程主要使用 AF_INET。以下内容均引自书中原文:

~~套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication,IPC)。有两种类型的套接字:基于文件的和面向网络的。

~~UNIX 套接字是我们所讲的套接字的第一个家族,并且拥有一个“家族名字” AF_UNIX (又名 AF_LOCAL,在 POSIX1.g 标准中指定),它代表地址家族(address family): UNIX。 包括 Python在内的大多数受欢迎的平台都使用术语地址家族及其缩写 AF;其他比较旧的系统可能会将地址家族表示成域(domain)或协议家族(protocol family),并使用其缩写 PF 而 非 AF。类似地,AF_LOCAL(在 2000~2001 年标准化)将代替 AF_UNIX。然而,考虑到后向兼容性,很多系统都同时使用二者,只是对同一个常数使用不同的别名。 Python 本身仍 然在使用 AF_UNIX。

~~第二种类型的套接字是基于网络的,它也有自己的家族名字 AF_INET,或者地址家族: 因特网。另一个地址家族 AF_INET6 用于第 6版因特网协议(IPv6)寻址。此外,还有其他 的地址家族,这些要么是专业的、过时的、很少使用的,要么是仍未实现的。在所有的地址 家族之中,目前 AF_INET 是使用得最广泛的。

Python 2.5 中引入了对特殊类型的 Linux 套接字的支持。套接字的 AF_NETLINK 家族(无连接[见 2.3.3节])允许使用标准的 BSD 套接字接口进行用户级别和内核级别代码之间的 IPC。之前那种解决方案比较麻烦,而这个解决方案可以看作一种比前一种更加优雅且风险更低的解决方案,例如,添加新系统调用、 /proc 支持,或者对一个操作系统的“IOCTL”

~~针对 Linux 的另一种特性(Python 2.6 中新增)就是支持透明的进程间通信(TIPC)协 议。 TIPC允许计算机集群之中的机器相互通信,而无须使用基于 IP 的寻址方式。 Python 对 TIPC 的支持以 AF_TIPC 家族的方式呈现。

总的来说, Python 只支持 AF_UNIX、 AF_NETLINK、 AF_TIPC 和 AF_INET 家族。

而对于括号中的SOCK_STREAM,是TCP套接字类型,本文用的TCP所以使用SOCK_STREAM
~~实现面向连接的通信主要协议是传输控制协议(TCP),而为了创建TCP套接字,必须使用SOCK_STREAM作为套接字类型。
~~实现非面向连接的通信主要协议是用户数据包协议(UDP),而为了创建UDP套接字,必须使用**SOCK_DGRAM **作为套接字类型。

——————————————————————————————————————————————

注解[2] try: # 异常捕获 [2]:

~~这里其实是对该程序退出时引起的异常进行的处理,处不处理程序都正常运行只是退出有异常,红色提示让人觉得代码运行有瑕疵,所以处理一下。假如代码中不加入try-except异常捕获,那么程序运行在客户端发送exit指令退出连接后,服务器端是这样的:

python的tcp套接字获取端口号 python中tcp_python的tcp套接字获取端口号_04


异常类型为ConnectionResetError,捕获处理后就正常了:

python的tcp套接字获取端口号 python中tcp_TCP_05


————————————————————————————————————————————————

注解[3]: while True: #开启循环

参考《Python核心编程(第3版)》书中内容,这是TCP服务器套接字伪代码,就是TCP套接字服务器定义的一般方式,下图就是截自书中:

python的tcp套接字获取端口号 python中tcp_python的tcp套接字获取端口号_06


————————————————————————————————————————————————

注解[4]: con.send(’—first connection—’.encode(‘utf-8’)) #发送消息到客户端

别的没什么说的,主要说一下这个encode(‘utf-8’):一开始只是在程序中部分send函数后添加encode(‘utf-8’),程序运行的结果就是:

python的tcp套接字获取端口号 python中tcp_套接字_07


~~这个bytes-like就是字节类型数据而不是str类型。从我百度那么多资料来看,猜测不管是客户端还是服务端,send发送消息都要进行encode(‘utf-8’)转码,在接收端接收消息都要相应地进行deconde(‘utf-8’)解码,注解[5]就是解码例子。尽管是猜测,但代码跑的结果来看似乎真的是这样。而且这样转码之后也可以发送中文消息,不然接收到的中文消息是一段码,是的,我试过了。

——但不知道为什么,按照书中在发送端就进行格式化消息发送,虽然代码写的时候不会有标红报错,但一运行就出错了,同样是上图中的TypeError类型错误,可能是我太菜鸡还不知道怎么格式化发送吧,所以本菜鸡只能在消息接收端格式化接收了,前提是客户端和服务器端时间同步。

————————————————————————————————————————————————

注解[5]: print(’【%s】%s’ %(ctime(),data.decode(‘utf-8’))) #格式化输出收到的客户端消息

注解[4]已经说了这下没得说了,就是在消息接收端对于消息的解码,将str类型转换为bytes-like类型。
————————————————————————————————————————————————

注解[6]:except ConnectionResetError: #捕获异常类型 [6]

~~在注解[2]里也已经说过了,通过运行程序按照提示获知异常类型进行except异常捕获并处理,当然大佬不用跑程序一眼就可以看出来哪里可能会出现什么异常就提前捕获了。
————————————————————————————————————————————————
————————————————————————————————————————————————
————————————————————————————————————————————————

服务器端说完了,现在说TCP客户端:

from socket import *                                         #导入socket库
from time import ctime                                       #导入ctime函数
cs=socket(AF_INET,SOCK_STREAM)                               #定义socket客户端clientserver
ip_port=('127.0.0.1',9998)                                   #地址和端口
cs.connect(ip_port)                                          #客户端用connect方法而不是accept,acccpt作为服务器监听,客户端需要连接服务器监听的地址和端口

while True:
    getinfo=cs.recv(1024)                                    #接收服务端消息,缓冲区为1K
    print('【%s】%s' %(ctime(),getinfo.decode('utf-8')))      #将消息解码并格式化输出
    quest=input('client :')
    cs.send(bytes(quest,'utf-8'))                            #bytes-like类型转码的另一种方式
    if quest=='exit':
        print('连接断开,面对疾风咯!')
        break                                                #断开连接

客户端也是有伪代码滴,一般格式同样截自书中:

python的tcp套接字获取端口号 python中tcp_客户端_08


——————————————————————————————————————————————————

——————————————————————————————————————————————————

接下来是代码运行结果,面对疾风吧!

——因为服务器端是一直监听端口的,所以应该先运行服务器端,如果先运行客户端,那么将无法进行任何连接,因为没有服务器等待接受请求。所以先放客户端:

客户端发送消息:我太难了,老铁最近我压力好大,并正常退出

python的tcp套接字获取端口号 python中tcp_套接字_09


服务器端:接收到客户端的消息,收到exit后退出

python的tcp套接字获取端口号 python中tcp_TCP_10