请问老师,运行结果期望不符时,应当如何调试

请问老师,运行结果期望不符时,应当如何调试

调试了两个晚上还是找不到哪里有问题,请老师指点一下。
main.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include "global.h"
#include "camera.h"
#include "server.h"

#ifndef DEBUG
// #define DEBUG
#endif

global_t global;

cam_info_t cam_info;

void signal_handler(int signum)
{ 
    // 关闭设备

    return;
}

void init_global(global_t *pglobal)
{
    pglobal->capture = true;
    pglobal->length = 0;

    if ((global.start = malloc(PICTURE_SIZE)) == NULL)
    {
        perror("Fail to malloc");
        exit(EXIT_FAILURE);
    }

    if (pthread_mutex_init(&(pglobal->update_lock), NULL) < 0)
    {
        perror("Fail to pthread_mutex_init");
        exit(EXIT_FAILURE);
    }

    if (pthread_cond_init(&(pglobal->update_cond), NULL) < 0)
    {
        perror("Fail to pthread_cond_init");
        exit(EXIT_FAILURE);
    }

    return;
}

int main()
{
    // 忽略SIGPIPE信号.
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
    {
        perror("Fail to signal");
        exit(EXIT_FAILURE);
    }

    /*
        // 捕捉ctrl+c信号.
        if (signal(SIGINT, signal_handler) == SIG_ERR)
        {
            perror("Fail to signal");
            exit(EXIT_FAILURE);
        }
    */

    // 初始化全局变量global.
    init_global(&global);

    // 初始化相机设备
    int cam_fd = init_camera("/dev/video0");
    if (0 > cam_fd)
    {
        perror("init_camera failure!");
        return -1;
    }
    // 初始化映射内存
    if (0 > init_mmap(cam_fd))
    {
        perror("init_mmap failure!");
        return -1;
    }
    // 创建子线程启动相机设备开始捕获图像.
    pthread_t cam_tid;
    int errno = 0;
    errno = pthread_create(&cam_tid, NULL, start_capturing, (void *)&cam_fd);
    if (errno != 0)
    {
        fprintf(stderr, "[ERROR] pthread_create : < %s >\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    pthread_detach(cam_tid);

    int s_fd = init_tcp("127.0.0.1", 8080, 5);

    cam_info.cam_fd = cam_fd;
    cam_info.s_fd = s_fd;

    /*并发接收客户端发来的请求*/
    while (1)
    {
        struct sockaddr_in cli_addr;
        socklen_t len = sizeof(cli_addr);
        int c_fd = accept(s_fd, (struct sockaddr *)&cli_addr, &len);
        if (c_fd == -1)
        {
            perror("[ERROR] Failed to accept.");
            return -1;
        }

#ifdef DEBUG
        printf("ip : %s port : %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
#endif

        pthread_t tid;
        int ret = pthread_create(&tid, NULL, client_thread, (void *)c_fd);
        if (ret != 0)
        {
            perror("Fail to pthread_create");
            continue;
        }

        pthread_detach(tid);

#ifdef DEBUG
        printf("loop again\n");
#endif
    }

    stop_capture(cam_fd);

    uninit_camera(cam_fd);

    close(s_fd);

    return 0;
}

camare.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>

#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <jpeglib.h>

#include "camera.h"
#include "global.h"

#ifndef DEBUG
// #define DEBUG
#endif

static struct camera_t *camera_data; // 映射缓冲区内存空间
static int n_buffer = 0;             // 映射的内存缓冲区个数

int init_camera(const char *dev_path)
{
    if (NULL == dev_path)
    {
        return -1;
    }

    // 打开摄像头设备
    int fd = open(dev_path, O_RDWR | O_NONBLOCK); // 读写非阻塞.
    if (0 > fd)                                   // fd:打开设备返回的文件句柄.
    {
        perror("open dev failure!");
        return -1;
    }

    // 检查摄像头能力集(capabilities), 拍照需要的能力是 V4L2_CAP_VIDEO_CAPTURE;
    struct v4l2_capability cap;               // 存储设备信息的结构体.(cap)
    if (0 > ioctl(fd, VIDIOC_QUERYCAP, &cap)) // 查询设备能力集(VIDIOC_QUERYCAP)
    {
        perror("VIDIOC_QUERYCAP failure!");
        return -1;
    }

    if (!(V4L2_CAP_VIDEO_CAPTURE & cap.capabilities)) // 检查拍照功能位(field)
    {
        perror("V4L2_CAP_VIDEO_CAPTURE failure!");
        return -1;
    }

    // 检查相机在某种能力下的输出格式, 并选择一个适合的格式;
    struct v4l2_fmtdesc fmt;                      // 存储帧描述信息的结构体.(fmt)
    fmt.index = 0;                                // 从第一个输出格式枚举, 输出格式不止一个
    fmt.type = V4L2_CAP_VIDEO_CAPTURE;            // 设置枚举类型
    while (0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmt)) // 枚举设备拍照功能(fmt)的输出格式(VIDIOC_ENUM_FMT)
    {
        printf("pixelformat: %c%c%c%c, description: %s. \n",
               fmt.pixelformat & 0xff, (fmt.pixelformat >> 8) & 0xff,
               (fmt.pixelformat >> 16) & 0xff, (fmt.pixelformat >> 24) & 0xff,
               fmt.description);

        ++fmt.index;
    }

    // 配置相机采集数据的格式;
    struct v4l2_format format;                 // 帧格式相关参数.
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置设备采集模式类型(pix)
    format.fmt.pix.width = IMG_WIDTH;
    format.fmt.pix.height = IMG_HEIGHT;
	format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    format.fmt.pix.field = V4L2_FIELD_INTERLACED; // 存储格式
    ioctl(fd, VIDIOC_S_FMT, &format);

    return fd;
}

int init_mmap(int fd)
{
    if (0 > fd)
    {
        return -1;
    }

    // 向驱动申请内存缓冲区(内核空间);
    struct v4l2_requestbuffers reqbuf;
    reqbuf.count = 4;                           // 申请4个内存缓冲区
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 申请拍照能力
    reqbuf.memory = V4L2_MEMORY_MMAP;           // 申请内存映射
    if (0 > ioctl(fd, VIDIOC_REQBUFS, &reqbuf)) // 申请内存映射缓冲区
    {
        perror("VIDIOC_REQBUFS failure!");
        return -1;
    }

    // 将内核缓冲区映射到用户空间(先查询后映射);
    camera_data = calloc(reqbuf.count, sizeof(struct camera_t)); // 创建缓冲区内存空间

    for (int i = 0; i < reqbuf.count; i++)
    {
        struct v4l2_buffer buf;
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if (0 > ioctl(fd, VIDIOC_QUERYBUF, &buf)) // 查询申请到缓冲区的信息.
        {
            perror("VIDIOC_QUERYBUF failure!");
            return -1;
        }
        camera_data[i].length = buf.length;
        camera_data[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); // 映射内存
        /**
         * void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
         *
         * @param addr:指定映射的起始地址,通常设为NULL,由内核来分配;
         * @param length:代表将文件中映射到内存的部分的长度;
         * @param prot:映射区域的保护方式。可以为以下几种方式的组合:
         *      PROT_EXEC 映射区域可被执行;
         *      PROT_READ 映射区域可被读取;
         *      PROT_WRITE 映射区域可被写入;
         *      PROT_NONE 映射区域不能存取;
         * @param flags:映射区的特性标志位,常用的两个选项是:
         *      MAP_SHARD:写入映射区的数据会复制回文件,且与其他进程共享;
         *      MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制,对此区域的修改不会写原文件;
         * @param fd:要映射到内存中的文件描述符,由函数open()打开文件时返回的值。
         * @param offset:文件映射的偏移量,offset必须是分页大小的整数倍,设置为0代表从文件起始对应。
         *
         * @return [void *]: 实际分配的内存的起始地址。
         */
    }

    // 将申请的内核缓冲区放入输入队列中;
    for (int i = 0; i < reqbuf.count; i++)
    {
        struct v4l2_buffer buf;
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if (0 > ioctl(fd, VIDIOC_QBUF, &buf)) // 内核缓冲区入队.
        {
            perror("VIDIOC_QBUF failure!");
            return -1;
        }
    }

    n_buffer = reqbuf.count;

    return 0;
}

void *start_capturing(void *arg)
{
    int cam_fd = (int)arg;

    // 启动相机采集数据;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (0 > ioctl(cam_fd, VIDIOC_STREAMON, &type))
    {
        perror("VIDIOC_STREAMON failure!");
        exit(EXIT_FAILURE);
    }

    // 监测fd的读事件,并在可读时读取数据;
    fd_set fds;
    struct timeval tv = {
        .tv_sec = 5,
        .tv_usec = 0,
    };

    while (global.capture)
    {
        FD_ZERO(&fds);
        FD_SET(cam_fd, &fds);

        int r = select(cam_fd + 1, &fds, NULL, NULL, &tv);
        /**
         * int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const  truct timeval* timeout);
         *
         * @param maxfd:监视对象文件描述符数量(范围);
         * @param readset:记录所有可能存在待读数据的文件描述符;
         * @param writeset: 记录所有可能无阻塞写入数据的文件描述符;
         * @param exceptset:记录所有可能发生异常的文件描述符;
         * @param timeout:超时时间;
         *
         * @return [int]: 错误返回-1,超时返回0。当关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数

        */
        if (0 == r)
        {
            // fprintf(stderr, "select Timeout\n");
            continue;
        }
        else
        {
            if (FD_ISSET(cam_fd, &fds)) // 监听cam_fd是否置位可读
            {
                if (0 == read_frame(cam_fd))
                {
                    continue;
                }
                else if (-1 == read_frame(cam_fd))
                {
                    perror("read_frame() failure!");
                    pthread_exit(NULL);
                }
            }
        }

        pthread_exit(NULL);
    }

    return 0;
}

int stop_capture(int fd)
{
    if (0 > fd)
    {
        return -1;
    }

    // 处理相机数据结束,关闭相机数据流;
    global.capture = false;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (0 > ioctl(fd, VIDIOC_STREAMOFF, &type))
    {
        perror("VIDIOC_STREAMOFF failure!");
        return -1;
    }

    return 0;
}

void uninit_camera(int fd)
{
    if (0 > fd)
    {
        return;
    }

    unsigned int i;

    for (i = 0; i < n_buffer; i++)
    {
        // 解除映射
        if (-1 == munmap(camera_data[i].start, camera_data[i].length))
        {
            printf("munmap() failed. camera_data[index] is %d. \n", i);
            exit(EXIT_FAILURE);
        }
    }

    free(camera_data->start); // 释放映射的缓冲区内存空间
    free(camera_data);        // 释放映射的缓冲区内存空间
    close(fd);                // 关闭设备

    return;
}

int read_frame(int fd)
{
    if (0 > fd)
    {
        return -1;
    }

    struct v4l2_buffer buf;
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    if (0 > ioctl(fd, VIDIOC_DQBUF, &buf)) // 从队列去取缓冲区.
    {
        perror("VIDIOC_DQBUF failure!");
        return -1;
    } // ioctl()在buf中输出已就绪的内核缓冲区,告知哪个index缓冲区可读

#ifdef DEBUG
    /**
     * 简单地讲,断言就是对某种假设条件进行检查.
     * 在 C 语言中,断言被定义为宏的形式(assert(expression)),而不是函数.其原型定义在 <assert.h> 文件中。
     * 其中,assert 将通过检查表达式 expression 的值来决定是否需要终止执行程序。
     * 如果表达式 expression 的值为假(即为 0),那么它将首先向标准错误流 stderr 打印一条出错信息,然后再通过调用 abort 函数终止程序运行;否则,assert 无任何作用。
     * */
    assert(buf.index < n_buffer);
#endif

    pthread_mutex_lock(&global.update_lock); // 上锁

    // 读取到全局的buffer中去.
    int frame_len = camera_data[buf.index].length;
    void *frame_ptr = camera_data[buf.index].start;
    global.length = frame_len;

    // 采集的数据大于预定义的大小, 扩充内存.
    if (global.length > PICTURE_SIZE)
    {
        global.start = realloc(global.start, global.length);
        memcpy(global.start, frame_ptr, frame_len);
    }
    else
    {
        memcpy(global.start, frame_ptr, frame_len);
    }

    pthread_mutex_unlock(&global.update_lock); // 解锁

    // 唤醒等待的线程.
    pthread_cond_broadcast(&global.update_cond);

#ifdef DEBUG
    // fopen返回文件指针(FILE *), 一般用fopen打开普通文件,open打开设备文件
    FILE *yuv_fp = fopen("./picture.yuv", "w"); // 打开YUV文件
    if (NULL == yuv_fp)
    {
        perror("fopen() failure!");
        return -1;
    }
    /**
     * <fwrite异步写入数据>
     * 理论上,每次读写 size*count 个字节的数据。(以数据块的形式读写文件)
     * @param src[void *]: 从源数据复制
     * @param size
     * @param count
     * @param dst[FILE *]: 写入到目标文件
     */
    fwrite(frame_ptr, frame_len, 1, yuv_fp);
    fflush(yuv_fp); // 异步写入数据需要刷新缓冲区
    fclose(yuv_fp); // 关闭YUV文件

    yuyv_to_rgb(frame_ptr, "picture.rgb", IMG_WIDTH, IMG_HEIGHT);
    yuyv_to_jpeg(frame_ptr, "picture.jpg", IMG_WIDTH, IMG_HEIGHT, 90);
#endif

    return 0;
}

/**
 * <YUV 格式转 RGB>
 *
 * @param yuv : YUV 帧图像指针
 */
int yuyv_to_rgb(const unsigned char *yuv, const char *file_name, int width, int height)
{
    if (NULL == yuv || NULL == file_name)
    {
        return -1;
    }
    if (0 == width || 0 == height)
    {
        return -1;
    }

    FILE *fp = fopen(file_name, "w"); // 打开RGB文件
    char *lineBuf = malloc(width * 3);
    int y, u, v;
    int r, g, b;

    /**
     * YUYV_422 :
     * Y1 U1 Y2  V1 | Y3  U2 Y4  V2 |
     * Y5 U3 Y6  V3 | Y7  U4 Y8  V4 |
     * Y9 U5 Y10 V5 | Y11 U6 Y12 V6 |
     *
     */

    for (int row = 0; row < height; ++row)
    {
        char *ptr = lineBuf;

        for (int col = 0; col < width; col += 2)
        {
            y = yuv[0];
            u = yuv[1];
            v = yuv[3];
            r = y + 1.402 * (v - 128);
            g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
            b = y + 1.772 * (u - 128);
            ptr[0] = r > 255 ? 255 : (r < 0 ? 0 : r);
            ptr[1] = g > 255 ? 255 : (g < 0 ? 0 : g);
            ptr[2] = b > 255 ? 255 : (b < 0 ? 0 : b);

            y = yuv[2];
            u = yuv[1];
            v = yuv[3];
            r = y + 1.402 * (v - 128);
            g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
            b = y + 1.772 * (u - 128);
            ptr[3] = r > 255 ? 255 : (r < 0 ? 0 : r);
            ptr[4] = g > 255 ? 255 : (g < 0 ? 0 : g);
            ptr[5] = b > 255 ? 255 : (b < 0 ? 0 : b);

            ptr += 6; // 每个pix 色彩RGB占3个元素
            yuv += 4; // 2个pix共用一个y, 下一个y值在y+4的位置
        }

        fwrite(lineBuf, width * 3, 1, fp);
        fflush(fp);
    }

    free(lineBuf);
    fclose(fp); // 关闭RGB文件

    return 0;
}

/**
 * <jpeg的压缩算法>
 *
 * @param img: yuyv422格式图像原始数据
 * @param file_name: yuyv压缩为jpeg格式后的数据存放文件的文件名
 * @param width: 图像宽度
 * @param height: 图像高度
 * @param quality: 压缩质量(1-100)
 */
int yuyv_to_jpeg(const unsigned char *yuv, const char *file_name, int width, int height, int quality)
{
    if (NULL == yuv || NULL == file_name)
    {
        return -1;
    }
    if (0 == width || 0 == height)
    {
        return -1;
    }
    if (0 > quality || 100 < quality)
    {
        return -1;
    }

    FILE *fp = fopen(file_name, "w"); // 打开jpeg文件

    struct jpeg_compress_struct cinfo; // 定义一个压缩对象.
    struct jpeg_error_mgr jerr;        // 用于存放错误信息.
    JSAMPROW row_pointer[1];           // 一行位图.
    cinfo.err = jpeg_std_error(&jerr); // 错误信息输出绑定到压缩对象.
    jpeg_create_compress(&cinfo);      // 初始化压缩对象.
    jpeg_stdio_dest(&cinfo, fp);       // 将保存输出数据的文件描述符与压缩对象绑定.
    cinfo.image_width = width;
    cinfo.image_height = height;             // 图像的宽和高,单位为像素.
    cinfo.input_components = 3;              // 3表示彩色位图,如果是灰度图则为1.
    cinfo.in_color_space = JCS_RGB;          // JSC_RGB表示彩色图像.
    jpeg_set_defaults(&cinfo);               // 采用默认设置对图像进行压缩.
    jpeg_set_quality(&cinfo, quality, TRUE); // 设置图像压缩质量.
    jpeg_start_compress(&cinfo, TRUE);       // 开始压缩.

    // 申请buf空间,大小为yuyv数据转换为rgb格式后每一行的字节数.
    unsigned char *line_buf = calloc(width, sizeof(struct rgb_t)); // 开辟行缓冲内存.
    int r, g, b;
    int y, u, v; // 对每行yuv数据进行rgb转换.
    while (cinfo.next_scanline < height)
    { // 逐行进行图像压缩.
        struct rgb_t *px = (struct rgb_t *)line_buf;

        for (int col = 0; col < width; col += 2)
        {
            // 此处将获得的YUV数据转换为RGB数据后写入到行缓冲当中.
            y = yuv[0];
            u = yuv[1];
            v = yuv[3];
            r = y + 1.402 * (v - 128);
            g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
            b = y + 1.772 * (u - 128);
            px->r = r > 255 ? 255 : (r < 0 ? 0 : r);
            px->g = g > 255 ? 255 : (g < 0 ? 0 : g);
            px->b = b > 255 ? 255 : (b < 0 ? 0 : b);
            px += sizeof(struct rgb_t); // 偏移,记录新Px数据

            y = yuv[2];
            u = yuv[1];
            v = yuv[3];
            r = y + 1.402 * (v - 128);
            g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
            b = y + 1.772 * (u - 128);
            px->r = r > 255 ? 255 : (r < 0 ? 0 : r);
            px->g = g > 255 ? 255 : (g < 0 ? 0 : g);
            px->b = b > 255 ? 255 : (b < 0 ? 0 : b);
            px += sizeof(struct rgb_t); // 偏移,记录新Px数据
            yuv += 4;                   // 2个pix共用一个y, 下一个y值在y+4的位置
        }
        row_pointer[0] = line_buf;
        jpeg_write_scanlines(&cinfo, row_pointer, 1); // 将行数据写入压缩对象.
    }
    jpeg_finish_compress(&cinfo);  // 压缩完成.
    jpeg_destroy_compress(&cinfo); // 释放申请的资源.
    free(line_buf);

    fclose(fp); // 关闭jpeg文件
    return 0;
}

sever.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "global.h"
#include "server.h"

/*
 * Only the following fileypes are supported.
 *
 * Other filetypes are simply ignored!
 * This table is a 1:1 mapping of files extension to a certain mimetype.
 */
static const struct
{
    const char *dot_extension;
    const char *mimetype;
} mimetypes[] = {
    {".html", "text/html"},
    {".htm", "text/html"},
    {".css", "text/css"},
    {".js", "text/javascript"},
    {".txt", "text/plain"},
    {".jpg", "image/jpeg"},
    {".jpeg", "image/jpeg"},
    {".png", "image/png"},
    {".gif", "image/gif"},
    {".ico", "image/x-icon"},
    {".swf", "application/x-shockwave-flash"},
    {".cab", "application/x-shockwave-flash"},
    {".jar", "application/java-archive"}};

int init_tcp(const char *ip_str, unsigned short port, int backlog)
{
    int s_fd, ret;
    struct sockaddr_in svr_addr;

    // 1.创建套接字
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (s_fd == -1)
    {
        perror("[ERROR] Failed to socket.");
        exit(EXIT_FAILURE);
    }

    /**
     * setsockopt - <端口复用允许在一个应用程序可以把 n个套接字绑在一个端口上而不出错>
     *
     * 端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。
     * 这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。
     * 如果没有设定端口复用,绑定会失败。
     *
     * @param sockfd:用于监听的文件描述符
     * @param level:设置端口复用需要使用 SOL_SOCKET 宏
     * @param optname:要设置什么属性(下边的两个宏都可以设置端口复用)
     *      SO_REUSEADDR
     *      SO_REUSEPORT
     * @param optval:设置是去除端口复用属性还是设置端口复用属性,实际应该使用 int 型变量
     *      0:不设置
     *      1:设置
     * @param optlen:optval 指针指向的内存大小 sizeof (int)
     */
    int optval = 1;
    if (0 > setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) // 端口复用
    {
        perror("Fail to setsockopt");
        exit(EXIT_FAILURE);
    }

    printf("ip: %#X\n", inet_aton(ip_str, NULL));
    bzero(&svr_addr, sizeof(struct sockaddr_in));
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(atoi(port));
    svr_addr.sin_addr.s_addr = inet_addr(ip_str);

    // 2.绑定ip地址与端口号
    ret = bind(s_fd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr));
    if (ret == -1)
    {
        perror("[ERROR] Failed to bind.");
        exit(EXIT_FAILURE);
    }

    ret = listen(s_fd, backlog);
    if (ret == -1)
    {
        perror("[ERROR] Failed to listen.");
        exit(EXIT_FAILURE);
    }

    printf("Listen s_fd %d port %d\n", s_fd, port);
    printf("server ip is %s\n", inet_ntoa(svr_addr.sin_addr));

    return s_fd;
}

void *client_thread(void *arg)
{
    int c_fd = (int)arg;
    char buf[1024] = {0};

    if (0 > recv(c_fd, buf, sizeof(buf) - 1, 0))
    {
        perror("recv() failure");
        goto end;
    }

    // 初始化请求头
    request_t request = {
        .type = 0,
        .parm = NULL, // 这里需要开辟空间存放http数据.
    };

    // 解析请求头
    if (0 > analyse_http_request(buf, &request))
    {
        perror("analyse_http_request() failure");
        goto end;
    }
    switch (request.type)
    {
    case A_FILE:
        send_file(c_fd, request.parm);
        break;
    case A_SNAPSHOT:
        send_snapshot(c_fd);
        break;
    case A_STREAM:
        send_stream(c_fd);
        break;
    }

end:
    close(c_fd); // 短连接,服务器主动断开

    if (NULL != request.parm)
    {
        free(request.parm);
    }

    return NULL;
}
/**Http应答格式
 * 响应与请求一样分成三个部分:响应行、响应头、响应体。
 * 1)响应行: HTTP/1.0 200 OK
 * 2)响应头:部分头属性解释
    Location:这个头配合302状态码,用于告诉客户端找谁(跳转)
    Server:服务器通过这个头,告诉浏览器服务器的类型
    Content-Encoding:告诉浏览器,服务器的数据压缩格式
    Content-Length:告诉浏览器,回送数据的长度
    Content-Type:告诉浏览器,回送数据的类型
    Last-Modified:告诉浏览器当前资源缓存时间
    Refresh:告诉浏览器,隔多长时间刷新
    Content-Disposition:告诉浏览器以下载的方式打开数据。例如: context.Response.AddHeader(“Content-Disposition”,“attachment:filename=aa.jpg”);context.Response.WriteFile(“aa.jpg”);
    Transfer-Encoding:告诉浏览器,传送数据的编码格式
    ETag:缓存相关的头(可以做到实时更新)
    Expries:告诉浏览器回送的资源缓存多长时间。如果是-1或者0,表示不缓存
    Cache-Control:控制浏览器不要缓存数据
    no-cache Pragma:控制浏览器不要缓存数据
    no-cache Connection:响应完成后,是否断开连接
    close/Keep-Alive Date:告诉浏览器,服务器响应时间

    *** 空行 ***

 * 3)响应体:
    内容 - 包含浏览器能够解析的静态内容,例如:html、纯文本、图片、视频等等信息;
*/
int analyse_http_request(const char *buf, request_t *req)
{
    // strstr()返回的是匹配成功的字符串以及后面的字符串
    if (NULL != strstr(buf, "GET /?action=snapshot"))
    {
        req->type = A_SNAPSHOT;
        return 0;
    }
    else if (NULL != strstr(buf, "GET /?action=stream"))
    {
        req->type = A_STREAM;
        return 0;
    }
    else
    {
        char *url = strstr(buf, "GET /");
        if (NULL == url)
        {
            printf("Http request error!\n");
            // send error to client
            return -1;
        }
        url += strlen("GET /"); // 光标偏移到"GET /"后边
        char arr[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-1234567890";
        // strspn()检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标。
        int len = MIN(MAX(strspn(url, arr), 0), 100); // 本行的意思是, 返回url请求头"GET /"后面的字符串 到第一个空格的位置
        req->parm = (char *)malloc(len + 1);
        memset(req->parm, 0, len + 1);
        memcpy(req->parm, url, len); // 获取到请求参数(文件名)
        printf("req->parm : %s.\n", req->parm);
        req->type = A_FILE;
        return 0;
    }
}

void send_file(int sockfd, char *pathfile)
{
    int n, fd;
    char buf[1024] = {0};
    char *extension, *mimetype = NULL;

    // 第一次请求头是"GET /", 返回网页首页即可
    if (pathfile == NULL || strlen(pathfile) == 0)
        pathfile = "index.html";

    /* find file-extension */
    if (NULL == (extension = strstr(pathfile, "."))) // 匹配文件拓展名" .xxx"
    {
        //    send_error(fd, 400, "No file extension found");
        return;
    }

    /* determine mime-type */
    for (int i = 0; i < sizeof(mimetypes) / sizeof(mimetypes[0]); ++i) // 遍历存储 文件拓展名的数组(mimetypes)
    {
        if (0 == strcmp(mimetypes[i].dot_extension, extension))
        {
            mimetype = (char *)mimetypes[i].mimetype;
            break;
        }
    }

    if (NULL == mimetype) // 文件类型查不到则退出当前函数
    {
        return;
    }

    // 拼接路径: "www/pathname", 打开文件
    sprintf(buf, "%s/%s", WEB_DIR, pathfile); // 发送格式化输出到 str 所指向的字符串。
    if ((fd = open(buf, O_RDONLY)) < 0)
    {
        fprintf(stderr, "Fail to open %s : %s.\n", buf, strerror(errno));
        // send error to webbrowser
        return;
    }

    // 添加http头.
    memset(buf, 0, sizeof(buf));
    sprintf(buf, "HTTP/1.0 200 OK\r\n"
                 "Content-type: %s\r\n" STD_HEADER "\r\n",
            mimetype); // 注意这里会多出个空行. STD_HEADER协议头中有换行,这里是两个换行

    // 发送http头信息和网页文件数据.
    n = strlen(buf);

    // 发送http头信息和网页文件数据.
    do
    {
        if (0 > send(sockfd, buf, n, 0))
        {
            perror("send_file() Fail to send file contain");
        }
    } while (n = read(fd, buf, sizeof(buf))); // 不停读取buf数据发送

    return;
}

void send_snapshot(int sockfd)
{
    int length;
    char *frame; // 备份数据,防止解锁后, send发送时,帧数据刷新导致数据发送错误
    char buf[BUFFER_SIZE];

    printf("send_snapshot(%d)\n", sockfd);

    // 上锁前应当先判断帧数据是否改变再上传资源

    pthread_mutex_lock(&global.update_lock); // 上锁

    pthread_cond_wait(&global.update_cond, &global.update_lock); // 等待唤醒

    // 获得视频数据
    length = global.length;
    frame = (char *)malloc(global.length);
    memcpy(frame, global.start, global.length);

    pthread_mutex_unlock(&global.update_lock);

    // 添加http头
    memset(buf, 0, sizeof(buf));
    sprintf(buf, "HTTP/1.0 200 OK\r\n"
                 "Content-type: image/jpeg\r\n" STD_HEADER
                 "\r\n");

    // 发送http头
    if (0 > send(sockfd, buf, strlen(buf), 0))
    {
        printf("send_snapshot() send http head failure\n");
        free(frame);
        return;
    }

    // 发送视频数据
    if (0 > send(sockfd, frame, length, 0))
    {
        printf("send_snapshot() send frame failure\n");
    }

    free(frame);
    return;
}

void send_stream(int sockfd)
{
    int length;
    char buf[BUFFER_SIZE];

    printf("send_stream(%d)\n", sockfd);

    sprintf(buf, "HTTP/1.1 200 OK\r\n" STD_HEADER
                 "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n"
                 "\r\n"
                 "--" BOUNDARY "\r\n");

    if (send(sockfd, buf, strlen(buf), 0) < 0)
    {
        perror("send_stream() fail to send http head");
        return;
    }

    char *frame = NULL;
    while (global.capture) // how to stop?
    {
        pthread_mutex_lock(&global.update_lock);
        pthread_cond_wait(&global.update_cond, &global.update_lock);

        length = global.length;
        frame = (char *)malloc(global.length);
        memcpy(frame, global.start, global.length);

        pthread_mutex_unlock(&global.update_lock);

        /* print the individual mimetype and the length
         * sending the content-length fixes random stream disruption observed
         * with firefox
         */
        sprintf(buf, "Content-Type: image/jpeg\r\n"
                     "Content-Length: %d\r\n"
                     "\r\n",
                length);

        if (0 > send(sockfd, buf, strlen(buf), 0))
        {
            perror("send_stream() Fail to send http header");
            break;
        }

        if (0 > send(sockfd, frame, length, 0))
        {
            perror("send_stream() Fail to send camera frame");
            break;
        }

        sprintf(buf, "\r\n--" BOUNDARY "\r\n"); // 发送分割 协议头
        if (0 > send(sockfd, buf, strlen(buf), 0))
        {
            break;
        }
        free(frame);
        usleep(100);
    }

    free(frame);
    return;
}

正在回答 回答被采纳积分+1

登陆购买课程后可参与讨论,去登陆

1回答
山行老师 2023-10-07 09:26:08
1.调试代码是一项很重要的能力,最基本的方式就是跟着代码流程加打印,确保代码执行到想要的位置;
2.也可以跟完整代码对比,看是不是漏了什么东西;
3.实在不能解决可以在学习群里把代码发出来,老师会下载下来调试;
4.调试效率最高的方式就是根据现象分析原因,比方说网页看不到视频(BUG),那就反推视频是怎么给网页的,在哪个函数哪行代码,有什么依赖,有什么先决条件.……
  • 提问者 慕小白0101 #1

    山行老师,我把完整版的代码重新编译了一次,老师您给的代码有问题。启动程序后,访问127.0.0.1:8080,无法读取拍照,视频流的图片。image那个框是空白的。。

    2023-10-07 14:54:07
  • 提问者 慕小白0101 #2

    https://img1.sycdn.imooc.com//climg/6521016a0924632513670957.jpg


    把原本在压缩包的mjpg_streamer删掉,重新make,新生成的mjpg_streamer是无法读取摄像头的

    2023-10-07 14:58:51
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
请稍等 ...
意见反馈 帮助中心 APP下载
官方微信

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师