两台CentOS服务器上的1000个Docker容器(每台500个)之间实现UDP通信(C语言版本) 给女朋友对象写得,她不会,我就写了一个
-
为了帮助您在两台CentOS服务器上的1000个Docker容器(每台500个)之间实现UDP通信,我将详细地逐步指导您完成整个过程。我们将使用C语言编写UDP服务器和客户端程序,并使用Docker来管理容器和网络配置。
-
我会在每个步骤中明确指出您需要修改的地方,以及需要特别注意的事项。由于您是初学者,我会尽量以简单易懂的方式来解释。
-
请您一步一步来看 ,仔细阅读里面相关的注意事项哦~~
重点:
❤️:你拿到过后,你先仔细的全部浏览一遍,再去动手操作哦~
目录
- 环境准备
- 安装Docker
- 检查网络连通性
- 编写UDP服务器程序
- 代码示例
- 详细注释
- 编写UDP客户端程序
- 代码示例
- 详细注释
- 创建Docker镜像
- 编写Dockerfile
- 构建镜像
- 运行服务器容器(服务器A)
- 编写运行脚本
- 解释脚本内容
- 运行客户端容器(服务器B)
- 编写运行脚本
- 解释脚本内容
- 网络和防火墙配置
- 修改防火墙设置
- 检查网络连接
- 验证通信
- 检查服务器日志
- 确认消息接收
- 注意事项和可能的问题
- 系统资源限制
- 防火墙和安全性
- 日志和监控
- 总结
1. 环境准备
1.1 安装Docker (这个我们安装了,可以 跳过了!❤️)
在两台CentOS服务器上都需要安装Docker。如果还未安装,请按照以下步骤进行:
# 更新包索引
sudo yum update -y# 安装必要的包
sudo yum install -y yum-utils device-mapper-persistent-data lvm2# 添加Docker仓库
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo# 安装Docker CE
sudo yum install -y docker-ce docker-ce-cli containerd.io# 启动Docker服务
sudo systemctl start docker# 设置Docker开机自启
sudo systemctl enable docker
注意:
-
权限问题:默认情况下,只有root用户或docker组的用户才能运行Docker命令。为了方便,您可以将当前用户添加到docker组:
sudo usermod -aG docker $(whoami)
然后退出并重新登录以使更改生效。
1.2 检查网络连通性(但是 我们需要 检测两台服务器是否可以走的通的!⚠️)(从这一步出发 ❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️)
确保两台服务器之间可以互相访问。
-
测试连接:
# 在服务器B上,尝试ping服务器A ping <服务器A的IP地址>
-
注意:如果ping不通,可能需要检查网络配置或联系网络管理员。
2. 编写UDP服务器程序 (下面是 重点了,请您仔细阅读,一步一步来,不要着急)
在服务器A上,创建一个目录来存放代码:
mkdir ~/udp_project
cd ~/udp_project
2.1 代码示例
创建一个名为udp_server.c
的文件,内容如下:
// udp_server.c/** UDP服务器程序* 监听指定的UDP端口并接收消息。** 编译:* gcc -o udp_server udp_server.c** 用法:* ./udp_server <端口号>*/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define BUFFER_SIZE 1024 // 接收消息的最大缓冲区大小int main(int argc, char *argv[]) {int sockfd; // 套接字文件描述符int port; // 监听的端口号struct sockaddr_in server_addr; // 服务器地址结构struct sockaddr_in client_addr; // 客户端地址结构char buffer[BUFFER_SIZE]; // 接收消息的缓冲区socklen_t addr_len; // 客户端地址结构的大小ssize_t recv_len; // 接收到的消息长度// 检查参数数量是否正确if (argc != 2) {fprintf(stderr, "用法: %s <端口号>\n", argv[0]);exit(1);}// 将端口号从字符串转换为整数port = atoi(argv[1]);// 创建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket 创建失败");exit(1);}// 将服务器地址结构清零memset(&server_addr, 0, sizeof(server_addr));// 设置地址族为AF_INET(IPv4)server_addr.sin_family = AF_INET;// 监听所有可用的网络接口server_addr.sin_addr.s_addr = INADDR_ANY;// 设置端口号(将主机字节序转换为网络字节序)server_addr.sin_port = htons(port);// 将套接字绑定到指定的端口上if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("绑定失败");close(sockfd);exit(1);}printf("UDP服务器正在监听端口 %d\n", port);// 无限循环,持续接收消息while (1) {addr_len = sizeof(client_addr);// 接收消息recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,(struct sockaddr *)&client_addr, &addr_len);if (recv_len < 0) {perror("接收失败");continue;}// 在接收到的数据末尾添加字符串结束符buffer[recv_len] = '\0';// 打印客户端信息和消息内容printf("从 %s:%d 收到消息:%s\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);}// 关闭套接字(实际上不会到达这里)close(sockfd);return 0;
}
2.2 详细注释
- 头文件包含:包含了网络编程和基本的C库函数所需的头文件。
- 宏定义:定义了缓冲区大小
BUFFER_SIZE
。 main
函数参数检查:确保运行程序时传入了正确的参数(端口号)。- 套接字创建:使用
socket()
函数创建一个UDP套接字。 - 地址结构初始化:使用
memset()
清零,然后设置地址族、IP地址和端口号。 - 绑定套接字:使用
bind()
函数将套接字绑定到指定的IP和端口上。 - 接收循环:使用
recvfrom()
函数接收来自客户端的消息,并打印出来。
3. 编写UDP客户端程序
在服务器B上,同样创建一个目录:
mkdir ~/udp_project
cd ~/udp_project
3.1 代码示例
创建一个名为udp_client.c
的文件,内容如下:
// udp_client.c/** UDP客户端程序* 向指定的IP地址和UDP端口发送消息。** 编译:* gcc -o udp_client udp_client.c** 用法:* ./udp_client <服务器IP> <服务器端口> <消息>*/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define BUFFER_SIZE 1024 // 消息的最大缓冲区大小int main(int argc, char *argv[]) {int sockfd; // 套接字文件描述符char *server_ip; // 服务器IP地址int server_port; // 服务器端口号struct sockaddr_in server_addr; // 服务器地址结构char *message; // 要发送的消息ssize_t sent_len; // 发送的消息长度// 检查参数数量是否正确if (argc != 4) {fprintf(stderr, "用法: %s <服务器IP> <服务器端口> <消息>\n", argv[0]);exit(1);}// 获取服务器IP、端口和消息server_ip = argv[1];server_port = atoi(argv[2]);message = argv[3];// 创建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket 创建失败");exit(1);}// 将服务器地址结构清零memset(&server_addr, 0, sizeof(server_addr));// 设置地址族为AF_INET(IPv4)server_addr.sin_family = AF_INET;// 将IP地址从文本转换为二进制形式if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {fprintf(stderr, "无效的IP地址:%s\n", server_ip);close(sockfd);exit(1);}// 设置端口号(网络字节序)server_addr.sin_port = htons(server_port);// 发送消息到服务器sent_len = sendto(sockfd, message, strlen(message), 0,(struct sockaddr *)&server_addr, sizeof(server_addr));if (sent_len < 0) {perror("发送失败");close(sockfd);exit(1);}printf("已向 %s:%d 发送消息:%s\n", server_ip, server_port, message);// 关闭套接字close(sockfd);return 0;
}
3.2 详细注释
- 参数检查:确保提供了服务器IP、端口和消息。
- 套接字创建:同样使用
socket()
函数创建UDP套接字。 - 地址结构设置:将服务器的IP和端口填入
server_addr
结构。 - 发送消息:使用
sendto()
函数发送消息到服务器。
4. 创建Docker镜像
4.1 编写Dockerfile
在两台服务器的udp_project
目录下,创建一个名为Dockerfile
的文件,内容如下:
# 使用官方的GCC镜像作为基础镜像
FROM gcc:latest# 在容器内创建工作目录
WORKDIR /usr/src/app# 将源代码复制到容器内
COPY udp_server.c udp_client.c ./# 编译UDP服务器和客户端程序
RUN gcc -o udp_server udp_server.c
RUN gcc -o udp_client udp_client.c# 暴露默认端口(可在运行时覆盖)
EXPOSE 5000/udp# 设置默认命令(可在运行容器时覆盖)
CMD ["./udp_server", "5000"]
解释:
- FROM:指定基础镜像为官方的
gcc
镜像,包含了GCC编译器。 - WORKDIR:设置工作目录为
/usr/src/app
。 - COPY:将
udp_server.c
和udp_client.c
复制到容器内的工作目录。 - RUN:编译服务器和客户端程序。
- EXPOSE:暴露5000端口的UDP协议(可在运行时指定其他端口)。
- CMD:设置容器启动时默认执行的命令。
4.2 构建镜像
在两个服务器的udp_project
目录下,执行以下命令构建Docker镜像:
docker build -t udp_app .
注意:
- 镜像名称:
udp_app
是镜像的名称,您可以根据需要更改。 - 构建过程:Docker会根据
Dockerfile
的指令一步步构建镜像。
5. 运行服务器容器(服务器A)
5.1 编写运行脚本
在服务器A的udp_project
目录下,创建一个名为run_servers.sh
的脚本,内容如下:
#!/bin/bash# 要运行的服务器容器数量
NUM_CONTAINERS=500# 服务器的基础端口号
BASE_PORT=5000for (( i=1; i<=NUM_CONTAINERS; i++ ))
doCONTAINER_NAME="server_$i"HOST_PORT=$((BASE_PORT + i))CONTAINER_PORT=$((BASE_PORT + i))# 运行服务器容器docker run -d --name $CONTAINER_NAME -p $HOST_PORT:$CONTAINER_PORT/udp udp_app ./udp_server $CONTAINER_PORTecho "已在端口 $HOST_PORT 启动服务器容器 $CONTAINER_NAME"
done
5.2 解释脚本内容
- 循环:从1到500,依次启动500个容器。
- 容器名称:
server_1
、server_2
、…、server_500
。 - 端口设置:
- 主机端口:
HOST_PORT
,从5001到5500。 - 容器端口:
CONTAINER_PORT
,与主机端口相同。
- 主机端口:
- 运行容器:
-d
:后台运行容器。--name
:指定容器名称。-p
:将主机的UDP端口映射到容器的UDP端口。udp_app
:使用之前构建的镜像。./udp_server $CONTAINER_PORT
:在容器内执行的命令,指定监听的端口。
需要您修改的地方:
- NUM_CONTAINERS:如果您想运行不同数量的容器,可以修改这个值。
6. 运行客户端容器(服务器B)
6.1 编写运行脚本
在服务器B的udp_project
目录下,创建一个名为run_clients.sh
的脚本,内容如下:
#!/bin/bash# 要运行的客户端容器数量
NUM_CONTAINERS=500# 客户端的基础端口号(用于区分消息)
BASE_PORT=5000# 服务器A的IP地址
SERVER_A_IP="请替换为服务器A的实际IP地址"for (( i=1; i<=NUM_CONTAINERS; i++ ))
doCONTAINER_NAME="client_$i"SERVER_PORT=$((BASE_PORT + i))MESSAGE="来自客户端 $i 的问候"# 运行客户端容器docker run -d --name $CONTAINER_NAME udp_app ./udp_client $SERVER_A_IP $SERVER_PORT "$MESSAGE"echo "已启动客户端容器 $CONTAINER_NAME,发送到 $SERVER_A_IP:$SERVER_PORT"
done
6.2 解释脚本内容
- 服务器IP地址:需要将
SERVER_A_IP
替换为服务器A的实际IP地址。 - 循环:启动500个客户端容器。
- 容器名称:
client_1
、client_2
、…、client_500
。 - 服务器端口:与服务器容器的端口对应,从5001到5500。
- 消息内容:每个客户端发送一条包含自己编号的消息。
- 运行容器:
-d
:后台运行。--name
:容器名称。udp_app
:使用同样的镜像。./udp_client $SERVER_A_IP $SERVER_PORT "$MESSAGE"
:在容器内执行的命令,指定服务器IP、端口和消息。
需要您修改的地方:
- SERVER_A_IP:❤️一定要替换为服务器A的实际IP地址,否则客户端无法连接到服务器❤️。
- NUM_CONTAINERS:如果需要,可以修改客户端容器的数量❤️。
7. 网络和防火墙配置
7.1 修改防火墙设置(服务器A)
为了允许UDP流量通过指定的端口,需要修改防火墙设置。
# 添加端口范围的UDP规则
sudo firewall-cmd --zone=public --add-port=5001-5500/udp --permanent# 重新加载防火墙
sudo firewall-cmd --reload
注意:
- 防火墙软件:如果使用的是
firewalld
,上述命令适用。如果使用其他防火墙,需要使用相应的命令。 - 端口范围:确保与您在脚本中使用的端口范围一致。
7.2 检查网络连接
在服务器B上,测试是否可以连接到服务器A的指定端口。
# 使用nc(netcat)工具测试UDP端口
echo "测试" | nc -u -v <服务器A的IP地址> 5001
如果连接成功,说明网络连通性正常。
8. 验证通信
8.1 检查服务器日志
在服务器A上,查看某个服务器容器的日志,例如server_1
:
docker logs server_1
您应该看到类似以下的输出:
UDP服务器正在监听端口 5001
从 <客户端IP>:<端口> 收到消息:来自客户端 1 的问候
8.2 确认消息接收
- 多个容器日志:可以检查其他服务器容器的日志,确认是否收到了对应客户端的消息。
- 故障排查:如果没有收到消息,请检查以下内容:
- 服务器A的防火墙设置:确保开放了必要的UDP端口。
- 服务器IP地址是否正确:在
run_clients.sh
脚本中。 - 网络连通性:使用
ping
或nc
测试。
9. 注意事项和可能的问题
9.1 系统资源限制
-
CPU和内存:运行大量容器会占用大量系统资源。请监控系统的CPU和内存使用情况。
-
文件描述符限制:可能需要增加系统的文件描述符限制。
# 临时增加限制 ulimit -n 65535
9.2 防火墙和安全性
- 安全策略:确保只允许可信任的流量。对于生产环境,建议使用VPN或设置访问控制。
- 端口范围:开放大量端口可能存在安全风险,务必确保网络的安全性。
9.3 日志和监控
- 日志收集:考虑使用日志收集工具,如ELK堆栈,来集中管理容器日志。
- 监控工具:使用监控工具,如Prometheus和Grafana,监控系统性能和容器状态。
10. 总结
通过以上步骤,您已经成功在两台CentOS服务器上的1000个Docker容器之间建立了UDP通信。我们使用C语言编写了简单的UDP服务器和客户端程序,并利用Docker来管理容器的部署。
您需要注意的关键点:
- IP地址和端口号:确保在脚本和代码中使用正确的IP地址和端口号。
- 防火墙配置:确保防火墙允许必要的UDP流量通过。
- 系统资源:监控系统的资源使用,防止过载。
- 代码编译:如果修改了C代码,需要重新构建Docker镜像。