Socket I/O 模型学习总结

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本文主要是针对Socket I/O 模型做自我学习总结。

网络I/O的模型大致有如下几种: 网络I/O的本质是socket的读取,socket在linux系统被抽象为流,I/O可以理解为对流的操作。 这个操作又分为两个阶段: 等待流数据准备(wating for the data to be ready)。 从内核向进程复制数据(copying the data from the kernel to the process)。 第一步通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。 第二步把数据从内核缓冲区复制到应用进程缓冲区。 举个简单比喻,来了解这几种模型。 网络IO好比钓鱼,等待鱼上钩就是网络中等待数据准备好的过程,鱼上钩了,把鱼拉上岸就是内核复制数据阶段。 钓鱼的人就是一个应用进程。 阻塞I/O是最流行的I/O模型。


一、基本概念

进程(线程)切换:所有系统都有调度进程的能力,它可以挂起一个当前正在运行的进程,并恢复之前挂起的进程。

进程(线程)的阻塞:运行中的进程,有时会等待其他事件的执行完成,比如等待锁,请求I/O的读写;进程在等待过程会被系统自动执行阻塞,此时进程不占用CPU。

文件描述符:在Linux,文件描述符是一个用于表述指向文件引用的抽象化概念,它是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,网络套接字也是文件描述符。

Linux信号处理:Linux进程运行中可以接收来自系统或者进程的信号值,然后根据信号值去运行相应捕捉函数;信号相当于是硬件中断的软件模拟。

用户空间和内核空间:操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。为了避免用户进程直接操作内核,保证内核安全,操作系统将虚拟内存划分为两部分,一部分是内核空间(kernel-space),一部分是用户空间(user-space)。在linux系统中,内核模块运行在内核空间,对应的进程处于内核态;而用户程序运行在用户空间,对应的进程处于用户态。

同步:调用某个东西,调用方得等待这个调用结果返回才能继续往后执行。

异步:和同步相反,调用方不会立刻得到结果,而是在调用发出之后调用者可以继续执行后续的操作,被调用者通过状态、通知或者回调函数来处理这个调用。

同步和异步强调的是消息通信机制 (synchronous communication/ asynchronous communication)。

阻塞:指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

非阻塞:不能立刻得到结果之前,该调用不会阻塞当前线程,而会立刻返回。

阻塞和非阻塞 强调的是程序在等待调用结果(消息,返回值)时的状态。

同步异步不能和阻塞非阻塞混为一谈,实际上他们有区别。比如对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket中调用recv函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。

同步IO和异步IO的区别就在于:数据访问的时候进程是否阻塞。

阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回。

同步和异步都只针对于本机SOCKET而言的。

阻塞和非阻塞是指当server端的进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪;

而同步和异步是指client端访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞(区别就

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于非阻塞I/O模型的服务器回射程序设计,可以使用以下步骤: 1. 创建 socket,并设置为非阻塞模式: ```c int listenfd = socket(AF_INET, SOCK_STREAM, 0); fcntl(listenfd, F_SETFL, O_NONBLOCK); ``` 2. 绑定 IP 地址和端口号: ```c struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); ``` 3. 监听 socket: ```c listen(listenfd, SOMAXCONN); ``` 4. 创建一个事件循环,并注册监听 socket 的读事件: ```c int epfd = epoll_create(1); struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; ev.data.fd = listenfd; epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); struct epoll_event events[MAX_EVENTS]; ``` 5. 在事件循环中,处理监听 socket 的读事件和连接 socket 的读写事件: ```c while (1) { int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); for (int i = 0; i < nfds; i++) { if (events[i].data.fd == listenfd) { // 有新的连接 while (1) { struct sockaddr_in clientaddr; socklen_t clientaddrlen = sizeof(clientaddr); int connfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen); if (connfd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } else { perror("accept"); break; } } fcntl(connfd, F_SETFL, O_NONBLOCK); ev.events = EPOLLIN | EPOLLET; ev.data.fd = connfd; epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); } } else { // 处理连接的读写事件 int connfd = events[i].data.fd; char buf[1024]; while (1) { int n = read(connfd, buf, sizeof(buf)); if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } else { perror("read"); epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, &ev); close(connfd); break; } } else if (n == 0) { epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, &ev); close(connfd); break; } else { write(connfd, buf, n); } } } } } ``` 在上面的代码中,使用 epoll 实现了非阻塞的事件监听,可以同时处理多个连接的读写事件。 总体而言,基于非阻塞 I/O 模型的服务器回射程序设计相对复杂一些,但可以实现高并发的连接处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值