0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

curl工具的简易实现

FPGA之家 来源:FPGA之家 作者:FPGA之家 2022-08-24 09:05 次阅读

前言

一般来说,使用套接字进行网络编程时,默认使用linux内核提供的网络服务。但是,现在我们自己在用户空间构建了一个tcp协议栈,并且让它为其他应用程序提供网络服务,这势必要求我们自己实现一套新的套接字接口,并且提供给其他应用程序指定使用。

但是,我们并不希望把该tcp协议栈封装成动态库的形式,因为这样一来,应用程序的编译是必须要把库一起连接进去的。那么原生网络编程开发的程序就不能基于我们tcp协议栈来运行了。

一种较好的设计思路是,把tcp协议栈剥离出来作为一个独立的组件来运行,然后通过一个中间件,把网络程序与tcp协议栈协同工作起来。这个中间件的主要工作就是负责偷龙换凤,也就是把网络程序中的内核网络服务转换成独立运行的tcp协议栈的网络服务。

核心思路:网络程序(curl)+自定义套接字库(liblevel.so)+tcp协议栈(level-ip),如下图:

7365045c-2341-11ed-ba43-dac502259ad0.png

curl小工具

curl是一种命令行工具,作用是发出网络请求,然后得到和提取数据,显示在"标准输出"(stdout)上面。我们直接在curl命令后加上网址和端口,就可以看到网页源码。比如抓取www.sina.com网址:

curl www.sina.com 80

下面我们来看一个curl工具的简易实现,如下图:

737ffb72-2341-11ed-ba43-dac502259ad0.png

第3行:判断目标主机名是否合法

第11行:判断目标端口好是否合法

第16行:完成主机名到地址解析

第21行:使用socket申请一个套接字描述符

第23行:使用connect函数发起tcp连接

第30行:按照http 1.1协议来填充要发送的内容,此处为http协议的get请求

第33行:调用write来发送网络数据

第41行:在while循环中重复接收服务器返回来的网页数据,并且打印在当前控制台终端上。

这是标准的网络应用程序,使用gcc命令编译后即可运行。

gcc curl.c -o curl

liblevelip.so库

level-ip脚本

liblevelip.so库重新封装了常用的socket套接字,并借助socket原生的本地套接字接口来与tcp协议栈(level-ip)进行数据通信。以后在curl程序使用socket套接字时,优先使用该库的服务接口,而不是内核的网络服务。这是通过level-ip这个shell脚本完成的,具体命令如下:

./level-ip curl www.sina.com 80

我们来分析一下level-ip这个shell脚本的原理,如下图:

73a4d4a6-2341-11ed-ba43-dac502259ad0.png

第1行:执行该shell脚本由/bin/sh程序来执行。

第3行:指定脚本如果发生错误,或者遇到不存在的变量就报错,并停止执行。

第5行:保存脚本的第一个参数到prog变量中。

第6行:去掉一个参数,即原来的1,2,依此类推。

第8行:LD_PRELOAD是一个环境变量,其指定的动态库加载等级最高。@表示第二个参数之后的全部参数。

综上所述,我们就可以确定curl程序是优先加载liblevelip.so库来使用了,通过这种打桩技术,我们可以在加载阶段替换部分系统函数的调用,比如我们常用的socket接口。

liblevelip.c文件

liblevelip.so库由liblevellip.c文件编译而来,该文件在tools文件夹中,我们逐步来分析一下这个c文件。

__libc_start_main函数

首先是__libc_start_main函数,该函数原本是glibc库里面的函数,curl程序里面的main函数就是从这里开始被调用。但是我们在liblevelip.c里面实现了这个函数,并且liblevelip.so的库加载顺序优先于glibc的动态库加载。因此在执行curl程序中的main函数之前,此函数先被执行。如下图:

73af364e-2341-11ed-ba43-dac502259ad0.png

第3行:dlsym函数里的第一个参数为RTLD_NEXT,这意味着我们将从其他动态库去加载__libc_start_main函数符号(比如glibc库),然后把函数句柄赋值给__start_main变量。

第7~22行:从glibc库中加载一部分linux系统原生提供的系统调用接口。因为我们的网络服务还是要依赖于一些更底层的系统调用接口的。

第24行:初始化一个链表节点lvlip_socks。

第26行:调用glibc的原生__libc_start_main接口。

socket函数

接下来就是liblevelip.so对外提供的第一个网络编程接口--socket函数了。该函数实现如下:

73ce1b9a-2341-11ed-ba43-dac502259ad0.png

第3~5行:检查网络通信协议族是否为tcp协议,如果不是tcp协议,则调用内核提供的网络服务。

第9行:借助tcp本地套接字接口,与tcp协议栈建立连接,用于通信的本地文件为/tmp/lvlip.socket

第11行:申请一个lvlip_sock类型的buff用于管理socket信息。结构体类型如下:

struct lvlip_sock {    struct list_head list;    int lvlfd; /* For Level-IP IPC */    int fd;};

list成员变量为链表结点

lvlfd记录与tcp协议栈通信的网络文件描述符

fd记录tcp协议栈的返回状态发送socket消息给tcp协议栈第12行:记录与tcp协议栈通信的网络文件描述符到sock->lvlfd第13行:把这次的网络通信消息加入lvlip_socks链表中第14行:网络通信消息数量加1第16行:获取当前线程的pid号

第17~18行:申请ipc_msg+ipc_socket结构体长度的buff,用于发送详细的socket信息到tcp协议栈。结构体定义如下:

struct ipc_msg {    uint16_t type;    pid_t pid;    uint8_t data[];} __attribute__((packed));

type:记录此次socket信息的具体类型

pid:记录请求网络服务的进程pid号

data:存放具体的通信内容

struct ipc_socket {    int domain;    int type;    int protocol;} __attribute__((packed));

实际上就是soket函数的三个参数。

第23~29行:把ipc_socket作为通信的具体内容填充到ipc_msg的data区域中去

第31行:调用transmit_lvlip()函数真正给tcp协议栈发送消息,并且等待协议栈的数据回复。

此处我们就把liblevelip.so中的socket函数给剖析清楚了,其他诸如close、connect、write、read、send、sendto、recv、此处我们就把liblevelip.so中的socket函数给剖析清楚了,其他诸如close、connect、write、read、send、sendto、recv、此处我们就把liblevelip.so中的socket函数给剖析清楚了,其他诸如close、connect、write、read、send、sendto、recv、recvfrom、poll、select等函数,原理都是一样的,此处不再展开分析。

tcp协议栈(level-ip)

用户空间的level-ip协议栈,在运行之初,就已经在main函数里面创建了一系列线程。如下图:

73f2ec5e-2341-11ed-ba43-dac502259ad0.png

其中第9行,在run_threads()函数里创建了一系列线程,如下图:

740ccf8e-2341-11ed-ba43-dac502259ad0.png

在这里,我们重点关注第5行创建的start_ipc_listener线程

该线程的实现如下:

74186d30-2341-11ed-ba43-dac502259ad0.png

第5行:指定tcp本地通信的路径文件为"/tmp/lvlip.socket",与我们前面liblevelip.so库的本地通信文件一致,这就说明它们之间确实是通过tco本地通信接口来通信的

第10行:调用socket接口开始进行tcp本地通信

第24行:调用bind函数绑定本地通信路径

第31行:调用listen函数监听指定端口,等待liblevelip.so库发起连接

第46行:如果liblevelip.so库发起连接,则调用accept函数准备开始收发信息。

第54行:每监听到一个新的连接,新创建一个socket_ipc_open函数来进行数据的具体收发。

socket_ipc_open函数主要是负责通信信息的读取,然后根据通信消息的类型不同,来进一步调用具体的处理函数,其实现如下:

743b7f0a-2341-11ed-ba43-dac502259ad0.png

第7行:调用read函数进行数据的读取

第8行:调用具体指令的回调信息

demux_ipc_socket_call的函数非常简单,实现如下:

74482b88-2341-11ed-ba43-dac502259ad0.png

前面我们在liblevelip.so库中调用socket()函数的时候,发送的消息类型为IPC_SOCKET,所以在此处我们进一步分析ipc_socket()这个函数。

它的具体实现如下:

7455531c-2341-11ed-ba43-dac502259ad0.png

我们重点是关注第7行的_socket函数,该函数就是tcp协议栈的核心接口之一了,它是整个tcp协议栈的真正入口,我们以后再来专门分析这个接口。然后第9行,ipc_write函数负责把tcp协议栈的处理结果返回给liblevelip.so库,代码较为简单,此处不再分析。

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Curl
    +关注

    关注

    0

    文章

    15

    浏览量

    8161
  • 编译
    +关注

    关注

    0

    文章

    653

    浏览量

    32803
  • 网络程序
    +关注

    关注

    0

    文章

    3

    浏览量

    901

原文标题:Linux系统中间件的巧妙实现--以用户空间的tcp协议栈为例

文章出处:【微信号:zhuyandz,微信公众号:FPGA之家】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Tiny4412下CURL安装与使用

    CURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称CURL为下载
    的头像 发表于 05-31 17:20 1378次阅读
    Tiny4412下<b class='flag-5'>CURL</b>安装与使用

    使用curl+wget抓取网页方法

    打包下载地址:Windows平台下的wget和curl工具包Windows平台下,curl下载解压后,直接是curl.exe格式,拷贝到系统命令目录下 C:\Windows\Syst
    发表于 02-25 09:54

    研发课堂丨手把手教你如何操作i.MX6UL对curl软件的支持

    本帖最后由 forlinx 于 2020-4-16 15:35 编辑 前言curl是一个开源项目,名字的含义是客户端(client)的URL工具的意思。主要的产品是curl(命令行工具
    发表于 04-16 15:35

    操作步骤:添加i.MX6UL对curl软件

    /libcurl目录下),指令执行后会配置编译器: 5. 使用交叉编译工具链编译: 6. 执行如下命令编译安装到步骤3中指定的位置。注意:本步骤会有多出错误提示,忽略即可。 7. 打开步骤3中的位置,此时目录
    发表于 04-16 15:31

    【ELF 1开发板试用】5.移植 curl 工具进行网络调试

    【ELF 1开发板试用】5.移植 curl 工具进行网络调试 连接互联网 动态 首先将开发板用网线与路由器LAN口相连接,可以执行动态获取 IP ,执行 udhcpc -i eth0 这个命令会将
    发表于 11-28 19:39

    Curl 下载v7.17.1

    Curl支持协定众多的文件传输程序,可下载FTP,HTT,HTTP,GOPHE,TELNET,DICT,FILE,LDAP的文件,还可以上传HTTP POST,HTTP PUT,FTP uploading,HTTP form based upload,proxies,cookies 点击查看:
    发表于 12-04 13:09 12次下载

    curl文件下载工具的使用方法

    curl文件下载工具的使用方法 curl是一个利用URL语法在命令行方
    发表于 12-04 13:03 1732次阅读

    如何通过STM32的串口实现简易脱机编程器

    如何通过STM32的串口实现简易脱机编程器如何通过STM32的串口实现简易脱机编程器如何通过STM32的串口实现
    发表于 04-25 09:38 60次下载

    浅谈cURL 和wget相似之处及优势对比

    cURL与wget:你应该选用哪一个? wget 和 cURL 都可以下载内容,它们都被设计成可脚本化,都可以写进脚本中,但是wget 简单直接,可享受超凡的下载速度,cURL是一个多功能工具
    的头像 发表于 02-13 13:04 4473次阅读

    simple curl操作类

    ./oschina_soft/curl.zip
    发表于 05-30 11:02 1次下载
    simple <b class='flag-5'>curl</b>操作类

    cURL的使用方法

    curl 是常用的开源命令行工具,用来请求 Web 服务器。它的名字就是客户端(client)的 URL 工具的意思。它的功能非常强大,命令行参数多达几十种。它支持包括 FTP、HTTP、HTTPS、FTP、SCP,SFTP 数
    的头像 发表于 08-05 11:54 4843次阅读

    windows下C语言使用curl库访问HTTP下载文件

    cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称cURL为下载
    的头像 发表于 09-17 15:57 2035次阅读
    windows下C语言使用<b class='flag-5'>curl</b>库访问HTTP下载文件

    Linux系统如何安装curl

    `curl`是用于在本地计算机与远程服务器之间传输数据的命令行工具。使用curl时您可以使用HTTP,HTTPS, SCP , SFTP和FTP等协议下载或上传数据。
    的头像 发表于 12-06 16:53 1.7w次阅读

    curl命令接入onenet简单使用

    注意的是,格式一定得正确无误。那么就接下来进入正题吧。先简单介绍下什么是curl吧: curl是利用url语法在命令行方式下工作的开源文件传输工具(来自百度百科)。curl是一种简单有
    发表于 10-20 17:11 0次下载
    <b class='flag-5'>curl</b>命令接入onenet简单使用

    linux开发板如何编译curl

    准备开发环境 确保你的开发板已经安装了编译 curl 所需的工具,包括 gcc 、 make 和可能的其他库。如果开发板是交叉编译环境(即你在一个平台上编译用于另一个平台的程序),你还需要确保安装了适当的交叉编译工具链。 2.
    的头像 发表于 08-30 15:33 480次阅读