嵌入式学习day38
http天气项目总结:
http用户层协议
从网上获得了天气数据
客户端程序:
socket
connect:组织了http请求报文并发送
回复响应报文 + 数据
对数据做解析
协议:
每一次的通信的规则
connection:keep-alive //长连接
特点:
- tcp通信建立连接
connection:close //短连接
特点:
- 每次通信,都需要重新建立连接
半包问题
做处理:
- connect server
- request_http_future
- recv http response
- parse_data
- 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()
)在数据未准备好时不会阻塞线程,而是立即返回一个错误码(通常是EAGAIN
或EWOULDBLOCK
)。用户程序需要** 主动轮询(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)。