当前位置: 首页 > backend >正文

嵌入式学习day38

http天气项目总结:

http用户层协议

从网上获得了天气数据

客户端程序:
socket

        connect:组织了http请求报文并发送

        回复响应报文 + 数据

        对数据做解析

协议:
每一次的通信的规则

connection:keep-alive //长连接

特点:

  1. tcp通信建立连接

connection:close //短连接

特点:

  1. 每次通信,都需要重新建立连接

半包问题

做处理:

  1. connect server
  2. request_http_future
  3. recv http response
  4. parse_data
  5. close

细节:
解析数据时 --- 段错误

        gcc main.c -g

        gdb ./a.out

        r //运行

        如果函数返回值是指针类型,要做错误判断

        if (p == NULL)


并发服务器

同时处理多个客户端

多进程实现并发:

#include "head.h"void do_child(int signo)
{wait(NULL);
}
int main(int argc, char const *argv[])
{//step1 socket int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");int on = 1;setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int));//step2 bind if (bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}//step3 listenif (listen(fd,5) < 0){perror("listen fail");return -1;}struct sockaddr_in cliaddr;bzero(&cliaddr,0);socklen_t len = sizeof(cliaddr);//step4 acceptsignal(SIGCHLD,do_child);while (1){int connfd = accept(fd,(struct sockaddr *)&cliaddr,&len);if (connfd < 0){perror("accept fail");return -1;}printf("---client connect---\n");printf("client ip:%s\n",inet_ntoa(cliaddr.sin_addr));printf("port: %d\n",ntohs(cliaddr.sin_port));if (fork() == 0){   close(fd);char buf[1024];while(1){recv(connfd,buf,sizeof(buf),0);printf("buf = %s\n",buf );if (strncmp(buf,"quit",4) == 0){break;}}close(connfd);}close(connfd);}close(fd);return 0;
}

多线程实现并发:

#include "head.h"void do_child (int signo)
{wait(NULL); 
}void *do_client(void *arg)
{int connfd = *(int *)arg;char buf[1024];while(1){recv(connfd,buf,sizeof(buf),0);printf("buf = %s\n",buf );if (strncmp(buf,"quit",4) == 0){close(connfd);//pthread_exit(NULL);return NULL;}}}int main(int argc, char const *argv[])
{//step1 socket int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");int on = 1;setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on, sizeof(int));   //step2 bind if (bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}//step3 listenif (listen(fd,5) < 0){perror("listen fail");return -1;}struct sockaddr_in cliaddr;bzero(&cliaddr,0);socklen_t len = sizeof(cliaddr);//step4 acceptsignal(SIGCHLD,do_child);while (1){int connfd = accept(fd,(struct sockaddr *)&cliaddr,&len);if (connfd < 0){perror("accept fail");return -1;}printf("---client connect---\n");printf("client ip:%s\n",inet_ntoa(cliaddr.sin_addr));printf("port: %d\n",ntohs(cliaddr.sin_port));//创建 线程 //让子线程去通信 pthread_t tid;int ret = pthread_create(&tid,NULL,do_client,&connfd);if (ret != 0){errno = ret; perror("pthread_create fail");return -1;}pthread_detach(tid); //设置为分离 }return 0;
}

总结:

  • 单循环服务器简单,可以处理多客户端,但不能同时处理
  • 并发服务器(多进程和多线程),可以处理多个客户端,可以同时处理
    • 多进程方式的效率低于多线程

多路IO复用

I --- input

O --- output

多路IO把负责通信的那个进程或线程可以不可以用来处理多个客户端的通信

提高并发的能力

IO的处理的模型:

阻塞IO模型:

        i -- 读

                scanf/getchar/fgets/read/recv

        o -- 写

                 管道的写有阻塞

  • 当进程调用 I/O 操作(如 read(), write(), recv(), send() 等)时,如果数据尚未准备好(例如读缓冲区为空或写缓冲区已满),当前线程会被挂起(阻塞),直到数据准备好或操作完成。

  • 整个过程中,CPU 不会空转,线程会被放入等待队列,直到内核通知其继续执行。

非阻塞IO模型:

  • 当你对一个文件描述符(如 socket)设置为非阻塞模式后,所有 I/O 系统调用(如 read(), write(), recv(), send())在数据未准备好时不会阻塞线程,而是立即返回一个错误码(通常是 EAGAINEWOULDBLOCK)。

  • 用户程序需要** 主动轮询(polling)**或通过 I/O 多路复用(如 select, poll, epoll)来判断何时数据准备好。

fcntl(fd, F_SETFL, O_NONBLOCK);  // 设置为非阻塞模式
ssize_t n = read(fd, buf, sizeof(buf));
if (n == -1 && errno == EAGAIN) {// 数据还没准备好,不能读
}

总结:

信号驱动IO:

信号驱动 I/O(Signal-driven I/O)是 Linux 提供的异步通知机制
当文件描述符上出现可读写事件时,内核不阻塞线程,而是向进程发送一个信号(SIGIO),进程在信号处理函数里再去调用 read() / write() 完成实际的 I/O 操作。
它介于“同步非阻塞轮询”和“真·异步 I/O”之间,被 POSIX 归类为同步 I/O(因为真正的数据拷贝仍然由用户线程完成)。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>int g_fd;
void do_handler(int signo)
{char buf[1024] = {0};read(g_fd,buf,sizeof(buf));printf("buf = %s\n",buf);
}int main(int argc, const char *argv[])
{if (mkfifo(argv[1],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}printf("mkfifo success\n");int fd = open(argv[1],O_RDONLY);if (fd < 0){perror("open fail");return -1;}g_fd = fd;printf("-------open-----\n");char buf[1024] = {0};int flags = fcntl(fd,F_GETFL);flags = flags | O_ASYNC;fcntl(fd,F_SETFL,flags);fcntl(fd, F_SETOWN, getpid());signal(SIGIO,do_handler);int i = 0;while (1){printf("i = %d\n",i);sleep(1);++i;}close(fd);return 0;
}

总结:

模型                   数据准备阶段        数据拷贝阶段       是否阻塞线程
阻塞 I/O             阻塞                       阻塞                     全程阻塞
非阻塞 I/O          轮询(立即返回)     阻塞                     仅拷贝阻塞
信号驱动 I/O       内核发信号         阻塞                      仅拷贝阻塞
真异步 I/O(AIO)   内核完成          内核完成               全程不阻塞

select

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

功能(What)

在一次系统调用里同时等待多个文件描述符(fd)的 I/O“就绪”事件:

  • 读就绪(可读/有新连接/对端关闭会读到 0)

  • 写就绪(发送缓冲区可写)

  • 异常(主要是 TCP 带外数据 OOB)
    适合:跨平台、小规模并发场景。

参数(Params)

  • nfds:三个集合中最大 fd 值 + 1(不是数量)。

  • readfds:关心读就绪的 fd 集合(可为 NULL)。

  • writefds:关心写就绪的 fd 集合(可为 NULL)。

  • exceptfds:关心“异常”事件(主要 OOB),一般业务很少用(可为 NULL)。

  • timeout:超时控制(可为 NULL)。

    • NULL:一直等到有事件。

    • {0,0}:非阻塞轮询,立刻返回。

    • 其他:最多等待指定时间。

注意:Linux 会修改 timeout 为“剩余时间”readfds/writefds/exceptfds 也会被改写为“就绪子集”

返回值(Return)

  • > 0:本次就绪的 fd 个数

  • = 0:超时。

  • = -1:出错,errno 常见:

    • EINTR:被信号打断(应重试这一轮或结合 pselect)。

    • EBADF:集合里有无效/已关闭的 fd。

    • EINVAL:参数非法(如 nfds 小于等于最大 fd)。

http://www.xdnf.cn/news/18847.html

相关文章:

  • 搭建域服务器
  • spring-ai-alibaba使用
  • 第18章|变量:把数据装进“盒子”的正确方式
  • 机器学习 TF-IDF方法
  • 【docker apoc 部署的neo4j安装apoc插件】
  • MySQL 面试题系列(五)
  • 【Kafka】重点概念和架构总结
  • Python 入门操作指南
  • 如何在 Docker 和AKS上使用 IIS
  • iOS技术之通过Charles抓包http、https数据
  • 【Linux】基本指令学习3
  • opencv+yolov8n图像模型训练和推断完整代码
  • Clerk 用户认证系统集成文档
  • ollama离线部署+大语言模型
  • AI-调查研究-62-机器人 机械臂五大应用场景详解:从焊接到手术,从农田到太空
  • 4步用代码拆解数学建模中的TOPSIS评价决策! ! !
  • Apache Commons Lang 3
  • 野火STM32Modbus主机读取寄存器/线圈失败(二)-解决CRC校验错误
  • uC/OS-III 队列相关接口
  • 数据分析与数据挖掘
  • 企业如何构建全面的高防IP防护体系?
  • Teams Workflows 业务流程搭建与Linux自动化运维拓展应用全解析
  • 状态设计模式
  • 构建面向人工智能决策的世界模型引擎所需的基本知识体系
  • 如何在GitHub找到10k+个stars的仓库
  • podman启动mongdb的container因为权限问题导致changing ownership和读取storage.bson失败的解决方法
  • CMake构建学习笔记20-iconv库的构建
  • 算法概述篇
  • 游戏空间划分技术
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(20):文法+单词第7回2