基于Linux的流媒体桌面客户端开发项目总结

基于Linux的流媒体桌面客户端开发

项目描述:本项目实现基于C/S模型的网络音频点播系统。该软件分为服务器和客户机两个部分,服务器以点播/多播的方式向局域网中所有的客户端发送数据,客户机可以根据自己的选择来决定要接收的数据。

主要工作:

  1. 采用socket编程来实现不同主机之间的通信
  2. 采用多线程的机制,增加处理频道节目单和音频并行服务数量
  3. 采用管道,实现客户端父子进程间的通信
  4. 使用状态机解析 HTTP 请求报文,支持解析 GET 和 POST 请求
  5. 采用令牌桶算法,实现读取MP3文件的流量控制
  6. 使用 Reactor高并发模型处理连接,实现可同时处理多个客户端连接
  7. 使用 epoll(ET或LT)技术实现高效的 IO 多路复用

个人收获:熟悉Linux平台的网络编程、对UDP以及TCP协议服务的过程有了更清晰的认识,对于进程间的通信、多线程的并发及流控算法有了⼀定的理解,增加项目经验(约2k行代码量)。

广播/点播/多播

概念:

D类IP地址-abcdef拓展:https://blog.csdn.net/kzadmxz/article/details/73658168

广播点播概念:单播、多播(组播)和广播的区别 - 简书

应用:

如何在Linux c/c++ 进行多播(组播)编程_linux 组播-CSDN博客

UDP特别的:多点通信
多播服务器端
  1. socket()(socket()函数用法详解:创建套接字
  2. 初始化sockaddr_in类型的变量,包括协议族、地址、端口
  3. sockoption,设置
//tcp
//创建多播组,加入多播组,发送数据
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    const char* multicast_group = "239.0.0.1"; // 设置多播组的IP地址
    int multicast_port = 12345; // 设置多播组的端口号

    int sockfd;
    struct sockaddr_in addr;
    char message[] = "Hello, Multicast!";

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        std::cerr << "Error creating socket" << std::endl;
        return 1;
    }

    // 初始化多播组地址
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(multicast_group);
    addr.sin_port = htons(multicast_port);

    // 启用多播功能
    int enable = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &enable, sizeof(enable)) < 0) {
        std::cerr << "Error setting multicast loop" << std::endl;
    }

    // 加入多播组
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(multicast_group);
    mreq.imr_interface.s_addr = INADDR_ANY;
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
        std::cerr << "Error joining multicast group" << std::endl;
    }

    // 发送数据到多播组
    sendto(sockfd, message, sizeof(message), 0, (struct sockaddr*)&addr, sizeof(addr));

    close(sockfd);

    return 0;
}
多播客户端:
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    const char* multicast_group = "239.0.0.1"; // 多播组的IP地址
    int multicast_port = 12345; // 多播组的端口号

    int sockfd;
    struct sockaddr_in addr;

    // 1. 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        std::cerr << "Error creating socket" << std::endl;
        return 1;
    }

    // 2. 设置多播组地址
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(multicast_group);
    addr.sin_port = htons(multicast_port);

    // 3. 加入多播组
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(multicast_group);
    mreq.imr_interface.s_addr = INADDR_ANY;
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
        std::cerr << "Error joining multicast group" << std::endl;
        close(sockfd);
        return 1;
    }

    // 4. 接收多播数据
    char buffer[1024];
    int bytes_received;
    while (true) {
        bytes_received = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
        if (bytes_received < 0) {
            std::cerr << "Error receiving data" << std::endl;
            break;
        }
        // 处理接收到的数据
    }

    // 5. 关闭套接字
    close(sockfd);

    return 0;
}


 

拓展知识点:

网络中的数据分割问题_tcp消息分段_ninekwll0791的博客-CSDN博客

计算机网络模型(TCP五层模型) - 知乎

有用的网站:

C 库函数 – setvbuf() | 菜鸟教程

c++网络编程目录总结

https://www.cnblogs.com/DOMLX/p/9663167.html

采用socket编程来实现不同主机之间的通信

概念:

UDP如何实现可靠传输

应用:

    讨论:跨主机的传输要注意的问题

    1. 字节序问题:

        大端:低地址处放高字节

        小端:低地址出放低字节

        主机字节序:host

        网络字节序:network

        解决:_ to _ _: htons, htonl, ntohs, ntohl (s 两字节, l四字节)

    2. 对齐

        struct {

            int i;

            float f;

            char ch;

        };

        解决:不对齐

    3. 类型长度问题:

        int

        char

        解决: int32_t, unit32_t, int64_t, int8_t, uint8_t

   4.用到的函数

        socket();

        bind();

        sockaddr -> man 7 ip -> 找Address format

        sendto();

        rcvfrom();

        inet_pton(); 点分式转为二进制格式

        inet_ntop();

        setsockopt();

        getsockopt();

        多点通讯:广播(全网广播,子网广播), 多波/组播

        224.0.0.1 所有支持多播的节点默认都存在这个组中,并且无法离开

     报式套接字代码过程:

被动端(先运行)

  1. 取得socket: 使用 socket() 系统调用创建一个套接字,例如 socket(AF_INET, SOCK_DGRAM, 0); 创建一个IPv4的报式套接字。

  2. 给socket取得地址: 使用 bind() 绑定套接字到本地地址和端口。这是可选的,但通常用于指定接收数据的本地端口和地址。

  3. 收/发消息: 使用 recvfrom() 接收来自客户端的数据报,使用 sendto() 发送数据报给客户端。在报式套接字通信中,每个数据报都是独立的,没有持续的连接。

  4. 关闭socket: 使用 close() 函数关闭套接字连接。

主动端

  1. 取得socket: 使用 socket() 创建一个套接字,例如 socket(AF_INET, SOCK_DGRAM, 0); 创建一个IPv4的报式套接字。

  2. 给socket取得地址(可省略): 如果需要,使用 bind() 将套接字绑定到本地地址。这通常是在主动端不指定本地端口时由系统自动选择的,所以通常可以省略。

  3. 收/发消息: 使用 sendto() 发送数据报给被动端,使用 recvfrom() 接收被动端的响应。在报式套接字通信中,每个数据报都是独立的,没有持续的连接。

  4. 关闭socket: 使用 close() 函数关闭套接字连接。

    流式套接字:

C端(主动端)

  1. 取得socket: 使用 socket() 系统调用创建一个套接字。例如:socket(AF_INET, SOCK_STREAM, 0); 创建一个IPv4的TCP套接字。

  2. 给socket取得地址(可省略): 如果需要,使用 bind() 将套接字绑定到本地地址。这通常是在客户端不指定本地端口号时由系统自动选择的,所以通常可以省略。

  3. 发送连接: 使用 connect() 函数连接到服务器。将服务器的地址信息传递给 connect() 函数,这个地址包括服务器的IP地址和端口号。

  4. 收/发消息: 使用 send() 发送数据到服务器,使用 recv() 接收来自服务器的响应。

  5. 关闭socket: 使用 close() 函数关闭套接字连接。

S端(被动端,先运行)

  1. 取得socket: 使用 socket() 创建一个套接字,同样可以是 socket(AF_INET, SOCK_STREAM, 0); 来创建IPv4的TCP套接字。

  2. 给socket取得地址: 使用 bind() 绑定套接字到服务器的IP地址和端口号。

  3. 将socket设置为监听模式: 使用 listen() 函数将套接字设置为监听模式,以便接受客户端的连接请求。

  4. 接受连接: 使用 accept() 函数等待客户端的连接请求。当有客户端连接时,accept() 会返回一个新的套接字,用于与客户端进行通信。

  5. 收/发消息: 使用新的套接字进行数据的收发,同样使用 send() 发送数据到客户端,使用 recv() 接收客户端的数据。

  6. 关闭socket: 使用 close() 函数关闭服务器的套接字连接。

使用的模型框架

⾼性能⽹络模式:Reactor Proactor

1、reactor和proactor区别

  1. 主线程的工作内容、下发给工作线程的工作内容不一样
⽆论是 Reactor ,还是 Proactor ,都是⼀种基于「事件分发」的⽹络编程模式,区别在于 Reactor
模式是基于「待完成」的 I/O 事件,然后下发给工作线程处理io事件以及业务逻辑,⽽ Proactor 模式则是基于「已完成」的 I/O 事件,然后下发给工作线程处理业务逻辑。
       2.适用场景不一样
2、reactor

3、proactor
阻塞 I/O ,当⽤户程序执⾏ read ,线程会被阻塞,⼀直等到内核数据准备好,并把数据从内核
缓冲区拷⻉到应⽤程序的缓冲区中,当拷⻉过程完成, read 才会返回。
注意, 阻塞等待的是「内核数据准备好」和「数据从内核态拷⻉到⽤户态」这两个过程

采用多线程的机制,增加处理频道节目单和音频并行服务数量

【Linux】——多线程_linux 多线程_hrimkn的博客-CSDN博客

1. 线程的概念

    一个正在运行的函数

    posix 线程是一套标准,而不是实现

    openmp线程

    线程标识:pthread_t

    pthread_equal();——比较线程ID

    pthread_self();——获取调用线程的ID

    最好不要和信号大规模混用

【精选】函数简介篇——线程相关函数_线程函数-CSDN博客

2. 线程的创建

    pthread_create();

    **线程的调度取决于调度器策略**

   

    线程的终止

        3种方式:

            + 线程从启动例程返回,返回值就是线程的id码

            + 线程可以被同一进程中的其他线程取消

            + 线程叫调用pthread_exit()函数

        pthread_join(); --> 相当于wait();收尸函数--等待由thread指定的线程结束。如果该线程已经终止,则pthread_join()立即返回。

    栈的清理

        pthread_cleanup_push();

        pthread_cleanup_pop();

        这两句话必须一对出现,要不然编译错误

    线程的取消选项

        线程取消:pthread_cancel();

        取消有两种状态:允许和不允许

        允许取消又分为:异步cancel,推迟cancel(默认)->推迟至cancel点再响应

        cancel点:POSIX定义测cancel点,都是可能引发阻塞的系统调用

        pthread_setcancelstate():设置是否允许取消

        pthread_setcanceltype():设置取消方式---可以设置延迟取消

        pthread_testcancel():本函数什么都不做,就是一个取消点

    线程分离

        pthread_detach(); 已经分离的不能再用pthread_join()收尸,各安天命

不能使用 pthread_join() 等函数等待分离状态的线程的终止,分离状态的线程仍然与父进程共享相同的进程空间,包括内存地址空间、文件描述符、全局变量等。线程的分离状态主要影响线程的退出处理方式,以及线程是否可以被其他线程等待。

    同一进程中的代码段时共用的,栈是独立的----就是说进程空间能创建多少个栈----查看系统的限制命令ulimit -a

    64位环境下c程序的虚拟空间为128T----

3. 线程同步

    互斥量:

类型:pthread_mutex_t

            pthread_mutex_init();

            pthread_mutex_destroy();

            pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;----静态初始化

            pthread_mutex_lock();

            pthread_mutex_trylock();---以非阻塞方式锁定互斥锁

            pthread_mutex_unlock();

           

            pthread_once(); 用来实现某个模块的单次初始化

    条件变量:

            pthread_cond_t

            pthread_cond_init();

            pthread_cond_destroy();

            pthread_cond_broadcast();

            pthread_cond_signal();

           

            pthread_cond_wait();

            pthread_cond_timewait();

    信号量:

    读写锁:读锁 -> 共享锁

            写锁 -> 互斥锁

            注意:临界区的跳转,需要注意解锁,否则容易造成死锁

 4. 线程属性

    pthread_attr_init();

    pthread_attr_destroy();

    pthread_attr_setstacksize();

    见man pthread_attr_init的see also

    线程同步的属性
        互斥量属性:

            pthread_mutexattr_init();

            pthread_mutexattr_destroy();

            pthread_mutexattr_getpshared(); getp这个p指的是是否跨进程使用

            phtread_mutexattr_setpshared();

            clone();

            pthread_mutexattr_gettype();

            pthread_mutexattr_settype();

        条件变量属性:

            pthread_condattr_init();

            pthread_condattr_destroy();

        读写锁属性:

        ..............

    线程池----->0 可抢

                =0 可放

                <0 exit

 5. 重入

    多线程中的IO

    线程和信号的关系

        pthread_pending();

        sigwait();

        pthread_kill();

    线程与fork

#### 6. openmp(www.OpenMP.org)

io复用--使用 epoll(ET或LT)技术实现高效的 IO 多路复用

进程间通信--采用管道,实现客户端父子进程间的通信

管道:

| 表示的管道称为 匿名管道 ,⽤完了就销毁。
命名管道 ,也被叫做 FIFO ,因为数据是先进先出的传输⽅式
管道这种通信⽅式效率低,不适合进程间频繁地交换数据 。当然,它的好处,⾃然就是简
单,同时也我们很容易得知管道⾥的数据已经被另⼀个进程读取了。

匿名管道

匿名管道的创建,需要通过下面这个系统调用:

int pipe(int fd[2])

这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符 fd[0],另一个是管道的写入端描述符 fd[1]。注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。

http的web

流量控制

数据库

哨兵

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值