当前位置:   article > 正文

【C++】网络通讯_c++网络通信

c++网络通信

快速开始

TCP/IP通信模型

通信模型示意图

Server & Client

服务端客户端通信示意图

函数详解

socket()

int socket(int domain, int type, int protocol);
  • 1
  • 返回值:获得I/O文件描述符(类似于open())
  • 参数:
    • domain:即协议域,又称为协议族(family)
      • AF_INET,使用IPV4地址
      • AF_INET6,使用IPV6地址
      • AF_LOCAL(或称AF_UNIX),使用一个绝对路径名作为地址
      • AF_ROUTE等等
    • type:指定socket类型。
      • SOCK_STREAM,传输控制协议TCP
      • SOCK_DGRAM,用户报数据协议UDP
      • SOCK_RAW,原始数据(IP协议的数据报),用于直接访问网络层
      • SOCK_PACKET,SOCK_SEQPACKET等等
    • protocol:协议,通常是零

bind()

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 1
  • 返回值:为0时表示绑定成功,-1时表示绑定失败
  • 参数:
    • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket
    • addr:指向要绑定给sockfd的协议地址,根据创建socket时的地址协议族的不同而不同;
    • protocol:协议,通常是零

补充:字节序
uint32_t ntohl (uint32_t __netlong);
uint16_t ntohs (uint16_t __netshort);
uint32_t htonl (uint32_t __hostlong);
uint16_t htons (uint16_t __hostshort);
函数名:n代表网络,h代表主机,即n to h,最后的 l 代表整型,4字节,s代表2字节。

struct sockaddr {
  __SOCKADDR_COMMON (sa_);
  char sa_data[14];	/* Address data.  */
};
  • 1
  • 2
  • 3
  • 4

IPV4
IPV4定义
IPV6
IPV6定义
如何使用?
使用

if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
  • 1

listen()

int listen(int sockfd, int backlog);
  • 1
  • 返回值:若成功返回0,若出错返回-1
  • 参数:
    • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket
    • backlog:完全连接队列容量

connect()

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
  • 1
  • 返回值:若成功返回0,若出错返回-1
  • 参数:
    • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket
    • serv_addr:server端地址
    • addr_len:地址结构体长度

连接过程
连接过程示意图
当全连接队列已满,就会根据/proc/sys/net/ipv4/tcp_abort_on_overflow策略进行处理。

  • 当tcp_abort_on_overflow=0,服务端直接丢弃该ACK;
    • 此时服务端处于【syn_rcvd】的状态
    • 客户端处于【established】的状态
    • 服务端定时 SYN/ACK 给客户端(默认5次)
    • 客户端发送数据过来,服务端会返回RST
  • 当tcp_abort_on_overflow=1,服务端直接返回RST通知client;
    • client会报connection reset by peer

accept()

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 1

用法示意

send()/recv()

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • 1
  • 2
  • 返回值:
    • -1:出错,表示通信链路已不可用
    • 大于零的值:表示已发送或接收数据的长度
  • 参数:
    • sockfd:为已建立好连接的socket。
    • buf:收发的数据地址
    • len:数据的长度,不能超过buf的大小,否则内存溢出
    • flags填0, 其他数值意义不大

发的时候可能没有全发,所以实际开发过程中需要在外进行封装,记录发了多少字节,接收也同理。

拆包和粘包

示意图

  • 影响:
    • 接收端难以分辨Message
  • 原因:
    • socket缓冲区与滑动窗口
    • MSS/MTU限制
    • Nagle算法
  • 措施:
    • 协议:定长、特殊分隔符、变长

close()四次分手

示意图

  • MSL(Maximum Segment Lifetime),报文最大生存时间;
  • 2MSL默认是60秒,停留在TIME_WAIT状态 60 秒;
  • 即断开连接后端口有60秒是不可用的。

并发编程

多进程模式

fork()一个子进程,给每个连接提供一个子进程。
示意图1

多线程模式

示意图2

I/O复用

服务端经典的C10k问题

  • 五种I/O模型
    • 同步阻塞IO(Blocking IO):即传统的IO模型。
    • 同步非阻塞IO(Non-blocking IO):设置socket属性为NONBLOCK。
    • IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO
    • 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO
    • 基于信号驱动的IO(Signal Driven IO)模型,该模型并不常用
  • 同步和异步、阻塞和非阻塞
    • 同步和异步描述的是:用户线程与内核的交互方式
    • 阻塞和非阻塞描述的是:用户线程调用内核IO操作的方式
    • 同步和异步关注的是:程序之间的协作关系
    • 阻塞与非阻塞:单个进程(线程)的执行状态

select()

思路:在读取socket之前,先查下它的状态,然后只处理ready状态的socket。

 // 初始化fdset集合
  fd_set readfdset;  
  FD_ZERO(&readfdset);
  FD_SET(listensock,&readfdset);
  
  // readfdset中socket的最大值。
  int maxfd = listensock;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • fd_set: 1024bit的位图
  • FD_SET: 将fd对应的位设置为1
  • maxfd:监视fd中的最大值
    示意图
  • readfdset保存到临时变量
  • 调用select阻塞等待I/O就绪
  • 遍历readfdset,逐位检测
  • listen事件,调用accept获取新的fd
  • read事件

优点:

  • 单线程处理多个的I/O请求
  • 可以设置超时时间

缺陷:

  • 可监视的fd数量上限是1024
  • 线性扫描fd,效率低

poll()

思路:用于数组代替fd_set,消除监视fd数量的限制。

struct pollfd fds[MAXNFDS];
fds[clientsock].fd = clientsock;
fds[clientsock].events = POLLIN; 
  • 1
  • 2
  • 3
  • 定义任意长度pollfd数组
  • 以fd为下标,填充数组,并指定需要监听的I/O类型
struct pollfd {
  int fd;		/* File descriptor to poll.  */
  short int events;	/* Types of events poller cares about.  */
  short int revents;  /* Types of events that actually occurred.  */
};
  • 1
  • 2
  • 3
  • 4
  • 5

使用

while (1) {
  int infds = poll(fds,maxfd+1,5000);
  
  for (int eventfd=0; eventfd <= maxfd; eventfd++) {
	if ((fds[eventfd].revents&POLLIN)==0) continue;
       ssize_t isize=read(eventfd,buffer,sizeof(buffer));
       // do something
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

优点:解决了selecr中fd上限的问题

缺陷:与select一样,遍历获取描述符

epoll()

思路:只返回状态发生变化的fd。

 int epollfd = epoll_create(1);
  // 添加监听描述符事件
  struct epoll_event ev;
  ev.data.fd = listensock;
  ev.events = EPOLLIN;
  epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

基于事件的触发模式,事件类型:EPOLLIN、EPOLLOUT等。

struct epoll_event {
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
}
  • 1
  • 2
  • 3
  • 4

事件中可以携带fd或任意用户数据。

typedef union epoll_data {
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用

while (1) {
  struct epoll_event events[64]; 
  int infds = epoll_wait(epollfd,events,64,-1);
  // 遍历有事件发生的结构数组。
  for (int i=0;i<infds;i++) {
    if (events[ii].events & EPOLLIN) {
      // 读取客户端的数据。
      read(events[i].data.fd,buffer,sizeof(buffer));
      do_something()
    }
  }
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

高效的原因:

  • 仅返回状态变化的fd
  • 使用mmap,在内核与用户空间传递信息

使用限制:

  • 当监控fd数量较少(10)时没有优势;
  • 仅能在linux上使用;

设计模式reactor

示意图

  • 同步的等待多个事件源到达
  • 将时间多路分解以及分配相应的时间服务进行处理,这个分派采用server集中处理(dispatch)
  • 分解的事件以及对应的事件服务应用从分派服务中分离出去(handler)

单reactor 单线程模型

此种模型,通常只有一个epoll对象,所有的接收客户端连接、客户端读取、客户端写入操作都饱含在一个线程内。该种模型也有一些中间件在用,比如redis。

单reactor 多线程模型

该模型主要是通过将前面的模型进行改造,将读写的业务逻辑交给具体的线程池来实现,这样可以显示reactor线程对IO的响应,以此提升系统性能。

muti-reactor 多线程/进程模型

在这种模型中,主要分为两个部分:mainReactor、subReactors。
mainReactor主要负责接收客户端的连接,然后将建立的连接通过负载均衡的方式分发给subReactors,subReactors来负责具体的每个连接的读写。

主流中间件所采用的网络模型

组件框架epoll触发方式reactor模型
redis水平触发单reactor模型
skynet水平触发单reactor模型
memcached水平触发多reactor模型(多线程)
nginx边缘触发多reactor模型(多进程)

主流网络框架

  • netty
  • gnet
  • libevent
  • evio(golang)
  • ACE(C++)
  • boost::asio(C++)
  • muduo(linux only)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/678787
推荐阅读
相关标签
  

闽ICP备14008679号