• 福州大学《嵌入式系统综合设计》实验三:多媒体开发基础编程


    一、实验目的

    本实验基于搭建好的开发环境和硬件环境,通过编写简单的通信实验,验证开发环境,掌握多媒体开发编程基础,包括SOCKET编程、多线程编程和线程同步知识。

    二、实验内容

    基于套接字、多线程、同步锁机制实现多媒体文件的收发;

    发送端Ubuntu的PC机读取文件,每1024个字节组成一个包通过TCP报文发送到接收端;接收SE5上启动2个线程,线程1接收报文并将报文存入缓存;线程2通过缓存读取报文存入文件中;要求线程1和线程2之间通过同步锁进行线程同步。

    三、开发环境

    开发主机:Ubuntu 22.04 LTS

    硬件:算能SE5

    本地如果有SE5硬件,则可以PC机作为客户端,SE5作为服务器端。本地如果没有SE5硬件,只有云空间,则可以直接将客户端和服务器端都通过云空间实现,机在云空间的SE5模拟环境中实现。

    四、实验器材

    开发主机 + 云平台(或SE5硬件)

    五、实验过程与结论

    5.1 原理流程

    硬件部署环境如下图所示:

    如上图所示,可以利用PC作为客户端,SE5作为服务器端,将PC机的文件传送至SE5中。如果是云平台开发,可以直接将客户端和服务器端都放在云平台的模拟器中。此时,即在一台机器内既实现客户端也实现服务器端,设置服务器端的通信地址为回环地址(127.0.0.1)。

    客户端程序采用TCP协议进行文件收发。客户端程序采用单线程处理,在和服务器端建立连接后,循环读取流媒体文件,并进行套接字发送。客户端运行流程包含了:

    1. 创建套接字
    2. 输入执行文件名,传输文件名,服务器地址和端口四个参数
    3. 连接服务器的ip地址及端口
    4. 读取需要发送的媒体文件
    5. 启动TCP发送文件,
    6. 循环读取流媒体文件,直到结束后断开连接。

    3-1 客户端操作流程图

    接收端作为服务端采用多线程进行编程。主线程用于接收连接后接收客户端发送的报文存入缓存。另起一个线程用于从缓存中读取数据包并存入文件中。服务器端的运行流程包含如下关键步骤:

    1. 创建套接字描述符
    2. 绑定ip地址和端口便于客户端接入
    3. 监听是否有客户端发出连接请求
    4. 收到连接请求后启动接收和写文线程
    5. 将接受的报文存入缓存中,同时从缓存读取报文存入文件中
    6. 传输完成后重新等待连接请求。

    3-2 服务端操作流程图

    5.2 关键代码解析
    5.2.1 客户端

    由于需要用到套接字进行编程,因此在头文件上需要包含一些必要的头文件:

    1. #include        
    2. #include
    3. #include
    4. #include
    5. #include

    创建套接字,可以直接利用操作系统的SOCKET接口实现,关键函数如下:

    1. int sockfd;
    2. struct sockaddr_in servaddr;
    3. if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)   //创建套接字并判断是否成功
    4. {
    5.         printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
    6.         return 0;
    7. }
    8. memset(&servaddr, 0, sizeof(servaddr));    //初始化结构体
    9. servaddr.sin_family = AF_INET;            //设置地址家族
    10. servaddr.sin_port = htons(atoi(argv[3]));  //设置端口
    11.     //发出连接请求判断是否连接成功
    12. if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)        
    13. {
    14.         printf("connect error: %s(errno: %d)\n", strerror(errno), errno);
    15.         return 0;
    16. }

    至此,客户端主动向服务器发送链接。

    在发送端可通过fopen打开文件,通过fread函数读取流媒体文件:   

    1. if ((fq = fopen(argv[1], "rb")) == NULL)
    2.     {
    3.         /*判断文件是否打开*/
    4.         close(serverFd);
    5.         return -1;
    6.  }
    7. ......
    8. /*循环读取文件并发送*/
    9. size_t readLen = fread(buffer, 1, sizeof(buffer), fq);

    发送端启动TCP发送,这里的write函数中调用的sockfd是套接字的句柄:

    1. while (!feof(fq))
    2.     {
    3.         /*循环读取文件并发送*/
    4.         size_t readLen = fread(buffer, 1, sizeof(buffer), fq);
    5.         if (readLen != write(serverFd, buffer, readLen))
    6.         {
    7.             printf("write error.\n");
    8.             break;
    9.         }
    10. }
    5.2.2 服务器端

    服务器端由于涉及到多线程,因此需要包含多线程头文件。并且服务器端还涉及到缓冲区,本实例可以通过队列方法设计缓冲区,因此可以包含队列头文件。还有涉及到同步锁机制,因此还需要包含同步锁头文件,具体如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include

    服务器端首先也需要创建套接字,并等待客户端发起连接,服务器端的关键代码如下:

    1. int main(int argc, char **argv)
    2. {
    3.     int listenFd, clientFd;
    4.     struct sockaddr_in servaddr;
    5.     if ((listenFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    6.     {
    7.         /*创建套接字*/
    8.         printf("create socket error\n");
    9.         return -1;
    10.     }
    11.     memset(&servaddr, 0, sizeof(servaddr));                     //初始化结构体
    12. servaddr.sin_family = AF_INET;                              //设置地址族协议
    13. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);               //设置地址
    14.     servaddr.sin_port = htons(6666);                            //设置默认端口
    15.     if (bind(listenFd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    16.     {
    17.         /*绑定套接字地址和端口*/
    18.         printf("bind socket error\n");
    19.         return -1;
    20.     }
    21.     if (listen(listenFd, 10) < 0)
    22.     {
    23.         /*开启监听*/
    24.         printf("listen socket error\n");
    25.         return -1;
    26.     }
    27.     struct sockaddr_in client_addr;
    28.     socklen_t size = sizeof(client_addr);
    29.     if ((clientFd = accept(listenFd, (struct sockaddr *)&client_addr, &size)) < 0)
    30.     {
    31.         /*建立连接*/
    32.         printf("accept socket error\n");
    33.         return -1;
    34.     }
    35.     std::thread write_thread(writeThread);
    36. size_t readLen = 0;
    37.     while (true)
    38.     {
    39.         /*循环读取客户端消息*/
    40.         char buff[MAXBUFF] = {0};
    41.         readLen = read(clientFd, buff, MAXBUFF);
    42.         if (readLen <= 0)
    43.             break;
    44.         std::string data(buff, readLen);
    45.         g_mx.lock();                                        //上锁
    46.         g_dataQue.push(data);
    47.         g_mx.unlock();                                      //解锁
    48.     }
    49.   write_thread.join();
    50.     close(clientFd);
    51.     close(listenFd);
    52.     return 0;
    53. }

    注意,在上述函数中定义了写文件线程:

    std::thread write_thread(writeThread);

    并且在主线程中启动了写文件线程:

    write_thread.join();

    接收线程执行函数:

    1. std::queue g_dataQue;                   //全局队列
    2. std::mutex g_mx;                                     //互斥锁
    3. void writeThread()
    4. {
    5.     /*写线程*/
    6.     FILE *out_put = fopen("recv_data.mp4", "w+");
    7.     sleep(1);                                        //休眠一秒,确保队列中有数据
    8.     while (true)
    9.     {
    10.         /*从队列中读取数据并存储*/
    11.         if (g_dataQue.size() == 0)
    12.             break;
    13.         g_mx.lock();
    14.         std::string data = g_dataQue.front();
    15.         g_dataQue.pop();
    16.         g_mx.unlock();
    17.         fwrite((void *)data.data(), 1, data.size(), out_put);
    18.     }
    19.     fclose(out_put);
    20. }

    如上所示,同步锁用于进行缓冲区的读写同步。上述实例中通过std::mutex实现同步。

    1. g_lock.lock();         //上锁
    2. ​​​​​​​g_lock.unlock();       //解锁
  • 相关阅读:
    手机能做静态二维码吗?用手机做二维码的教程
    Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点
    vue使用swiper(轮播图)-真实项目使用
    决定放弃uniapp开发了,因为它实在是没有taro友好
    mvcc介绍
    五、伊森商城 前端基础-Vue 计算属性和侦听器 & 过滤器 & 组件化 & 生命周期钩子函数 p25
    rocketmq-console-1.0.0启动报错
    Lucas在与位运算有关的组合数中的应用
    ros学习笔记13——unknown package [sensor_msgs] on search path [{{‘ros_to_deepstream
    web开发技术
  • 原文地址:https://blog.csdn.net/m0_52537869/article/details/134534694