哈尔滨工业大学服务计算实验报告
容器化应用部署与通信实验
基础实验
实验过程
在本次实验当中,我选择了Ubuntu 24.04 LTS
作为两个程序的开发容器,使用C语言作为编程语言。
-
Ubuntu镜像的拉取:
docker pull ubuntu:24.04
-
通过拉取的Ubuntu镜像构建一个开发容器:
docker run -dit ubuntu
-
我是通过使用
vscode
的docker
插件,连接到容器:
-
安装gcc和g++以及vim编辑器:
apt-get update apt-get install -y gcc g++ vim # 测试gcc、g++、vim是否安装成功 gcc --version g++ --version vim --version
-
编写客户端client.c代码,发包间隔1s,发送的数据包格式为:
Sequence: 序号,Target IP: IP地址,Target Port: 端口号
:#include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #define BUFFER_SIZE 1024 int main() { int sockfd; struct sockaddr_in servaddr; char buffer[BUFFER_SIZE]; int sequence_number = 0; // 获取环境变量 char *target_ip = getenv("IP"); char *target_port = getenv("PORT"); if (target_ip == NULL || target_port == NULL) { fprintf(stderr, "Invalid environment variables: IP or PORT\n"); return 1; } int port = atoi(target_port); if (port <= 0) { fprintf(stderr, "Invalid port number: %s\n", target_port); return 1; } // 创建UDP socket sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket creation failed"); return 1; } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); if (inet_pton(AF_INET, target_ip, &servaddr.sin_addr) <= 0) { fprintf(stderr, "Invalid IP address: %s\n", target_ip); close(sockfd); return 1; } while(1){ // 准备数据包 snprintf(buffer, BUFFER_SIZE, "Sequence: %d, Target IP: %s, Target Port: %d", sequence_number, target_ip, port); // 发送数据包 if (sendto(sockfd, buffer, strlen(buffer), 0, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("sendto failed"); close(sockfd); return 1; } printf("Sent Massage: %s\n", buffer); sleep(1); sequence_number++; } close(sockfd); return 0; }
-
编写服务端server.c代码,监听端口指定为8080,接收数据包并解析数据包内容,输出至文件:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #define BUFFER_SIZE 1024 int main() { int sockfd; struct sockaddr_in servaddr, cliaddr; char buffer[BUFFER_SIZE]; socklen_t len; ssize_t n; // 创建UDP socket sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket failed"); return 1; } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(8080); // 服务端监听指定为8080端口 // 绑定socket if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind failed"); close(sockfd); return 1; } len = sizeof(cliaddr); while (1) { // 接收数据包 n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&cliaddr, &len); if (n < 0) { perror("recvfrom failed"); continue; } buffer[n] = '\0'; // 确保字符串以null结尾 // 解析并输出到文件 FILE *file = fopen("received_data.txt", "a"); if (file == NULL) { perror("file open failed"); continue; } fprintf(file, "Received from %s:%d - %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buffer); fclose(file); printf("Received Massage: %s\n", buffer); } close(sockfd); return 0; }
-
将两个程序编译成可执行文件(这两个.c文件我将它放在了
/apps
目录下):gcc client.c -o client gcc server.c -o server
-
随后将当前容器构建为新的镜像(在Windows的终端当中执行以下命令,以后内容都在Windows的终端下执行):
docker commit -a "fang" -m "listen8080" < 容器ID(使用docker ps命令查看当前Ubuntu开发容器的ID) > client-server # -a:指定新作者 # -m:指定提交说明 # client-server:这是我指定新镜像的名称
-
将新镜像构建两个新的容器并启动:
docker run -dit client-server # 执行两次
-
查看两个容器的IP地址:
docker inspect 3e2 docker inspect 393
3e2的容器地址:172.17.0.4
393的容器地址:172.17.0.3
-
接下来将393作为服务端,3e2作为客户端,分别运行客户端和服务端程序,实现两个容器之间的相互通信(因为实验要求相互通信,但是实际上反过来操作基本上完全相同,所以在这里就展示一种情况):
-
启动服务端
docker exec -it 393 /bin/bash cd /apps ./server
此时
server
从监听端口8080没有接收到任何消息,所以不打印任何信息 -
再开一个Windows终端
docker exec -it 3e2 /bin/bash cd /apps export IP=172.17.0.3 export PORT=8080 ./client
此时
client
向172.17.0.3:8080
持续发送数据包,并打印了接收到的信息,并将其写入到received_data.txt
文件中。
使用
vscode
连接到393
容器,查看received_data.txt
文件,可以看到client
发送的消息。
-
实验结果
本次实验成功实现了两容器之间的通信,并将接收到的信息写入到文件中。
镜像已经上传至Docker Hub
,可通过如下命令拉取:
docker pull yuqingfang2004/client-server:listen8080