CC++ & Algorithm

计算机网络基础

中等9
语言版本:C++Python
概述:把计算机网络比作“数字快递系统”,用Python写一个简单的“传信”程序,让你明白计算机怎么互相通信。

网络通信原来这么简单——用Python模拟收发消息

计算机网络就像一套“数字快递系统”。每台计算机(设备)都有一个唯一的“地址”——IP地址,就像家的门牌号。数据被切成一个个“包裹”(数据包),在路上经过路由器(像快递站)的转发,最终送到目的地。今天我们就用一个简单的Python程序,模拟两台电脑(客户端和服务器)之间的通信,把看不见的网络过程变成能运行的代码。

一、网络中的两个角色:谁请求,谁响应?

在网络上,两个设备通信时通常扮演不同的角色:

  • 客户端:主动发起请求的一方。比如你打开浏览器输入网址,你的浏览器就是客户端,它向存放网页的服务器说“请把首页内容给我”。
  • 服务器:被动等待请求,然后提供数据的一方。例如存放网页的电脑(服务器)收到请求后,把网页文件传给你。

生活中的例子:你(客户端)去便利店买东西,告诉店员要什么;店员(服务器)从货架上拿给你。没有你主动问,店员不会凭空把东西塞给你。

二、IP地址和端口:门牌号 + 房间号

每个连接互联网的设备都有一个IP地址,相当于“门牌号”,用来找到这台电脑。但一台电脑上可能同时运行很多网络程序(比如浏览器、微信、游戏),怎么知道数据该交给哪个程序呢?这就需要端口——相当于同一个门牌号下的不同房间号。

  • 常用端口:Web服务器(HTTP)用80,HTTPS用443,数据库用3306。
  • 特殊地址:127.0.0.1 表示“本机”,只在你自己电脑上测试时使用,数据不会真的发送到外网。
  • 我们自己选用的端口是12345,只要不和常用端口冲突就行。

三、数据包:把大文件切成小块

当你发送一条消息“你好,网络世界!”,电脑不会直接扔一串字符到网线上。它会把这串字符切分成多个小数据包(比如每包512字节),每个包都写上源地址、目的地址、序号。路由器根据目的地址把包一站一站转发,就像快递包裹经过多个中转站。最后接收方把所有包按序号拼回完整消息。

四、用Python写一个“传信”程序

Python自带的socket库可以让我们创建网络套接字(Socket),实现两台电脑间的通信。我们写一个最简单的例子:一台电脑当服务器(先启动),另一台当客户端(后启动),双方交换一句祝福。

注意:以下代码只能在 同一台电脑上测试(因为用了127.0.0.1),如果你想在两台不同电脑上试,把IP地址换成对方的实际IP(但需要关闭防火墙或设置允许)。

代码中每行都有中文注释,请仔细阅读。

1. 服务器端代码(先运行)
# 服务器端(先启动)
import socket  # 导入网络通信库

# 创建一个TCP套接字(AF_INET表示IPv4,SOCK_STREAM表示TCP)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定到本机127.0.0.1的12345端口
server.bind(('127.0.0.1', 12345))

# 开始监听,最多允许1个客户端排队等待连接
server.listen(1)
print("服务器已启动,等待客户端连接...")

# 接受客户端连接,返回一个新的套接字conn和客户端的地址addr
conn, addr = server.accept()
print(f"客户端 {addr} 已连接")

# 接收客户端发来的数据,最多接收1024字节,并解码为字符串
data = conn.recv(1024).decode()
print(f"收到客户端消息:{data}")

# 回复客户端:将字符串编码为字节再发送
conn.send("服务器收到了你的祝福!".encode())

# 关闭连接
conn.close()
server.close()
2. 客户端代码(后运行)
# 客户端(后启动)
import socket

# 创建TCP套接字
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接到服务器(IP和端口要和服务器一致)
client.connect(('127.0.0.1', 12345))

# 发送一条消息,先编码为字节
client.send("你好,网络世界!".encode())

# 接收服务器的回复,解码
reply = client.recv(1024).decode()
print(f"服务器回复:{reply}")

# 关闭连接
client.close()

五、运行步骤

  1. 打开两个终端窗口(或者用两个Python IDE标签页)。
  2. 先运行服务器程序,你会看到“服务器已启动,等待客户端连接...”。
  3. 再运行客户端程序,客户端会自动连接,发送消息,然后服务器打印收到的消息,回复,客户端打印回复。
  4. 两条消息成功传输后,程序结束。

六、新手容易犯的错误

  1. 程序运行顺序搞反:必须先启动服务器,再启动客户端。如果客户端先运行,它会因为找不到服务器而报错 Connection refused
  2. 端口被占用:如果你之前测试过同样的端口但没正常关闭程序,或者有其他软件占用了12345端口,服务器会报错 Address already in use。可以换一个端口号(比如12346),或者等一会儿再试。
  3. IP地址写错:如果改成其他IP(比如192.168.1.x),但另一台电脑没连到同一个局域网,或者防火墙没放行,连接会失败。
  4. 忘记编码/解码send()recv()传输的是字节数据,所以发送前要.encode(),收到后要.decode(),否则会出现 TypeError 或乱码。
  5. 只开了一个终端:服务器和客户端必须同时运行,如果在一个终端里先跑服务器,按Ctrl+C中断后再跑客户端,服务器已经关闭,客户端也会报错。

七、完整可运行示例(含注释)

为了方便复制,我把上面两段代码合并到一块,但实际运行时请分开在两个文件或两个终端里跑。

文件1: server.py

# 服务器端
import socket

# 创建TCP套接字
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定到本机端口12345
server.bind(('127.0.0.1', 12345))
# 开始监听,允许1个客户端排队
server.listen(1)
print("服务器已启动,等待客户端连接...")

# 接受连接
conn, addr = server.accept()
print(f"客户端 {addr} 已连接")

# 接收数据并解码
data = conn.recv(1024).decode()
print(f"收到客户端消息:{data}")

# 发送回复
conn.send("服务器收到了你的祝福!".encode())

# 关闭
conn.close()
server.close()

文件2: client.py

# 客户端
import socket

# 创建TCP套接字
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器
client.connect(('127.0.0.1', 12345))

# 发送消息
client.send("你好,网络世界!".encode())

# 接收回复
reply = client.recv(1024).decode()
print(f"服务器回复:{reply}")

# 关闭
client.close()

运行后你会看到类似输出:

服务器已启动,等待客户端连接...
客户端 ('127.0.0.1', 54321) 已连接
收到客户端消息:你好,网络世界!

服务器回复:服务器收到了你的祝福!

八、相关指引

  • 如果想进一步学习网络编程,可以了解 TCP/UDP的区别(UDP是无连接的,像发短信不保证对方一定收到)。
  • 实际应用中,我们很少直接用socket写HTTP请求,而是使用更高级的库如requests(你只需要requests.get("https://www.example.com")就能拿到网页内容)。
  • 网络的基础协议:IP(互联网协议)负责寻址,TCP(传输控制协议)负责可靠传输。
  • 如果你想把程序变成真正的聊天软件,可以加上多线程,让服务器同时服务多个客户端,或者让客户端也能不停发送消息。

现在你已经亲手写了一个网络通信程序,恭喜你迈出了理解计算机网络的第一步!

例题精讲

1单选题

在OSI七层模型中,哪一层负责将数据包从源端路由到目的端?

A物理层
B数据链路层
C网络层
D传输层
2单选题

TCP和UDP都是传输层协议,其中哪个协议是面向连接的?

ATCP
BUDP
C两者都是
D两者都不是
3判断题

IP地址192.168.1.1属于私有IP地址范围。

4判断题

HTTP协议默认使用TCP端口443进行通信。

5填空题
使用Python socket库获取域名对应的IP地址,请补全代码。
import socket
domain = "___"
ip = socket.gethostbyname(domain)
print(f"IP address of {domain} is {ip}")