哈尔滨工业大学服务计算实验报告

哈尔滨工业大学服务计算实验报告

容器化应用部署与通信实验

基础实验

实验过程

在本次实验当中,我选择了Ubuntu 24.04 LTS作为两个程序的开发容器,使用C语言作为编程语言。

  • Ubuntu镜像的拉取:

    docker pull ubuntu:24.04
    
  • 通过拉取的Ubuntu镜像构建一个开发容器:

    docker run -dit ubuntu
    
  • 我是通过使用vscodedocker插件,连接到容器:

  • 安装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
      

      此时client172.17.0.3:8080持续发送数据包,并打印了接收到的信息,并将其写入到received_data.txt文件中。

      使用vscode连接到393容器,查看received_data.txt文件,可以看到client发送的消息。

实验结果

  本次实验成功实现了两容器之间的通信,并将接收到的信息写入到文件中。
  镜像已经上传至Docker Hub,可通过如下命令拉取:

docker pull yuqingfang2004/client-server:listen8080
  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值