C++ 问题集锦

1.C++ vector和list的区别

1.vector数据结构
vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。
因此能高效的进行随机存取,时间复杂度为o(1);
但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。

2.list数据结构
list是由双向链表实现的,因此内存空间是不连续的。
只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);
由于链表的特点,能高效地进行插入和删除

总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;
如果需要大量的插入和删除,而不关心随机存取,则应使用list。


2.Linux中异步IO等待无非就三个系统调用:select, poll和epoll。很多人无法理解三种调用的区别,或不够了解,今天就结合Linux kernel code详细描述三个的区别!
 
    select:
    select 的限制就是最大1024个fd,可以查看kernel中的posix_types.h,里面定义了fdset数据结构,显然select不适合poll大量fd的场景(如webserver)。 
 
    include/linux/posix_types.h :

#undef __NFDBITS  
#define __NFDBITS       (8 * sizeof(unsigned long))  
  
#undef __FD_SETSIZE  
#define __FD_SETSIZE    1024  
  
#undef __FDSET_LONGS  
#define __FDSET_LONGS   (__FD_SETSIZE/__NFDBITS)  
  
#undef __FDELT  
#define __FDELT(d)      ((d) / __NFDBITS)  
  
#undef __FDMASK  
#define __FDMASK(d)     (1UL << ((d) % __NFDBITS))  
  
typedef struct {  
        unsigned long fds_bits [__FDSET_LONGS];  
} __kernel_fd_set;  
poll:
   poll相对于select改进了fdset size的限制,poll没有再使用fdset数组结构,反而使用了pollfd,这样用户可以自定义非常大的pollfd数组,这个pollfd数组在kernel中的表现形式是poll_list链表,这样就不存在了1024的限制了,除此之外poll相比select无太大区别。

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,  
                struct timespec *end_time)  
{  
        struct poll_wqueues table;  
        int err = -EFAULT, fdcount, len, size;  
        /* Allocate small arguments on the stack to save memory and be 
           faster - use long to make sure the buffer is aligned properly 
           on 64 bit archs to avoid unaligned access */  
        long stack_pps[POLL_STACK_ALLOC/sizeof(long)];  
        struct poll_list *const head = (struct poll_list *)stack_pps;  
        struct poll_list *walk = head;  
        unsigned long todo = nfds;  
  
        if (nfds > rlimit(RLIMIT_NOFILE))  
                return -EINVAL;  
  
        len = min_t(unsigned int, nfds, N_STACK_PPS);  
        for (;;) {  
                walk->next = NULL;  
                walk->len = len;  
                if (!len)  
                        break;  
  
                if (copy_from_user(walk->entries, ufds + nfds-todo,  
                                        sizeof(struct pollfd) * walk->len))  
                        goto out_fds;  
  
                todo -= walk->len;  
                if (!todo)  
                        break;  
  
                len = min(todo, POLLFD_PER_PAGE);  
                size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;  
                walk = walk->next = kmalloc(size, GFP_KERNEL);  
                if (!walk) {  
                        err = -ENOMEM;  
                        goto out_fds;  
                }  
        }  
 epoll:
    select与poll的共同点是fd有数据后kernel会遍历所有fd,找到有效fd后初始化相应的revents,用户空间程序须再次遍历整个fdset,以找到有效的fd,这样实际上就遍历了两次fd数组表,对于极大量fd的情况,这样的性能非常不好,请看一下do_poll代码:

static int do_poll(unsigned int nfds,  struct poll_list *list,  
                   struct poll_wqueues *wait, struct timespec *end_time)  
{  
        poll_table* pt = &wait->pt;  
        ktime_t expire, *to = NULL;  
        int timed_out = 0, count = 0;  
        unsigned long slack = 0;  
  
        /* Optimise the no-wait case */  
        if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
                pt = NULL;  
                timed_out = 1;  
        }  
  
        if (end_time && !timed_out)  
                slack = select_estimate_accuracy(end_time);  
  
        for (;;) {  
                struct poll_list *walk;  
  
                for (walk = list; walk != NULL; walk = walk->next) {  
                        struct pollfd * pfd, * pfd_end;  
  
                        pfd = walk->entries;  
                        pfd_end = pfd + walk->len;  
                        for (; pfd != pfd_end; pfd++) {  
                                /* 
                                 * Fish for events. If we found one, record it 
                                 * and kill the poll_table, so we don't 
                                 * needlessly register any other waiters after 
                                 * this. They'll get immediately deregistered 
                                 * when we break out and return. 
                                 */  
                                if (do_pollfd(pfd, pt)) {  
                                        count++;  
                                        pt = NULL;  
                                }  
                        }  
                }  

   epoll的出现解决了这种问题,那么epoll是如何做到的呢? 我们知道select, poll和epoll都是使用waitqueue调用callback函数去wakeup你的异步等待线程的,如果设置了timeout的话就起一个hrtimer,select和poll的callback函数并没有做什么事情,但epoll的waitqueue callback函数把当前的有效fd加到ready list,然后唤醒异步等待进程,所以你的epoll函数返回的就是这个ready list, ready list中包含所有有效的fd,这样一来kernel不用去遍历所有的fd,用户空间程序也不用遍历所有的fd,而只是遍历返回有效fd链表,所以epoll自然比select和poll更适合大数量fd的场景。
static int ep_send_events(struct eventpoll *ep,  
                          struct epoll_event __user *events, int maxevents)  
{  
        struct ep_send_events_data esed;  
  
        esed.maxevents = maxevents;  
        esed.events = events;  
  
        return ep_scan_ready_list(ep, ep_send_events_proc, &esed);  
}  
 现在大家应该明白select, poll和epoll的区别了吧!有人问既然select和poll有这么明显的缺陷,为什么不改掉kernel中的实现呢? 原因很简单,后向ABI兼容,select和poll的ABI无法返回ready list,只能返回整个fd数组,所以用户只得再次遍历整个fd数组以找到哪些fd是有数据的。
    epoll还包括 “Level-Triggered” 和 “Edge-Triggered”,这两个概念在这里就不多赘述了,因为"man epoll"里面解释的非常详细,还有使用epoll的example。

3.

TCP连接简介
当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,
当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,
连接的建立是需要三次握手的,而释放则需要4次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的
经典的三次握手示意图:



经典的四次握手关闭图:
 


一、长连接与短连接
长连接: 指在一个TCP连接上可以连续发送多个数据包,
        在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接;
        一般需要自己做在线维持。 
短连接: 指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接;
        一般银行都使用短连接。 
        它的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段 

比如http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。 
其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。

长连接与短连接的操作过程 
通常的短连接操作步骤是: 
  连接→数据传输→关闭连接;

而长连接通常就是: 
  连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接; 

这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,
短连接在没有数据传输时直接关闭就行了

什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
所以每个操作完后都不断开,下次次处理时直接发送数据包就OK了,不用建立TCP连接。

例如:数据库的连接用长连接, 
如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

二、发送接收方式
1、异步 
报文发送和接收是分开的,相互独立的,互不影响。这种方式又分两种情况: 
(1)异步双工:接收和发送在同一个程序中,由两个不同的子进程分别负责发送和接收 
(2)异步单工:接收和发送是用两个不同的程序来完成。 

2、同步 
报文发送和接收是同步进行,既报文发送后等待接收返回报文。 
同步方式一般需要考虑超时问题,即报文发出去后不能无限等待,需要设定超时时间,
超过该时间发送方不再等待读返回报文,直接通知超时返回。
 
在长连接中一般是没有条件能够判断读写什么时候结束,所以必须要加长度报文头。
读函数先是读取报文头的长度,再根据这个长度去读相应长度的报文。

三. 单工、半双工和全双工
根据通信双方的分工和信号传输方向可将通信分为三种方式:
单工、
半双工、
全双工。

在计算机网络中主要采用双工方式,其中:
局域网采用半双工方式,
城域网和广域网采用全双年方式。   

1. 单工(Simplex)方式:
通信双方设备中发送器与接收器分工明确,只能在由发送器向接收器的单一固定方向上传送数据。
采用单工通信的典型发送设备如早期计算机的读卡器,典型的接收设备如打印机。   

2. 半双工(Half Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以相互传送数据,但某一时刻则只能向一个方向传送数据。
例如,步话机是半双工设备,因为在一个时刻只能有一方说话。   

3. 全双工(Full Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以同时在两个方向上传送数据。
例如,电话是全双工设备,因为双方可同时说话。

而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。

总之,长连接和短连接的选择要视情况而定。

四、一个最简单的长连接与心跳保持的示例程序
/*!
 ******************************************************************************
 * \File
 *  
 * \Brief
 *   
 * \Author
 *  Hank
 ******************************************************************************
 */
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <arpa/inet.h>
  10. #include <unistd.h>
  11. #include <sys/time.h>
  12. #include <sys/types.h>
#define MAXBUF 1024


int main(int argc, char **argv)
{
  int sockfd, len;
  struct sockaddr_in dest;
  char buffer[MAXBUF];
  char heartbeat[20] = "hello server";
  fd_set rfds;
  struct timeval tv;
  int retval, maxfd = -1;

  if (argc != 3)
  {
    printf("error! the right format should be : \
          \n\t\t%s IP port\n\t eg:\t%s127.0.0.1 80\n",
          argv[0], argv[0]);
    exit(0);
  }

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  {
    perror("Socket");
    exit(errno);
  }

  bzero(&dest, sizeof(dest));
  dest.sin_family = AF_INET;
  dest.sin_port = htons(atoi(argv[2]));
  memset(&(dest.sin_zero), 0, 8);
  if (inet_aton(argv[1], (struct in_addr*)&dest.sin_addr.s_addr) == 0)
  {
    perror(argv[1]);
    exit(errno);
  }

  if (connect(sockfd, (struct sockaddr*)&dest, sizeof(dest)) != 0)
  {
    perror("Connect");
    exit(errno);
  }
  printf("\nReady to start chatting.\n\
        Direct input messages and \n\
        enter to send messages to the server\n");

  while (1)
  {
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);
    maxfd = 0;

    FD_SET(sockfd, &rfds);
    if (sockfd > maxfd)
      maxfd = sockfd;

    tv.tv_sec = 2;
    tv.tv_usec = 0;

    retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
    if (retval == -1)
    {
      printf("Will exit and the select is error! %s", strerror(errno));
      break;
    }
    else if (retval == 0)
    {
      //printf("No message comes, no buttons, continue to wait ...\n");
      len = send(sockfd, heartbeat, strlen(heartbeat), 0);
      if (len < 0)
      {
        printf("Message '%s' failed to send ! \
              The error code is %d, error message '%s'\n",
              heartbeat, errno, strerror(errno));
        break;
      }
      else
      {
        printf("News: %s \t send, sent a total of %d bytes!\n",
              heartbeat, len);
      }

      continue;
    }
    else
    {
      if (FD_ISSET(sockfd, &rfds))
      {
        bzero(buffer, MAXBUF+1);
        len = recv(sockfd, buffer, MAXBUF, 0);


        if (len > 0)
        {
          printf("Successfully received the message: '%s',%d bytes of data\n",
                  buffer, len);
        }
        else
        {
          if (len < 0)
              printf("Failed to receive the message! \
                    The error code is %d, error message is '%s'\n",
                    errno, strerror(errno));
          else
              printf("Chat to terminate!\n");


          break;
        }
      }


      if (FD_ISSET(0, &rfds))
      {
        bzero(buffer, MAXBUF+1);
        fgets(buffer, MAXBUF, stdin);


        if (!strncasecmp(buffer, "quit", 4))
        {
          printf("Own request to terminate the chat!\n");
          break;
        }


        len = send(sockfd, buffer, strlen(buffer)-1, 0);
        if (len < 0)
        {
          printf("Message '%s' failed to send ! \
                The error code is %d, error message '%s'\n",
                buffer, errno, strerror(errno));
          break;
        }
        else
        {
          printf("News: %s \t send, sent a total of %d bytes!\n",
                buffer, len);
        }
      }
    }
  }


  close(sockfd);
  return 0;
}


发布了36 篇原创文章 · 获赞 45 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/u012154840/article/details/78457814
今日推荐