从read 系统调用到 C10M 问题

这篇具有很好参考价值的文章主要介绍了从read 系统调用到 C10M 问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.前言

从上个世纪到现在,工程师们在优化服务器性能的过程中,提出了各种不同的io模型,比如非阻塞io,io复用,信号驱动式io,异步io。具体io模型在不同平台上的实现也不一样,比如io复用在bsd上可以由kqueue实现,在solaris系统上可以由/dev/poll实现。为了实现系统的可移植性,POSIX 确保 select和poll在 unix-like系统上得到广泛的支持。

在上个世纪,Dan Kegel 提出了C10K的设想,现在C10K 已经不是什么问题,比如nginx就可以做到百万级别的qps。于是又有人提出来了C10M的设想,Robert David Graham 从unix的最初设计初衷给出了自己的解决方案。

二.常见io模型

1.阻塞io

常见的read系统调用,是最常见的阻塞io:

从read 系统调用到 C10M 问题

2.非阻塞式io

非阻塞io的典型使用方式如下,设置非阻塞标志,并且常与io复用一起使用,使用起来比较复杂。

val = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, val | O_NONBLOCK); /* O_NONBLOCK 标志非阻塞 */

 

从read 系统调用到 C10M 问题

3.io 复用 (select/poll)

io复用在处理数量庞大的fd时非常有效,我们以select为例,select的核心api是select函数:

int select(int nfds, fd_set *_Nullable restrict readfds,
                  fd_set *_Nullable restrict writefds,
                  fd_set *_Nullable restrict exceptfds,
                  struct timeval *_Nullable restrict timeout);

看一个例子:

#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1;
	fd_set		rset;
	char		sendline[MAXLINE], recvline[MAXLINE];

	FD_ZERO(&rset);
	for ( ; ; ) {
		FD_SET(fileno(fp), &rset); /* 设置要监听的socket fd */
		FD_SET(sockfd, &rset); /* 设置要监听的file fd */
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL); /* select 调用 */

		if (FD_ISSET(sockfd, &rset)) {	/* socket 可读 */
			if (Readline(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
			Fputs(recvline, stdout);
		}

		if (FD_ISSET(fileno(fp), &rset)) {  /* input 可读 */
			if (Fgets(sendline, MAXLINE, fp) == NULL)
				return;		/* all done */
			Writen(sockfd, sendline, strlen(sendline));
		}
	}
}

从read 系统调用到 C10M 问题 

4.信号驱动式io

但凡涉及到信号的程序都比较复杂。要使用信号驱动式io,先开启socket的信号驱动式io功能,并通过sigaction 系统调用安装一个信号处理函数:

void
dg_echo(int sockfd_arg, SA *pcliaddr, socklen_t clilen_arg)
{
	int			i;
	const int	on = 1;
	sigset_t	zeromask, newmask, oldmask;

	sockfd = sockfd_arg;
	clilen = clilen_arg;

	for (i = 0; i < QSIZE; i++) {	/* init queue of buffers */
		dg[i].dg_data = Malloc(MAXDG);
		dg[i].dg_sa = Malloc(clilen);
		dg[i].dg_salen = clilen;
	}
	iget = iput = nqueue = 0;

	Signal(SIGHUP, sig_hup); /* 安装信号处理函数 */
	Signal(SIGIO, sig_io);
	Fcntl(sockfd, F_SETOWN, getpid()); /* 设置属主 */
	Ioctl(sockfd, FIOASYNC, &on); /* 开启信号驱动式io */
	Ioctl(sockfd, FIONBIO, &on); /* non-bloking */

	Sigemptyset(&zeromask);		/* init three signal sets */
	Sigemptyset(&oldmask);
	Sigemptyset(&newmask);
	Sigaddset(&newmask, SIGIO);	/* signal we want to block */

	Sigprocmask(SIG_BLOCK, &newmask, &oldmask);
	for ( ; ; ) {
		while (nqueue == 0)
			sigsuspend(&zeromask);	/* wait for datagram to process */

			/* 4unblock SIGIO */
		Sigprocmask(SIG_SETMASK, &oldmask, NULL);

		Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0,
			   dg[iget].dg_sa, dg[iget].dg_salen);

		if (++iget >= QSIZE)
			iget = 0;

			/* 4block SIGIO */
		Sigprocmask(SIG_BLOCK, &newmask, &oldmask);
		nqueue--;
	}
}

从read 系统调用到 C10M 问题 

5.异步io

我们来看一个aio的例子(由于aio的例子过于复杂,我们这里只截取部分关键代码):

for (i = 0; i < NBUF; i++) {
    switch (bufs[i].op) {
        case UNUSED:
            /*
				 * Read from the input file if more data
				 * remains unread.
				 */
            if (off < sbuf.st_size) {
                bufs[i].op = READ_PENDING;
                bufs[i].aiocb.aio_fildes = ifd;
                bufs[i].aiocb.aio_offset = off;
                off += BSZ;
                if (off >= sbuf.st_size)
                    bufs[i].last = 1;
                bufs[i].aiocb.aio_nbytes = BSZ;
                if (aio_read(&bufs[i].aiocb) < 0) /* aio_read */
                    err_sys("aio_read failed");
                aiolist[i] = &bufs[i].aiocb;
                numop++;
            }
            break;

        case READ_PENDING:
            if ((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS) /* aio_error */
                continue;
            if (err != 0) {
                if (err == -1)
                    err_sys("aio_error failed");
                else
                    err_exit(err, "read failed");
            }

            /*
				 * A read is complete; translate the buffer
				 * and write it.
				 */
            if ((n = aio_return(&bufs[i].aiocb)) < 0) /* 调用aio_return成功则 说明数据已经返回 */
                err_sys("aio_return failed");
            if (n != BSZ && !bufs[i].last)
                err_quit("short read (%d/%d)", n, BSZ);
            for (j = 0; j < n; j++)
                bufs[i].data[j] = translate(bufs[i].data[j]);
            bufs[i].op = WRITE_PENDING;
            bufs[i].aiocb.aio_fildes = ofd;
            bufs[i].aiocb.aio_nbytes = n;
            if (aio_write(&bufs[i].aiocb) < 0) /* aio_write */
                err_sys("aio_write failed");
            /* retain our spot in aiolist */
            break;

        case WRITE_PENDING:
            if ((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS) /* aio_error */
                continue;
            if (err != 0) {
                if (err == -1)
                    err_sys("aio_error failed");
                else
                    err_exit(err, "write failed");
            }

            /*
				 * A write is complete; mark the buffer as unused.
				 */
            if ((n = aio_return(&bufs[i].aiocb)) < 0)
                err_sys("aio_return failed");
            if (n != bufs[i].aiocb.aio_nbytes)
                err_quit("short write (%d/%d)", n, BSZ);
            aiolist[i] = NULL;
            bufs[i].op = UNUSED;
            numop--;
            break;
    }
}

 

从read 系统调用到 C10M 问题

6.同步和异步的分类

网络上对io同步和异步的争论很多,这里给出Stevens的分类标准: 

同步 阻塞io,非阻塞io,io复用,信号驱动式io
异步 异步io

 

三.C10K io策略

在上个世纪,Dan Kegel 提出了C10K的设想,即单机实现10k的并发量,主要提出了以下四种类型的解决方法:

服务器范式 例子 备注 软件实现
Serve many clients with each thread, and use nonblocking I/O(level-triggered) select, poll(posix), /dev/poll(solaris), kqueue(bsd) 轮询  
Serve many clients with each thread, and use nonblocking I/O (readiness change) kqueue(bsd), epoll(linux), Realtime Signals(linux) 事件通知 nginx, redis
Serve many clients with each server thread, and use asynchronous I/O aio 异步,没有得到广泛支持  
Serve one client with each server thread

LinuxThreads, Java threading support in JDK 1.3.x and earlier 

早期的java使用绿色线程

 
  • 在实现的过程中有诸多限制,比如打开fd的限制,创建thread数量的限制,根据不同内核而异。
  • 32 位系统,用户态的虚拟空间只有3G,如果创建线程时分配的栈空间是10M,那么一个进程最多只能创建300 个左右的线程。 64 位系统,用户态的虚拟空间大到有128T,理论上不会受虚拟内存大小的限制(10M个线程),而会受系统的参数或性能限制(线程上下文切换)。

四.C10M

Robert David Graham认为如果要解决C10M的问题,必须对unix内核进行改造。当下的unix系统的设计目标是为了满足非常广泛的需求,于是加上了许多通用的功能,比如进程管理,内存管理等等。C10M的问题不是通用的问题,需要自己处理数据控制,而不是依赖unix内核,而且需要做到packet scalability, multi-core scalability, memory scalability。

专项问题,需要特殊的解决方案。

五.总结

本文从常见io模型出发,梳理了高并发服务器可能涉及到的io模型,这些经典io模型在过去十年基本没有发生变化。了解这些底层技术对我们了解深入理解服务器是非常有必要的。

六.参考

《unix网络编程》

http://www.kegel.com/c10k.html#threads.java

http://highscalability.com/blog/2013/5/13/the-secret-to-10-million-concurrent-connections-the-kernel-i.html 

https://man7.org/linux/man-pages/man2/select.2.html                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          文章来源地址https://www.toymoban.com/news/detail-627929.html

到了这里,关于从read 系统调用到 C10M 问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ppt怎么压缩到10m以内?分享好用的压缩方法

    PPT是一种常见的演示文稿格式,有时候文件过大,我们会遇到无法发送、上传的现象,这时候简单的解决方法就是压缩其大小,那怎么才能将PPT压缩到10M以内呢? PPT文件大小受到影响的主要因素就是以下几点: 1、图片大小和数量:PPT中的图片是占用空间最大的元素之一,因

    2024年02月14日
    浏览(41)
  • ppt怎么压缩到10m以内?分享ppt缩小方法

    在日常工作中,我们常常需要制作和分享PowerPoint演示文稿,然而,有时候文稿中的图片、视频等元素会导致文件过大,无法在电子邮件或其他平台上顺利传输。为了将PPT文件压缩到10M以内,我们可以使用一些专门的文件压缩工具。 一、嗨格式压缩大师 嗨格式压缩大师是一款

    2024年02月07日
    浏览(40)
  • 【Linux操作系统】深入理解系统调用中的read和write函数

    在操作系统中,系统调用是用户程序与操作系统之间进行交互的重要方式。其中,read和write函数是常用的系统调用函数,用于在用户程序和操作系统之间进行数据的读取和写入。本文将深入介绍read和write函数的工作原理、用法以及示例代码,以帮助读者更好地理解和应用这两

    2024年02月13日
    浏览(42)
  • 全球、全国遥感土地利用数据产品下载(1m、10m、30m分辨率,内含链接与详细教程)

         土地利用/覆被数据能够获取地表覆被信息,同时也是地球系统科学学科的基础数据(如生态、水文、地质等)吗,目前,基于遥感生成的土地利用/覆被数据产品比较多样,本文整理了目前应用比较多的7种数据产品进行介绍,数据主要为全球数据与全国数据,分辨率为

    2024年02月08日
    浏览(55)
  • pip install 报错: Microsoft Visual C++ 14.0 is required (10M安装包,5分钟解决)

    目录 问题现象  解决办法: 1.下载文件包 2. 安装 3. 安装好之后 pip install 报错: Microsoft Visual C++ 14.0 is required 系统: win10 我们在pip安装的时候有时候会报错, 如下: 这是缺少C++库导致的,解决办法也很简单 网上一堆教程,让你下载几个G的 Visual Studio, 又慢又麻烦,而且还

    2024年02月07日
    浏览(54)
  • 架构设计常用到的10种设计模

    设计模式是软件设计中常用的解决方案,可以帮助解决编程中遇到的一些常见问题。常用的设计模式大致可以分为三大类:创建型、结构型和行为型。这里列举的10种设计模式是从这三大类中挑选出来的,每一种都有其独特的应用场景: 创建型模式 单例模式(Singleton) :确

    2024年03月24日
    浏览(29)
  • win10跑深度学习程序无法调用gpu的问题(已解决)

    win10跑深度学习真的是一言难尽,但是windows系统又使用的比较习惯,过去使用过ubuntu系统,里面写文档什么的确实不习惯,所以自己做的实验项目也主要是以win10为主工具是常见的pycharm+anaconda+win10 采用的是keras2.3.1,更改了程序中一些代码之后,每次跑模型都会中断 记录一下

    2024年01月16日
    浏览(46)
  • win10系统开机提示explorer系统调用失败的解决方法

    最近有win10系统用户在开机的时候,发现遇到提示explorer系统调用失败的情况,这是怎么回事呢,经过分析是由于Explorer.exe 进程遇到问题时导致的,本文就给大家讲解一下win10系统开机提示explorer系统调用失败的具体解决方法。 1、按Ctrl + Alt + Del打开任务管理器 ,打开详细信息

    2024年02月16日
    浏览(54)
  • 关于在Android 11系统手机上请求READ_PHONE_STATE权限的问题

    起因是因为bugly报错: 上网查了下,原来在Android11及以上机型上调用telephonyManager.getNetworkType()需要READ_PHONE_STATE权限,于是我就在应用启动时加上了申请该权限的代码,并且在调用getNetworkType()方法的地方加了判断,如果系统版本大于等于11并且没有被授予READ_PHONE_STATE权限,就

    2024年02月12日
    浏览(48)
  • win10系统下ffmpeg的安装配置与Python调用

    ffmpeg是一个用来对数字音频/视频进行转换的开源程序。其支持文件类型较多,有GUI界面单独操作,或通过命令行调用。 在深度学习中,往往采用ffmpeg来打开/读取音频文件,以作为样本。 目前网上的资料大体是多媒体从业者以ffmpeg单独操作为前提的安装配置,而python中如何调

    2023年04月09日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包