Posix API与网络协议栈实现原理

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: Posix API与网络协议栈实现原理

一、Posix简介

1.1 什么是Posix(Portable Operating System Interface of UNIX )

Posix,意为可移植操作系统接口,它定义了操作系统应该为应用程序提供的接口标准。

1.2 作用

Posix标准旨在期望获得源代码级别的软件可移植性。比如:在linux下写的程序,预期在Windows下也能正常运行。

二、Posix网络API

2.1网络编程客户端和服务端常用API

2.2 客户端和服务端代码示例
2.2.1 服务端server.cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
  if (argc != 2)
  {
    printf("Using:./server port\nExample:./server 5005\n\n"); return -1;
  }
  // 第1步:创建服务端的socket。
  int listenfd;
  if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
  { 
    perror("socket"); 
    return -1; 
  }
  // 第2步:把服务端用于通信的地址和端口绑定到socket上。
  struct sockaddr_in servaddr;    // 服务端地址信息的数据结构。
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;  // 协议族,在socket编程中只能是AF_INET。
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);          // 任意ip地址。
  //servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
  servaddr.sin_port = htons(atoi(argv[1]));  // 指定通信端口。
  if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )
  { 
    perror("bind"); 
    close(listenfd); 
    return -1; 
  }
  // 第3步:把socket设置为监听模式。
  if (listen(listenfd,5) != 0 ) 
  { 
    perror("listen"); 
    close(listenfd); 
    return -1; 
  }
  // 第4步:接受客户端的连接。
  int  clientfd;                  // 连上来的客户端socket。
  int  socklen = sizeof(struct sockaddr_in); // struct sockaddr_in的大小
  struct sockaddr_in clientaddr;  // 客户端的地址信息。
  clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, (socklen_t*)&socklen);
  printf("client (%s) connect server success。。。\n", inet_ntoa(clientaddr.sin_addr));
  // 第5步:与客户端通信,接收客户端发过来的报文后,将该报文原封不动返回给客户端。
  char buffer[1024];
  // memset(buffer, 0, 1024);
  while (1)
  {
      int ret;
      memset(buffer, 0, sizeof(buffer));
      // 接收客户端的请求报文。
      if ( (ret = recv(clientfd, buffer, sizeof(buffer), 0)) <= 0) 
      {
         printf("ret = %d , client disconected!!!\n", ret); 
         break;   
      }
      printf("recv msg: %s\n", buffer);
      // 向客户端发送响应结果。
      if ( (ret = send(clientfd, buffer, strlen(buffer), 0)) <= 0) 
      { 
        perror("send"); 
        break; 
      }
      printf("response client: %s success...\n", buffer);
  }
  // 第6步:关闭socket,释放资源。
  close(listenfd); 
  close(clientfd); 
  return 0;
}
2.2.2 客户端client.cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("Using:./client ip port\nExample:./client 127.0.0.1 5005\n\n"); return -1;
  }
  // 第1步:创建客户端的socket。
  int sockfd;
  if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) 
  { 
    perror("socket"); 
    return -1; 
  }
  // 第2步:向服务器发起连接请求。
  struct hostent* h;
  if ( (h = gethostbyname(argv[1])) == 0 )   // 指定服务端的ip地址。
  { printf("gethostbyname failed.\n"); close(sockfd); return -1; }
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
  memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
  // 向服务端发起连接清求。
  if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)  
  { 
    perror("connect"); 
    close(sockfd); 
    return -1; 
  }
  char buffer[1024];
  // 第3步:与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
  for (int i = 0; i < 3; i++)
  {
    int ret;
    memset(buffer, 0, sizeof(buffer));
    sprintf(buffer, "这是第[%d]条消息!", i+1);
    if ( (ret = send(sockfd, buffer, strlen(buffer),0)) <= 0) // 向服务端发送请求报文。
    { 
      perror("send"); 
      break; 
    }
    printf("发送:%s\n", buffer);
    memset(buffer,0,sizeof(buffer));
    if ( (ret = recv(sockfd, buffer, sizeof(buffer), 0)) <= 0) // 接收服务端的回应报文。
    {
      printf("ret = %d error\n", ret); 
      break;
    }
    printf("从服务端接收:%s\n", buffer);
    sleep(1);
  }
  // 第4步:关闭socket,释放资源。
  close(sockfd);
}
运行结果:

2.3 着重分析以下几个函数
2.3.1 socket函数
int socket(int domain, int type, int protocol);

调用socket()函数会创建一个套接字(socket)对象。套接字由两部分组成,文件描述符(fd)和 TCP控制块(Tcp Control Block,tcb) 。Tcb主要包括关系信息有网络的五元组(remote IP,remote Port, local IP, local Port, protocol),一个五元组就可以确定一个具体的网络连接。

2.3.2 listen 函数
listen(int listenfd, backlog);

服务端在调用listen()后,就开始监听网络上连接请求。第二个参数 backlog, 在Linux是指全连接队列的长度,即一次最多能保存 backlog 个连接请求。

2.3.3 connect 函数

客户端调用connect()函数,向指定服务端发起连接请求。

2.3.4 accept 函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept()函数只做两件事,将连接请求从全连接队列中取出,给该连接分配一个fd并返回。

2.3.5 三次握手过程分析

三次握手与listen/connect/accept三个函数有关,这里放到一起进行描述。

  1. 客户端调用 connect 函数,开始进入三次握手。客户端发送syn包,以及带着随机的seq;
  2. 服务端listen函数监听到有客户端连接,listen函数会在内核协议栈为该客户端创建一个Tcb控制块,并将其加入到半连接队列。服务端在收到syn包后,会给客户端恢复ack和syn包;
  3. 客户端收到服务端的ack和syn后再次恢复ack,连接建立成功。
  4. 服务端在收到客户端的ack后,会将该客户端对应的Tcb数据从半连接队列移动到全连接队列。只要全连接队列中有数据就会触发accept,返回连接成功的客户端fd、IP以及端口。此时,Tcb完整的五元组构建成功。
2.3.6 常见面试问题
  • 为什么要三次握手?
    答:因为一个完整的TCP连接需要双方都得到确认,客户端发送请求和收到确认需要两次;服务端发送请求和收到确认需要两次,当中服务回复确认和发送请求合并为一次总共需要3次;才能保证双向通道是通的。
  • 一个服务器的端口数是65535,为何能做到一百万的连接?
    答:主要是因为一条连接是由五元组所组成,所以一个服务器的连接数是五个成员数的乘积。
  • 如何应对Dos(Deny of Service,拒绝服务)攻击
    答:Dos攻击就是利用三次握手的原理,模拟客户端只向服务器发送syn包,然后耗尽被攻击对象的资源。比较多的做法是利用防火墙,做一些过滤规则
2.4 send/recv 函数

至此,客户端与服务端已经成功建立连接,就可以相互通信了。

send/recv 函数主要负责数据的收发。

2.4.1 过程分析
  • send函数:负责将数据从用户空间拷贝到内核(具体是拷贝到该连接对应的Tcb控制块中的发送缓冲区)。注意:send函数返回并不意味着数据已成功发送,因为数据在到达内核缓冲区后,内核会根据自己的策略决定什么时候将数据发出。
  • recv函数:负责将数据从内核缓冲区拷贝到用户空间。同理,数据也显示到达该连接对应的Tcb控制块的接受缓冲区。
2.4.2 常见面试问题
  • 如何解决Tcp的粘包问题?
    答:(1) 在包头上添加一个数据包长度的字段,用于数据的划分,实际项目中这个也用的最多;(2)包尾部加固定分隔符;
  • Tcp如何保证顺序到达?
    答:顺序到达是由于TCP的延迟ACK的机制来保证的,TCP接收到数据并不是立即回复而是经过一个延迟时间,回复接收到连续包的最大序列号加1。如果丢包之后的包都需要重传。在弱网情况下这里就会有实时性问题和带宽占用的问题;
2.5 close 函数

在服务器与客户端建立连接之后,会进行一些读写操作,完成读写操作后我们需要关闭相应的socket,好比操作完打开的文件要调用fclose关闭打开的文件一样。close过程涉及到四次挥手的全过程

2.5.1 四次挥手流程:
  1. 客户端调用close函数,内核会发送fin包,客户端进入fin_wait1状态;
  2. 服务端收到fin包回复ack,客户端进入close_wait状态。此时,客户客户端往服务端发送的通道就关闭了,因为Tcp是全双工的,服务端还可以向客户端发数据。
  3. 客户端收到ack,进入到fin_wait2状态;
  4. 服务端发送完数据,发送fin包,服务端进入last_ack状态;
  5. 客户端收到fin包后,回复ack,进入到time_wait状态;
  6. 服务端收到ack,双方连接正常关闭。

注意:close操作只是让相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

2.5.2 双方同时调用close

2.5.2 常见面试题
  • time_wait 作用?
    答:防止最后一个ACK没有顺利到达对方,超时重新发送ack。time_wait时常一般是120s可以修改。
  • 服务器掉线重启出现端口被占用怎么办?
    答:其实主要是由于还处于time_wait状态,端口并没有真正释放。这时候可以设置SO_REUSEADDR属性,保证掉线能马上重连。

3. Tcp状态机

文章参考于<零声教育>的C/C++linux服务期高级架构

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
25天前
|
网络协议 安全 5G
网络与通信原理
【10月更文挑战第14天】网络与通信原理涉及众多方面的知识,从信号处理到网络协议,从有线通信到无线通信,从差错控制到通信安全等。深入理解这些原理对于设计、构建和维护各种通信系统至关重要。随着技术的不断发展,网络与通信原理也在不断演进和完善,为我们的生活和工作带来了更多的便利和创新。
62 3
|
2月前
|
并行计算 安全 网络协议
探索未来网络:量子互联网的原理与应用
本文深入探讨了量子互联网的基本概念、技术原理及其潜在应用。通过对量子纠缠、量子叠加和量子隐形传态等核心概念的解释,文章展示了量子互联网如何利用量子力学特性来实现超高速、超高安全性的通信。此外,还讨论了量子互联网在金融、医疗、国防等领域的应用前景,以及当前面临的技术挑战和未来的发展方向。
72 2
|
4天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
15 2
|
15天前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
43 13
|
7天前
|
运维 物联网 网络虚拟化
网络功能虚拟化(NFV):定义、原理及应用前景
网络功能虚拟化(NFV):定义、原理及应用前景
22 3
|
18天前
|
网络协议 安全 算法
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
实战:WireShark 抓包及快速定位数据包技巧、使用 WireShark 对常用协议抓包并分析原理 、WireShark 抓包解决服务器被黑上不了网等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
|
29天前
|
机器学习/深度学习 人工智能 监控
深入理解深度学习中的卷积神经网络(CNN):从原理到实践
【10月更文挑战第14天】深入理解深度学习中的卷积神经网络(CNN):从原理到实践
82 1
|
1月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
1月前
|
存储 安全 算法
网络安全与信息安全:构建数字世界的防线在数字化浪潮席卷全球的今天,网络安全与信息安全已成为维系现代社会正常运转的关键支柱。本文旨在深入探讨网络安全漏洞的成因与影响,剖析加密技术的原理与应用,并强调提升公众安全意识的重要性。通过这些综合性的知识分享,我们期望为读者提供一个全面而深刻的网络安全视角,助力个人与企业在数字时代中稳健前行。
本文聚焦网络安全与信息安全领域,详细阐述了网络安全漏洞的潜在威胁、加密技术的强大防护作用以及安全意识培养的紧迫性。通过对真实案例的分析,文章揭示了网络攻击的多样性和复杂性,强调了构建全方位、多层次防御体系的必要性。同时,结合当前技术发展趋势,展望了未来网络安全领域的新挑战与新机遇,呼吁社会各界共同努力,共筑数字世界的安全防线。
|
1月前
|
存储 安全 自动驾驶
探索未来网络:量子互联网的原理与应用
【10月更文挑战第2天】 本文旨在探讨量子互联网的基本原理、技术实现及其在通讯领域的革命性应用前景。量子互联网利用量子力学原理,如量子叠加和量子纠缠,来传输信息,有望大幅提升通信的安全性和速度。通过详细阐述量子密钥分发(QKD)、量子纠缠交换和量子中继等关键技术,本文揭示了量子互联网对未来信息社会的潜在影响。

热门文章

最新文章