iis服务器助手广告广告
返回顶部
首页 > 资讯 > 操作系统 >Linux下C编程(3)
  • 315
分享到

Linux下C编程(3)

Linux 2023-01-31 01:01:45 315人浏览 八月长安
摘要

创建一个能用的Socket是非常简单的,因为GLIBC已经为你做了很多简化工作,但是从另一个角度来说,一个通用的SOCKET不代表一个高效性能的网络应用。我们前面说到sockfd其实同真正的FD是一样的。都是linux下的一个打开的设备描述

创建一个能用的Socket是非常简单的,因为GLIBC已经为你做了很多简化工作,但是从另一个角度来说,一个通用的SOCKET不代表一个高效性能的网络应用。我们前面说到sockfd其实同真正的FD是一样的。都是linux下的一个打开的设备描述符。内核通过这个描述符进行I/O操作。进行I/O操作就有一个性能问题,这个性能问题在于两个条件,一个条件是对同一个FD,有多个客户进行操作时如何更好的排队。另一个就是一个客户如果有多个FD,那应该怎么排队选择问题。因为我们知道不管是READ还是READFREOM它其实都是阻塞操作。一旦占用就始终等到有新数据来到。那么如何解决这个问题呢?首先我们看第一个排队问题,就是多个客户使用同一个SOCKET,如果当前来的数据不是占据的客户,那显然会导致阻塞。所以我们想出另一个方法,就是当一个或多个I/O条件满足,如输入数据已准备好被读或者描述字可以承接更多输出时的时候,作为消费者的客户端可以被通知到,这样的能力称之为I/O复用。这个在GLIBC中设计了两个新的函数就是SELECT/POLL。以下是几种I/O模型的比较图:

1)阻塞I/O模型,缺省的套接口都是阻塞的,你使用READ时一定要有数据时进程才会进行下去。如下图:

image

2)非阻塞I/O,在将套接口设置为非阻塞方式下,内核就让请求的I/O操作在没有数据的情况直接返回一个错误,不再等特。显然这种操作需要不停的尝试,消耗非常多的CPU。

image

非阻塞I/O可以使用fcntl参数进行设置READ/RECVFROM,但很明显它的尝试次数非常多。

[windriver@windriver-Machine ltest]$ GCc noread.c -o noread
[windriver@windriver-machine ltest]$ strace -e read -o out.txt ./noread
aaa
aaa
bbbbb
bbbbb
ccccc
ccccc

[windriver@windriver-machine ltest]$ cat out.txt
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320\20"..., 512) = 512
read(0, 0x80498c0, 4096)                = -1 EAGaiN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, "aaa\n", 4096)                  = 4
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, "bbbbb\n", 4096)                = 6
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, "ccccc\n", 4096)                = 6
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, "\n", 4096)                     = 1
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
read(0, 0x80498c0, 4096)                = -1 EAGAIN (Resource temporarily unavailable)
--- SIGINT (Interrupt) @ 0 (0) ---
+++ killed by SIGINT +++
[windriver@windriver-machine ltest]$
[windriver@windriver-machine ltest]$ cat noread.c
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<errno.h>

char buffer[4096];

int main(int argc,char** argv)
{

  int delay = 1,n,m = 0;
  if(argc>1)
     delay = atoi(argv[1]);
  fcntl(0,F_SETFL,fcntl(0,F_GETFL)|O_NONBLOCK);
  fcntl(1,F_SETFL,fcntl(1,F_GETFL)|O_NONBLOCK);

  while(1)
  {
    n = read(0,buffer,4096);
    if(n>0)
      m = write(1,buffer,n);
    if((n<0||m<0) && (errno!=EAGAIN))
      break;
    sleep(delay);
  }
  perror(n<0 ?"stdin":"stdout");
  exit(1);
}

3)I/O复用,I/O复用的基本想法就是使用一个数据结构来存储I/O操作与FD的映射关系,使用专门的函数select和poll来检测socketFD,一旦FD上数据准备好,则直接就可以使用I/O操作。

image

3.1)I/O复用之信号驱动方式 这种方式不使用select 函数,只是在应用刚建立时安装好相应的信号,然后在信号处理程序中进行数据I/O操作。这方法一般不建议使用,因为毕竟信号量是一种比较大的中断操作,会导致系统停顿,而且内核是否支持这种SIGIO信号量,还有SIGIO是否是SOCKET操作以及I/O操作集中于信号处理程序中。这些都是需要考虑的因素,一般不建议使用。

image

3.2)I/O复用之异步I/O模式,这只见于POSIX.1的1993版本中,是2.6内核的一个标准特性,简称AIO,基本思想是允许进程发起很多I/O操作,而不用阻塞或者等特操作完成。虽然是2.6标准特性,但由于复杂,目前没有很好的实现。

image

3.3)select 函数是建立在fd_set这个数据类型基础之上,本质上对FD集合的枚举过程,它的操作过程非常简单,就是在三种类型的FD集合中,在指定时间范围内检测是否有数据准备好,如果准备好,则返回大于0数值表示,如下图所示,

image

其中maxfdp1是需要检查的文件描述符个数,通常是后面三种文件描述符集合中FD值最大值加上1。这主要是为了枚举性能考虑,而不是将所有FD都算上。返回值是响应I/O操作的操作文件描述符的数量的最大值,因此,如果有多个FD,需要使用FD_ISSET进行测试是否是当前返回。另外因为在不同平台的fd_set长度不同,通常系统默认是FD_SET_SIZE是1024,可以通过修改参数,并且内部实现不一样,所以通常使用一些宏操作进行增删清空。

image

这里说到select函数,实际上还有一个非常类似的函数是poll,通常poll是System V标准,而select是BSD标准。 但是LINUX比较搞,它是上层用SELECT,实际上底层还是用的POLL. 在LINUX下实际上POLL性能比SELECT要高一点,POLL也是监视FD集合,不过将这个FD集合单独使用一个数据结构pollfd.

struct pollfd {
          int fd;        
          short events;  
          short revents;
      };

然后用poll(struct pollfd *ufds,unsigned int nfds, int timeout),所以poll枚举一个确定的FD集合,并且使用确定的事件来关注,通过返回这些确定的事件,可以确定I/O读写。相对于select来说,没有FD_SETSIZE限制。但是仍然需要对FD集合进行线性扫描。

[windriver@windriver-machine ltest]$ gcc pollexam.c -o pollexam
[windriver@windriver-machine ltest]$ ./pollexam
sdfdasdftimeout :0,0
[windriver@windriver-machine ltest]$ sdfdasdf
bash: sdfdasdf: command not found
[windriver@windriver-machine ltest]$ ./pollexam
sfsdfdasdf
============
input string is :sfsdfdasdf
[windriver@windriver-machine ltest]$ ./pollexam
timeout :0,0
[windriver@windriver-machine ltest]$ cat pollexam.c
#include <sys/poll.h>
#include<stdio.h>
#include<string.h>

int main(void)
{

  struct pollfd pfds;
  int retval;
  char str[256];

  memset(&pfds,0,sizeof(pfds));
  pfds.fd=0;
  pfds.events =POLLIN;

  retval= poll(&pfds,1,2*1000);
  if(pfds.revents & POLLIN){
    fscanf(stdin,"%s",str);
    printf("============\ninput string is :%s\n",str);
    }
    else {
      printf("timeout :%d,%x\n",retval,pfds.revents);
    }
}

poll使用事件进行返回,应用通过获取指定FD上关联的事件来进行相应的处理。常用的事件包括读事件POLLIN,写事件POLLOUT如下所示:

image

4)改进版I/O复用之epoll。是LINUX内核在2.6之后为处理大量客户端的socketFd而改进的poll,它也可以称之为select/poll的增强版本。虽然是增强版本,但是也是适用于特定场景下的,这个特定场景是大量并发连接中只有少量活跃的情况。在这种情况下如何避免扫描FD集合的开销和如何有效触发活跃I/O操作。这里有两个关键改进,一个相对于select时FD_SETSIZE无限制,它实际上就是LINUX能够打开的FD的最大数量,通常可以cat /porc/sys/fs/file-max来设制或用ulimit –n 来设置。epoll所支持的FD上限理论上就是最大可以打开文件的数量,也就是说如果你有1G的内存,理论上可以打开10W个FD。这一点可以极大的满足大量用户的服务器开发。另外一个相对于poll需要对保存FD集合的数据结构进行线性扫描,并返回对应的事件这种模式的改进,FD不再进行线性扫描,它只针对活跃的SOCKET进行操作,具体实现就是内核会对FD进行回调实现,只有活跃SOCKET的这些回调函数才能触发IO事件,返回给用户。因此,对一个大并发量的应用服务器,如果有很多连接,但一时时段的活跃连接并不多时,采用EPOLL效率非常高。Epoll对事件触发的方式提供了两种选择,一种是默认的LT模式,即Level trigger,适应于非阻塞和阻塞I/O,缺省。这种模式下内核会一直触发,直到事件被用户消费掉。也就是说在这种情况下FD上的数据一定被写完或者读完才不会有下一次的触发事件。而ET模式,俗称Edge trigger,边沿触发适用于非阻塞模式下,在这种模式下,当文件描述符从未就绪变为就绪时,内核通过epoll告诉你,然后内核假设你已知道文件描述符已就绪,可以进行读写。后面内核不再发送通知,只有当新的文件描述符来到时候才会发出就绪通知。因此,在这种情况下如果没有读完的FD,内核不会继续通知,所以进行文件读写时需要进行循环读写到EATRAN。

[windriver@windriver-machine ltest]$ g++ epollserv.cpp -o epollserv
[windriver@windriver-machine ltest]$ ./epollserv
accept a connect from  127.0.0.1
EPOLLIN
read 12345
EPOLLIN
read 67890

[windriver@windriver-machine ltest]$ cat epollserv.cpp
#include<iOStream>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<errno.h>

using namespace std;

#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000

void setnonblocking(int sock)
{
   int opts;
   opts = fcntl(sock,F_GETFL);
   if(opts<0)
   {
    perror("fcntl(sock,GETFL)");
    exit(1);
   }

   opts = opts|O_NONBLOCK;
   if(fcntl(sock,F_SETFL,opts)<0)
   {
    perror("fcntl(sock,SETFL,opts)");
    exit(1);
   }
}

int main()
{
  int i,maxi,listenfd, connfd,sockfd,epfd,nfds;
  ssize_t n;
  char line[MAXLINE];
  socklen_t clilen;
  struct epoll_event ev,events[20];
  epfd = epoll_create(256);
  struct sockaddr_in clientaddr;
  struct sockaddr_in serveraddr;
  listenfd = socket(AF_INET,SOCK_STREAM,0);
  setnonblocking(listenfd);
  ev.data.fd = listenfd;
  ev.events=EPOLLIN|EPOLLET;
  //ev.events=EPOLLIN;
  epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
  bzero(&serveraddr,sizeof(serveraddr));
  serveraddr.sin_family = AF_INET;
  char* local_addr="127.0.0.1";
  inet_aton(local_addr,&(serveraddr.sin_addr));
  serveraddr.sin_port = htons(SERV_PORT);
  bind(listenfd,(sockaddr*)&serveraddr,sizeof(serveraddr));
  listen(listenfd,LISTENQ);
  maxi = 0;
  for(;;){
     nfds = epoll_wait(epfd,events,20,500);
     for(i=0;i<nfds;++i)
       {
        if(events[i].data.fd == listenfd)
         {
          connfd = accept(listenfd,(sockaddr*)&clientaddr,&clilen);
          if(connfd<0)
           {
             perror("connfd<0");
             exit(1);
           }
          setnonblocking(connfd);
          char *str = inet_ntoa(clientaddr.sin_addr);
          cout<<"accept a connect from  "<<str<<endl;
          ev.data.fd = connfd;
          ev.events = EPOLLIN|EPOLLET;
         //ev.events = EPOLLIN;
         epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
         }
         else if(events[i].events & EPOLLIN)
         {
           cout<<"EPOLLIN"<<endl;
           if((sockfd = events[i].data.fd)<0)
             continue;
           if((n=read(sockfd,line,MAXLINE))<0)
             {
              if(errno == ECONNRESET)
                {
                 close(sockfd);
                 events[i].data.fd = -1;
                }
                else
                   cout<<"readline error"<<endl;
             }
             else if(n == 0)
             {
               close(sockfd);
               events[i].data.fd = -1;
             }
             line[n]='\0';
             cout<<"read "<<line<<endl;
             ev.data.fd = sockfd;
             ev.events = EPOLLOUT|EPOLLET;
            //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
            else if(events[i].events & EPOLLOUT)
            {
              sockfd = events[i].data.fd;
              write(sockfd,line,n);
              ev.data.fd = sockfd;
              ev.events = EPOLLIN|EPOLLET;
              epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
         }
    }
    return 0;
}          
[windriver@windriver-machine ltest]$

[windriver@windriver-machine ltest]$ cat cli.pl
#!/usr/bin/perl

use IO::Socket;
my $host = "127.0.0.1";
my $port = 5000;

my $socket = IO::Socket::INET->new("$host:$port") or die "create socket error $@";
my $msg_out = "1234567890";
print $socket $msg_out;

print "now send over,Go to sleep...\n";

while(1)
{
   sleep(1);
}
[windriver@windriver-machine ltest]$

--结束END--

本文标题: Linux下C编程(3)

本文链接: https://www.lsjlt.com/news/184747.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Linux下C编程(3)
    创建一个能用的SOCKET是非常简单的,因为GLIBC已经为你做了很多简化工作,但是从另一个角度来说,一个通用的SOCKET不代表一个高效性能的网络应用。我们前面说到sockfd其实同真正的FD是一样的。都是LINUX下的一个打开的设备描述...
    99+
    2023-01-31
    Linux
  • linux系统编程(3)
    一 线程间同步 同步:相互之间配合完成一件事情 互斥:保证访问共享资源的完整性(有你没我) POSIX 线程中同步:使用信号量实现 信号量 : 表示一类资源,它的值表示资源的个数 对资源访问: p操作(申请资源) [将资源的值 -...
    99+
    2023-01-31
    系统 linux
  • C语言在linux下编程详解
    目录文件操作总结文件操作 #include <sys/unistd.h> //含有close函数 #include <fcntl.h> //含有op...
    99+
    2022-11-12
  • Linux下c mysql程序的编译命令(转)
    Linux下c mysql程序的编译命令(转)[@more@]最好用 #gcc -o test test.c `mysql_config --cflags --libs`其它的编译命令: #gcc -o t...
    99+
    2022-10-18
  • linux shell编程学习笔记(3)
    1、什么是变量保存变化的数据——变量名:名称固定,由系统预设或用户自定义——变量值:根据用户设置、系统环境变化而变化2、如何定义变量——变量名=变量的值2.1变量名的规则(1)数字、字母、下划线(2)字母区分大小写(3)当变量名相同时,后赋...
    99+
    2023-01-31
    学习笔记 linux shell
  • linux下shell编程教程
    以下是一个简单的Linux下Shell编程教程:1. Shell是什么?Shell是一种命令行解释器,用于执行用户输入的命令。它提供...
    99+
    2023-09-13
    linux
  • 解析Linux下C++编译和链接
    目录编译原理问题一:头文件遮挡目标文件符号解析问题二:静态库顺序动态链接问题三:全局符号介入运行时加载动态库问题四:静态全局变量与动态库导致double free总结编译原理 将如下最简单的C++程序(main.cp...
    99+
    2022-06-03
    c++ linux c++ 编译 c++ 链接
  • c++网络编程下Linux的epoll技术和Windows下的IOCP模型
    目录一、IOCP和Epoll之间的异同1、异2、同二:Epoll理解与应用。1、epoll是什么?2、epoll与select对比优化3、epoll是怎么优化select问题的三、epoll的几个函数的介绍:1、ep...
    99+
    2022-06-03
    Linux epoll技术 Windows IOCP模型
  • linux下练习及答案(3)
    1、创建用户gentoo,附加组为bin和root,默认shell为  /bin/csh,注释信息为"Gentoo Distribution"   2、创建下面的用户、组和组成员关系  名字为admins 的组  用户natasha,使用a...
    99+
    2023-01-31
    答案 linux
  • Linux下原码安装 python-3.
    Linux下原码安装 python-3.6.6 下载 # wget https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tgz 安装依赖包 # yum -y install zl...
    99+
    2023-01-31
    原码 Linux python
  • Linux系统下如何编写C语言
    这篇“Linux系统下如何编写C语言”除了程序员外大部分人都不太理解,今天小编为了让大家更加理解“Linux系统下如何编写C语言”,给大家总结了以下内容,具有一定借鉴价值,内容详细步骤清晰,细节处理妥当,希望大家通过这篇文章有所收获,下面让...
    99+
    2023-06-27
  • C/C++ Linux Socket网络编程流程分析
    目录一、Socket简介二、Socket编程基础 1. 网络字节序2. sockaddr数据结构3. IP地址转换函数三、Socket编程函数1. socket函数2.&n...
    99+
    2023-02-06
    C/C++ Linux Socket网络编程 Linux Socket网络编程 Socket网络编程
  • linux如何编译c程序
    linux中编译c程序的方法:1、打开linux终端;2、在linux终端命令行中输入“gedit yisu.c”命令打开yisu.c文件;3、编译一段完整的c代码,保存并退出;4、输入“gcc yisu.c”命令等待c程序编译成功;5、最...
    99+
    2022-10-25
  • 切面编程(3)
    过滤器相对比较简单了public class WebFiler implements Filter{     //在bean销毁的时候执行 @Override public void destroy() { System.out.p...
    99+
    2023-01-31
    切面
  • linux-php的编译安装3
    环境: Red Hat Enterprise Linux Server release 6.2 (Santiago) 内核: 2.6.32-220.el6.x86_64 PHP版本: php-5.3.10 My...
    99+
    2023-01-31
    linux php
  • Linux C下线程池有什么用
    这篇文章主要介绍了Linux C下线程池有什么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。线程池也是多线程的处理方式。是将“生产者”线程提出任务添加到“任务队列”,然后一...
    99+
    2023-06-15
  • linux入门教程(3)
    (文:flynng)  3.4 进程管理  Linux是一个多用户多任务的操作系统。多用户是指多个用户可以在同一时间使用计算机系统;多任务是指Linux可以同时执行几个任务,它可以在还未执行完一个任务时又执行另一项任务。  在Linux系统...
    99+
    2023-01-31
    入门教程 linux
  • linux下ftp服务阶段实验(3)
    linux下ftp服务阶段实验(3) 3.  (1)匿名用户的FTP根目录是/ftp,只能下载,下载速度为100Kbps    (2)本地用户hello的FTP根目录为/hello,可以上传下载,家目录被锁定,下载速度为200Kbps   ...
    99+
    2023-01-31
    阶段 linux ftp
  • CentOS Linux下的3款分区工具
    博文目录简介  伴随着科技的飞速发展,越来越多的企业对于服务器的稳定要求越来越高,越来越多的企业开始采用linux系统来部署自己的服务,以求高效的稳定性,当然任何操作系统都需要一个最基本的基础,那就是硬盘,及硬盘分区,今天来给大家推荐几款C...
    99+
    2023-01-31
    分区 工具 CentOS
  • Linux操作系统下C语言编程注意事项有哪些
    Linux操作系统下C语言编程注意事项有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、工具的使用本文引用地址:http://www.eepw.com.cn/articl...
    99+
    2023-06-16
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作