10. 网络编程详解

一、前言

前一章节我们通过对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()

总结一下服务器流程:

  1. 创建服务器的监听套接字:so_server=socket.socket()
  2. 绑定本机IP和端口:so_server.bind(('0.0.0.0',5544))
  3. 监听客户端:listen
  4. 等待客户端连接,返回的套接字才是用来与该客户端通信的:so_client=so_server.accept()
  5. 发送消息:so_client.send('数据')
  6. 接收消息:so_client.recv(1024)
  7. 关闭socket:so_client.close()与so_server.close()
作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux