一、前言
前一章节我们通过对GUI的学习,我们其实就已经可以开始开发一些小软件了。
但想要让软件、程序更加的通用化,那么网络编程就是必不可少的一部分。
对于网络的理解,可以参照本站的这篇文章:网络编程。
本文主要讲解如何使用python完成网络编程。
一般网络编程我们只需要关注两个协议,一个是tcp、另一个则是udp,下面对其分别进行讲解。
二、TCP实现聊天
1.Tcp服务器
这里还是有必要说明一下为什么要写Tcp服务器。
两个电脑想要进行通信,就必须得有一台电脑等待另一台电脑来连。
等待别人连接的我们称它为“服务器”,主动去连接的我们称它为“客户端”。
这也常被成为C/S架构,即“客户端/服务器”架构。
不同平台的网络编程并不完全相同,但python作为一门高级语言,已经将底层的细节都抽象好成了通用、好使的接口函数、对象,我们直接用就行了。
并且由于python是跨平台的,所以你只需要写一份代码,就可以拿到多个系统上运行!
如果你一入门就学的python,那可能确实感受不到,而如果你学的C++,那应该就会稍微理解的更深一点,比如,当你用C++在windows、linux上进行网络编程的话,你就得分别写两份代码!
在python中进行网络编程,需要引入下面这个模块:
import socket
socket
翻译过来也称为套接字,python的网络编程中,我们就是用的它来调用系统的网络资源。
这个模块中就有一个socket
类,我们首先需要实例化一个socket
对象:
so_server = socket.socket()
此时这个so_server
对象就是一个套接字了,后面我们就将这个套接字当作服务器来使用。
没错,客户端同样使用这个方式来创建的套接字
所谓套接字,就相当于门牌号,而电脑就相当于一个公寓小区
因为我们一台电脑里面可以有很多软件,每一个软件都需要通信的话,电脑如何区分呢?
答案就是通过套接字,我们可以通过套接字绑定电脑的网络资源,然后就可以使用电脑的网络资源与其它电脑进行通信。
然后来到下一步就是绑定电脑资源,调用函数:bind
so_server.bind(('0.0.0.0', 5544))
这个函数很简单,只接受一个元组,并且这个元组内只有两个数据。
-
第一个数据就是我们想要绑定的ip地址。
-
第二个数据是我们想要绑定的端口号。
首先是ip地址,一台电脑是可以拥有多个ip地址的,比如当你电脑连接上一个wifi
时,就会为你分配一个ip地址。
同时还有一个特殊的ip地址:127.0.0.1
,被用于本机自己使用,也就是即使你不连接任何网络,这个ip地址也是可以使用的,一般用于自己电脑上测试软件之内的。
称为
本机回环地址
,即只能用于本机的应用之间进行通信。
而这个0.0.0.0
地址同样很特殊,它代指当前电脑上所有可用的ip地址,所以无论你当前电脑上拥有几个ip地址,它都会将其绑定到这个套接字上。
这样无论客户端从哪个ip地址连上来,就都是可以的。
接着是端口号的问题,这个是每台电脑硬件这么规定的,即用16位二进制表示端口号,2的16次方等于65536
。
即我们能使用的端口号范围就是0到65535
,但小于1024的端口号一般都有默认的功能。
比如我们上网浏览网页,其网站默认就是使用的80
端口或443
端口。
比如你可以通过网址连接百度的服务器电脑:
https://www.baidu.com/
但其实这就是省略了端口号,选用的默认,上面实际上应该为:
https://www.baidu.com/:443
如果你将443改为其它端口就连接不上了!
所以我们一般选用较大的端口号,比如几千,一两万的都是可以的,太大也不好,因为它是系统自动分配的端口号。
比如还是连接百度的服务器为例,我们只是指定了要连接对方的哪个端口号,却并没有指定自己用哪个端口去连接啊!咋也能连上去?
这其实就是系统帮我们自动分配的端口号!
可以理解为一台电脑就是一栋大楼,ip就是大楼的地址,端口就是这栋大楼中具体的每家每户门牌号,即各种应用程序。
而127.0.0.1
这个地址呢,就是本楼各个用户之间交换数据的内部地址,外部其他楼是看不到的。
绑定完成后就是监听了,这里调用的函数为listen
:
so_server.listen(5)
它只有一个参数,就是同时可监听的客户端数量。
因为监听到后,还需要服务器与客户端建立连接,这直接就有一个时间差,那么后面的客户端就得等着,最多等多少个呢?
这就是这个参数的作用,如果等待的客户端数量超过了这个数量,后来的客户端就会被直接拒绝连接,而这个数量一般都为5
。
然后我们就可以等待客户端连接了,等待连接的函数为:accept
(so_client,addr) = so_server.accept()
它的作用就是卡在这里,等待客户端连接上来,然后返回可以与客户端通信的套接字与地址。
注意,它的返回值是元组类型,为了方便,我直接用的两个参数分开将其接收。
如果没有客户端连接上来,它就一直卡在这里,等待!
同时要特别注意,与客户端通信的套接字与等待客户端连接的套接字不是一个用途!不要尝试用等待客户连接的套接字来给客户端发送消息!
然后我们就可以和客户端收发数据了!
print(f'客户端ip地址:{addr}')
so_client.send('你已成功连接到服务器!'.encode('utf-8')) # 给客户端发送数据
buf = so_client.recv(1024) # 接收客户端发来的数据
print(f'客户端:{buf.decode("utf-8")}')
这里我先调用的发送数据函数send
,它的参数就是你想要发送的内容,这里我给客户端发送了一个提示信息。
由于网络上发送的数据均为字节数据,所以我们的字符串需要先编码为字节数据,方法很简单,就是调用它的encode
函数即可,它的参数为编码类型,一般都是utf-8
编码。
编码相关的知识可以参考这篇文章:编码。
然后就是接受客户端发来的消息,使用recv
函数,它有一个参数就是缓存区大小。
因为我们无法确定客户端每次会发送来多大的数据,所以就需要自定义一个缓存区等待客户端发送来的数据填充。
如果缓冲区被填满了,又或者现在缓存区已经有数据而客户端此刻并没有继续发送数据,这个函数就会将这些接收到的数据进行返回。
没错,这个函数会卡在这里的,和上面的accept
函数一样,只有当客户端发送来数据后才会返回!
同样的,这个函数收到的也只是字节数据。
所以我们需要将其进行解码操作,也就是调用函数decode
,由于前面字符串我们用的utf-8
进行的编码,这里也要用utf-8
进行解码才能还原称为字符串。
最后,在程序结束前,我们还需要释放掉这些网络资源,调用其close
函数即可:
so_client.close()
so_server.close()
虽然一般程序结束之后,系统会帮我们把所有东西都处理好,但我们也还是要养成好习惯。
虽然小程序无所谓,但如果是那种比较大的程序,如果你不使用,却又一直不关闭,程序也不会立即结束,那就会极大占用系统资源,比如qq、微信等。
至此,我们就完成了一个最简单的服务器程序:
import socket
so_server = socket.socket()
so_server.bind(('0.0.0.0', 5544))
so_server.listen(5)
(so_client, addr) = so_server.accept()
print(f'客户端ip地址:{addr}')
so_client.send('你已成功连接到服务器!'.encode('utf-8')) # 给客户端发送数据
buf = so_client.recv(1024) # 接收客户端发来的数据
print(f'客户端:{buf.decode("utf-8")}')
so_client.close()
so_server.close()
总结一下服务器流程:
- 创建服务器的监听套接字:
so_server=socket.socket()
- 绑定本机IP和端口:
so_server.bind(('0.0.0.0',5544))
- 监听客户端:
listen
- 等待客户端连接,返回的套接字才是用来与该客户端通信的:
so_client=so_server.accept()
- 发送消息:
so_client.send('数据')
- 接收消息:
so_client.recv(1024)
- 关闭socket:
so_client.close()与so_server.close()