C++ Webserver从零开始:基础知识(一)——Linux网络编程基础API

前言     

        本专栏将从零开始制作一个C++ Webserver,用以记录笔者学习的过程

        如果你想要跟着我这个专栏制作一个C++ Webserver,你需要掌握以下前置基础课程知识:

                1.C/C++的语法(在Leetcode刷100~200题的程度即可)

                2.计算机网络基础知识

                3.操作系统基础知识

        掌握以上前置课程知识后,即可开始本专栏的内容


一.socket地址API

       1.主机字节序和网络字节序

        我们知道,一个32位计算机的CPU累加器一次能累加4字节的数据,而这4字节的数据在内存中排列的顺序是可以有2种方式的,即大端字节序和小端字节序

        大端字节序:一个整数的高位字节(23~31bit)存储在内存的低地址处

        小端字节序:一个整数的低位字节(0 ~ 7 bit)存储在内存的低地址处

           

        由于不同的主机使用的字节序可能不同,因此两个主机之间发送数据可能会发生错误。解决方法是:

        人们制定一个规范:发送端统一使用大端字节序(因此大端字节序称为网络字节序

        而因为现代PC大多采用小端字节序,因此小端字节序称为主机字节序

        

Linux中实现主机字节序和网络字节序的函数:

#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

        他们的作用可以通过名字理解,比如 htonl :" host to network long" ,即长整型的主机字节序转换成网络字节序。这四个函数中long类型的函数一般用来转换IP地址,short类型的一般用来转换端口号(操作系统基础知识)。


        2.通用socket地址

        主机之间的通信需要知晓对方的地址,而网络中主机的地址是TCP/IP协议族来定义的(计算机网络基础知识),在Linux网络编程中,我们通过使用socket的这个套接字来进行网络通信。socket定义了一系列的API实现网络通信,是非常方便好用的工具。

        在socket网络编程中表示地址的是结构体sockaddr,但由于这个结构体的设计问题,无法容纳多数协议族的地址值,因此Linux定义了一个新的通用socket地址结构体

#include<bits/socket.h>
struct sockaddr_storge
{sa_family_t sa_family;unsigned long int __ss_align;char__ss_padding[128-sizeof(__ss_align)];
}

     


        3.专用socket地址

        以上两种通用socket地址其实并不好用,所以Linux为各个协议族提供了专门的socket地址结构体

        其中,UNIX本地协议族使用sockaddr_un,本文不予说明

        而TCP/IP协议族使用sockaddr_in和sockaddr_in6两个分别对应IPv4和IPv6

        

         但需要注意的是,使用sockaddr_in或其他专用socket地址(包括socket_storge),最后都要强制转换成通用socket地址类型sockaddr,这是因为所有socket编程接口使用的地址参数的类型都是sockaddr


二.创建socket

        我们了解socket地址API后,要如何创建一个socket呢?下面是创建socket的代码

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type, int protocol);

int socket(int domain,int type,int protocol);

  • 功能:创建一个套接字
  • 参数:
    • domain:协议族
      • AF_INET:IPv4
      • AF_INET6:IPv6
      • AF_UNIX,AF_LOCAL:本地套接字通信
    • type:通信过程中使用的协议类型
      • SOCK_STEAM:流式协议(传输层使用TCP协议)
      • SOCK_DGRAM:报式协议(传输层使用UDP协议)
    • protoco:具体的一个协议,写 0 就行
  • 返回值:返回文件描述符,操作内核缓冲区
    • 失败:-1

        以上块引用中,在写Webserver时都是用笔者加粗部分的参数

另外:

        type参数中还可以与以下两个参数相与计算:

                SOCK_NONBLOCK:创建的socket为非阻塞

                SOCK_CLOEXEC:用fork调用创建子进程时,在子进程中关闭该socket


三.绑定socket(命名socket)

        在创建socket时,我们在第一个参数时给它指定了协议族,但是并未指定使用协议族中哪个具体的socket地址。所以我们用系统调用bind来给socket绑定地址。

        PS:服务端需要绑定,客户端不需要绑定,客户端采用匿名绑定,操作系统会代劳。

        

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);
  • 功能:将my_addr所指的socket地址分配给未命名的socket文件描述符
  • 参数
    • sockfd:通过socket函数得到文件描述符
    • addr:需要绑定的socket地址,这个地址封装了ip和端口号的信息
    • addrlen:第二个参数结构体占的内存大小
  • 返回值
    • 0:成功
    • -1:失败


四.监听socket

        现在我们创建好了socket,也为其分配好了socket地址,接下它就可以工作进行主机间的通信了吗?其实并不行,我们还需要为他创建一个监听队列,用以存储待处理的客户连接。

        以下是创建监听队列的系统调用

        

#include<sys/socket.h>
int lsiten(int sockfd,int backlog);

int listen(int sockfd,int backlog);

  • 功能:监听socket上的连接
  • 参数
    • sockfd:通过socket()函数得到文件描述符
    • backlog:未连接的和已经连接的和的最大值
  • 返回值:
    • 成功:0
    • 失败:-1


五.接受连接(服务端)

        现在我们已经有了一个监听socket(执行过listen调用,处于LISTEN状态的socket),我们终于可以进行通信啦!而最后一步,就是将监听队列中的一个socket取出来,即可与远端的主机进行读写交互了

        下面是从监听队列取出socket的系统调用

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* addr,socklen_t* addrlen);
  • 功能:接受客户端连接,默认时一个阻塞的函数,阻塞等待客户端连接
  • 参数
    • sockfd:用于监听的文件描述符
    • addr:传出参数,记录连接成功后客户端的地址信息
    • addrlen:指定第二个参数的对应的内存大小
  • 返回值:
    • 成功:返回用于通信的文件描述符
    • 失败:-1


六.发起连接(客户端)

        注意,上文的绑定socket,监听socket和接受连接都是服务端要干的事,接下来讲客户端的任务。在服务端创建好了监听队列后,就可以接受来自客户端的连接请求,那么客户端是怎么发起连接请求的呢?

        

#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr* serv_addr,socklen_t addr_len);
  • 功能:客户端连接服务器
  • 参数
    • sockfd:用于通信的文件描述符
    • serv_addr:客户端要连接的服务器地址信息
    • addrlen:指定第二个参数的对应的内存大小
  • 返回值:
    • 成功:0
    • 失败:-1


七.关闭连接

        通信完成后,如要关闭连接,可以通过下面的系统调用

#include<unistd.h>
int close(int fd);

        fd参数是待关闭的socket,close系统调用不会立刻关闭一个连接,而是将fd的引用计数-1,只有当其引用计数为0时才会真正关闭连接(类似C++的智能指针)。

        在多进程程序中,一次fork系统调用默认将引用计数+1.

        如果你非要立即终止连接,也有办法,即shutdown系统调用,读者可以自行搜索。


八.数据读写

        我们在二到七的过程中完整经历了socket通信的创建,命名,监听,接受(发起),关闭的过程,在连接建立成功到关闭连接的这个时间段中我们就可以进行两个主机之间的通信。

        通信的方式即:

                1.发送信息

                2.接受信息

        专业的说法也就是数据读写。Linux本身对文件的读写也可以用于socket,因为socket本身也是文件(Linux中万物皆文件)。但socket还是提供了几种好用的数据读写系统调用。

          分别有

                1.TCP数据读写

                2.UDP数据读写

                3.通用数据读写

           本文只介绍TCP数据读写,UDP和通用数据读写请读者自行学习

        

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd,void* buf,size_t len,int flags);
ssize_t send(int sockfd,const void* buf,size_t len,int flags);
  • 功能:数据读写
  • 参数:
    • sockfd:用于通信的文件描述符
    • buf: 缓冲区的位置
    • len:缓冲区的大小
    • flags:通常取0,其他含义自行搜素,可以进行具体的控制
  • 返回值:
    • 成功:
      • recv:返回实际读到的数据的长度,如果小于期望长度len就多调用几次recv
      • send:实际写入的数据长度
    • 失败:-1


九.一些废话

        前面一到八即Linux网络编程基础API的常用内容了,其他的一些不常用的API如地址信息函数,socket选项等没有写入文中,一是因为其使用场景较少,二是我对这个专栏的定位是:

        简洁且重要

        我写的内容基本上是完成C++ Webserver所必须掌握的前置知识,所以会极可能的少,这样的话对一些没有基础却又无从下手的后辈能起到一个引入门的作用。

        等真正地了解了这些内容后,再去提升应该会容易的多。之所以这也想是因为我在学习完C/C++,操作系统,计算机网络之后进行项目制作时发现根本无从下手,我在网上找到的内容很少有建立在你完全不了解任何Webserver的知识,但又学完了基础课程的情况下。所以我希望能写一些内容,对和我一样的同学有些帮助。

        另外就是我也是在学习过程中,所以想记录一下学习过程帮自己更好地完成这个目标。如果本文有什么问题,或者你有什么建议的话欢迎私信笔者,我看到了应该都会回复。
                

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/343092.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工业相机相关概念词介绍:ISP算法、线阵相机、常用术语

工业相机相关概念词介绍&#xff1a;ISP算法、线阵相机、常用术语 ISP基本框架及算法介绍相机的常用设置50个常用术语 0. ISP基本框架及算法介绍 ISP(Image Signal Processor)&#xff0c;即图像处理&#xff0c;主要作用是对前端图像传感器输出的信号做后期处理&#xff0c…

伴随矩阵定义和计算

一、伴随矩阵定义 1&#xff09;代数余子式 代数余子式也很好理解&#xff0c;在余子式的基础上多了一个-1的次方而已。 2)余子式 余子式很好理解&#xff0c;就是除了这个元素&#xff0c;出去该行该列剩下的行列式的值。 求每个元素的代数余子式&#xff0c;按行求&#xf…

Chrome 浏览器插件从 Manifest V2 升级到 V3 版本所需要修改的点

一、Manifest V2 支持时间表 Chrome 浏览器官方已经给出确定的时间来弃用 V2 版本的插件了。 最早从 2024 年 6 月的 Chrome 127 开始&#xff0c;我们将开始停用 Chrome 的不稳定版本&#xff08;开发者版、Canary 版和 Beta 版&#xff09;中的 Manifest V2 扩展程序。受此变…

Ansible的切片特性与多机器选取

一、【概述】 本文介绍一下Ansible的多机器选取和切片特性&#xff0c;这个还是一个比较有用的技巧&#xff0c;可以快速选取仓库中我们需要的机器清单。 因为该特性可能与其他工具语法稍微有些不一样&#xff0c;时间长了会忘&#xff0c;值得记录一下 二、【具体说明】 1…

【Effective Objective - C】—— 熟悉Objective-C

【Effective Objective - C】—— 熟悉Objective-C 熟悉Objective-C1.oc的起源消息和函数的区别运行期组件和内存管理要点&#xff1a; 2.在类的头文件中尽量少引入其他头文件向前声明要点&#xff1a; 3.多使用字面量语法&#xff0c;少用与之等价的方法字符串字面量字面数值字…

SQL-修改表操作

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现错误&am…

探索SQL性能优化之道:实用技巧与最佳实践

SQL性能优化可能是每个数据库管理员和开发者在日常工作中必不可少的一个环节。在大数据时代&#xff0c;为确保数据库系统的响应速度和稳定性&#xff0c;掌握一些实用的SQL优化技巧至关重要。 本文将带着开发人员走进SQL性能优化的世界&#xff0c;深入剖析实用技巧和最佳实践…

运用3d技术建立数字化模型---模大狮模型网

随着科技的不断进步和发展&#xff0c;3D技术已经被广泛运用于各个领域。其中&#xff0c;建立数字化模型是3D技术的一个重要应用方向。本文将从3D技术的概念、数字化模型的定义、数字化模型的建立方法、应用领域等方面介绍运用3D技术建立数字化模型的相关知识。 一、3D技术的概…

JVM内存模型深度剖析与优化

欢迎大家关注我的微信公众号&#xff1a; 目录 JVM整体结构及内存模型 JVM内存参数设置 JVM整体结构及内存模型 首先附一段简单代码&#xff0c;我们从代码层面来讲解内存模型 public class Math {public static final int initData 666;public static User user new …

【QML COOK】- 007-Item对象、信号和槽

信号&#xff08;signal&#xff09;和槽&#xff08;slot&#xff09;是Qt的独特的设计&#xff0c;自然在QML中也被支持。 Item是QML所有类型的基类&#xff0c;Item类型不会显示在窗口上&#xff0c;但是可以支持信号和槽。本节就用Item编写一个信号和槽的实例。 1. 创建Q…

【ACL 2023】 The Art of Prompting Event Detection based on Type Specific Prompts

【ACL 2023】 The Art of Prompting: Event Detection based on Type Specific Prompts 论文&#xff1a;https://aclanthology.org/2023.acl-short.111/ 代码&#xff1a;https://github.com/VT-NLP/Event_APEX Abstract 我们比较了各种形式的提示来表示事件类型&#xff0…

uniapp 打包成 apk(原生APP-云打包)免费

修改APP配置 根据需求&#xff0c;修改 manifest.json 配置&#xff0c;常见的修改有&#xff1a; 应用名称&#xff0c;应用版本名称&#xff0c;应用版本号 升级版本时&#xff0c;应用版本名称和应用版本号必须高于上一版的值 应用图标 点浏览选择png格式的图片后&#x…