资讯详情

linux 应用编程(持续更新)

在嵌入式 Linux 在系统中,我们编写的应用程序通常需要与硬件设备交互 Tips:本文将使用正点原子 ALPHA/Mini I.MX6U 开发板开发板出厂系统测试

开发板 电脑 虚拟机互传数据

我看的是正点原子的视频。网络环境建设很好 互传我看的是正点 “【正点原子】I.MX6U网络环境TFTP&NFS搭建手册V1.3.1” 我一般是用nfs共享文件夹的方法易于使用,无需记住指令

应用编程

应用层控制硬件的两种方式

设备文件是各种硬件设备向应用层提供的界面,包括字符设备文件和块设备文件 设备文件通常是/dev/目录下,我们也将/dev 目录下的文件称为设备节点。 设备节点不是控制硬件设备的唯一点来控制硬件设备== sysfs 文件系统==对硬件设备进行操控

sysfs 文件系统

sysfs 是一个基于内存的文件系统,同 devfs、proc 同样的文件系统,称为虚拟文件系统 以文件的形式向应用层提供内核信息 它可以产生包含所有系统硬件层次的视图。管理系统设备

sysfs 文件系统将连接到系统上的设备和总线组织成分级文件,显示设备驱动模型中各组件的层次关系。 sysfs 可以显式地描述核对象、对象属性和对象之间的关系,提供导出核对象的机制 (kernel object,例如,硬件设备的数据和属性以文件目录结构的形式访问用户空间。 在这里插入图片描述

sysfs 文件系统挂载/sys 目录下 是 sysfs 文件系统中的目录包括 block、bus、class、dev、devices、firmware、fs、kernel、modules、power 等等,每个目录下有许多文件或子目录 系统中的所有设备(对象)都将在/sys/devices 是的 sysfs 文件系统中最重要的目录结构 /sys/bus、/sys/class、/sys/dev 将设备组织存储在这些目录中,并以挂载的总线类型、功能分类和设备号的形式存储设备这些目录下的文件链接到/sys/devices 中。

设备的数据和属性将导出到用户空间,并以文件的形式为用户空间提供访问支持。这些文件可以称为属性文件

应用层通常可以通过两种方式控制底层硬件: ? /dev/设备文件(设备节点)目录; ? /sys/目录下设备属性文件。 有些设备只能通过设备节点进行操控,而有些设备只能通过 sysfs 控制方法;当然,这与设备驱动的具体实现有关 将使用简单的设备sysfs,例如LED、GPIO 然而,设备节点通常用于一些复杂的设备,如 LCD 等,触摸屏,摄像头等。

标准接口和非标准接口

Linux 为了尽量降低驱动开发者的难度和界面标准化,设备驱动框架的概念出现在内核中 对各种常见设备进行分类,如 LED 类设备,输入类设备,FrameBuffer 类设备、video 类设备、PWM 设备等,并为各类设备设计了一套成熟、标准、典型的驱动实现框架,称为设备驱动框架 设备驱动框架为驱动开发和应用层提供了统一的界面规范

驱动工程师编写 LED 驱动时,使用 LED 开发自己的驱动框架 LED 驱动程序的优点是可以为上层应用层提供统一标准化的接口,降低驱动开发工程师的难度。

由于计算机系统可以连接和使用的外围设备太多,因此不可能准确地将每个外围设备分类为某一设备类型。通常,这些不能分类的外围设备被称为杂项设备。它是一接口

LED应用编程

其实现使用 sysfs 方式控制 进入到/sys/class/leds 目录下。/sys/class/leds 所有的目录都存储在目录下。 LED 类设备

sys-led 文件夹是底板上的用户 LED 设备文件夹 这里我们主要关注的是 brightness、max_brightness 以及 trigger 这三个文件都是 LED 设备属性文件: ? brightness:亮度。对于 PWM 控制的 LED 就亮度等级而言,存在亮度等级问题。不同的亮度等级对应 LED 亮度也不一样 但对于 GPIO控制(控制 GPIO 输出高低电平)的 LED 例如,通常没有亮度等级。 brightness 等于 0 表示 LED 灭 ? max_brightness:该属性文件只能读取,不能写,用于获取 LED 设备的最大亮度等级。 ? trigger:触发模式。常用的触发模式包括none(无触发),mmc0(当对 mmc0 当设备启动读写操作时, LED 会闪烁)、timer(LED 会有规律的 亮一灭,被定时器控制),heartbeat(心跳呼吸模式,LED 模仿人的心跳和呼吸。

可以自己动手使用。 echo 或 cat 测试和控制命令 LED 状态

echo timer > trigger //将 LED 设置为触发模式 timer echo none > trigger //将 LED 设置为触发模式 none echo 1 > brightness //点亮 LED echo 0 > brightness//熄灭 LED 

写完代码后 使用前设置交叉编译工具的环境 source 执行安装目录environment-setup-cortexa7hf-neon-poky-linux-gnueabi 脚本文件

source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi 

然后编译

${ 
        CC} -o LED led.c 

这个LED交叉编译后arm平台可以运行的程序。

GPIO应用编程

与 LED 设备一样,GPIO 也是通过 sysfs 控制方法,输入/sys/class/gpio 目录下 ? gpiochipX:当前 SoC 所包含的 GPIO 控制器。板在板上。 GPIO1、GPIO2、GPIO3、GPIO4、GPIO5.这里分别对应 gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128 这 5 个文件夹

进去后可以看到 我们主要关注这个目录 base、label、ngpio 这三个属性文件 :与 gpiochipX 中的 X 相同,表示该控制器所管理的这组 GPIO 引脚中最小的编号 :该组 GPIO 对应的标签,也就是名字。

:该控制器所管理的 GPIO 引脚的数量(所以引脚编号范围是:base ~ base+ngpio-1)。

对于给定的一个 GPIO 引脚,如何计算它在 sysfs 中对应的编号呢?其实非常简单,譬如给定一个 GPIO引脚为 GPIO4_IO16,那它对应的编号是多少呢?首先我们要确定 GPIO4 对应于 gpiochip96,该组 GPIO 引脚的最小编号是 96(对应于 GPIO4_IO0),所以 GPIO4_IO16 对应的编号自然是 96 + 16 = 112

回到刚才的在/sys/class/gpio文件夹 ⚫ export:用于将指定编号的 GPIO 引脚导出。在使用 GPIO 引脚之前,需要将其导出,导出成功之后才能使用它。

echo 0 > export # 导出编号为 0 的 GPIO 引脚(对于 I.MX6UL/I.MX6ULL 来说,也就是GPIO1_IO0)

导出成功之后会发现在/sys/class/gpio 目录下生成了一个名为 gpio0 的文件夹(gpioX,X 表示对应的编号)

⚫ unexport:将导出的 GPIO 引脚删除。当使用完 GPIO 引脚之后,我们需要将导出的引脚删除

echo 0 > unexport # 删除导出的编号为 0 的 GPIO 引脚

删除成功之后,之前生成的 gpio0 文件夹就会消失!

以上就给大家介绍了/sys/class/gpio 目录下的所有文件和文件夹 控制 GPIO 引脚主要是通过 export 导出之后所生成的 gpioX(X 表示对应的编号)文件夹,在该文件夹目录下存在一些属性文件可用于控制 GPIO引脚的输入、输出以及输出的电平状态等。

Tips:需要注意的是,并不是所有 GPIO 引脚都可以成功导出,如果对应的 GPIO 已经在内核中被使用了,那便无法成功导出

进入到 gpio0 目录, 我们主要关心的文件是 active_low、direction、edge 以及 value 这四个属性文件 ⚫ direction:配置 GPIO 引脚为输入或输出模式。该文件可读、可写 读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式) ⚫ value:在 GPIO 配置为输出模式下,向 value 文件写入"0"控制 GPIO 引脚输出低电平 ⚫ active_low:这个属性文件用于控制极性,可读可写,默认情况下为 0,为高电平1。否则反向 ⚫ edge:控制中断的触发模式,需将其设置为输入模式: 非中断引脚:echo “none” > edge 上升沿触发:echo “rising” > edge 下降沿触发:echo “falling” > edge 边沿触发:echo “both” > edge 当引脚被配置为中断后可以使用 poll()函数监听引脚的电平状态变化

输入设备应用编程

常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。

input 子系统

驱动开发人员基于 input 子系统开发输入设备的驱动程序,input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。 基于 input 子系统注册成功的输入设备,都会在==/dev/input 目录下生成对应的设备节点(设备文件)== 节点名称通常为 eventX(X 表示一个数字编号 0、1、2、3 等)

①、应用程序打开/dev/input/event0 设备文件; ②、应用程序发起读操作(譬如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下); ③、当有数据可读时,应用程序会被唤醒,读操作获取到数据返回; ④、应用程序对读取到的数据进行解析。

其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据,该结构体定义在<linux/input.h>头文件中

struct input_event { 
        
 struct timeval time;
 __u16 type;
 __u16 code;
 __s32 value;
};

⚫ type:type 用于描述发生了哪一种类型的事件(对事件的分类)

/* * Event types */
#define EV_SYN 0x00 //同步类事件,用于同步事件
#define EV_KEY 0x01 //按键类事件
#define EV_REL 0x02 //相对位移类事件(譬如鼠标)
#define EV_ABS 0x03 //绝对位移类事件(譬如触摸屏)
#define EV_MSC 0x04 //其它杂类事件
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)

数据同步 提到了同步事件类型 EV_SYN,同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。 内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。

/* * Synchronization events. */
#define SYN_REPORT 0
#define SYN_CONFIG 1
#define SYN_MT_REPORT 2
#define SYN_DROPPED 3
#define SYN_MAX 0xf
#define SYN_CNT (SYN_MAX+1)

所以的输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0。

⚫ code:code 表示该类事件中的哪一个具体事件,譬如一个键盘上通常有很多按键,譬如字母 A、B、C、D 或者数字 1、2、3、4 等,而 code变量则告知应用程序是哪一个按键发生了输入事件 ⚫ value:内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。如果 value 等于 1,则表示 KEY_1 键按下;value 等于 0 表示 KEY_1 键松开。

触摸屏看文档吧,懒得写~

网络基础知识

网络通信概述、OSI 七层模型、IP 地址、TCP/IP 协议族、TCP 和 UDP 协议等等

网络通信概述

网络通信本质上是一种进程间通信,是位于网络中不同主机上的进程之间的通信,属于 IPC 的一种, 通常称为 socket IPC 网络数据的传输媒介有很多种,大体上分为有线传输(譬如双绞线网线、光纤等)和无线传输(譬如 WIFI、蓝牙、ZigBee、4G/5G/GPRS 等)

在内核层,提供了网卡驱动程序,可以驱动底层网卡硬件设备,同时向应用层提供 socket 接口。 在应用层,应用程序基于内核提供的 socket 接口进行应用编程,实现自己的网络应用程序。 除了 socket 接口之外,在应用层通常还会使用一些更为高级的编程接口,譬如 http、网络控件等,那么这些接口实际上是对 socket 接口的一种更高级别的封装。

网络互连模型:OSI 七层模型

物理层的作用是实现相邻计算机节点之间比特流的透明传送,==尽可能屏蔽掉具体传输介质和物理设备的差异。==使数据链路层不必考虑网络的具体传输介质是什么。

数据链路层的具体工作是接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;同样,也将来自上层的数据帧,拆装为位流形式的数据转发到物理层;并且,还负责处理接收端发回的确认帧的信息,以便提供可靠的数据传输。

数据链路层又分为 2 个子层:逻辑链路控制子层(LLC)和媒体访问控制子层(MAC)。MAC 子层的主要任务是解决共享型网络中多用户对信道竞争的问题,完成网络介质的访问控制;LLC 子层的主要任务是建立和维护网络连接,执行差错校验、流量控制和链路控制。

本层通过 IP 寻址来建立两个节点之间的连接,为源端发送的数据包选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。 该层包含的协议有:IP(Ipv4、Ipv6)、ICMP、IGMP 等。

传输层(Transport Layer)定义传输数据的协议端口号,以及端到端的流控和差错校验。传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括差错校验处理和流控等问题。 TCP、UDP 协议就工作在这一层,端口号既是这里的“端”。

会话层就是负责建立、管理和终止表示层实体之间的通信会话。 ==不同实体之间表示层的连接称为会话。==因此会话层的任务就是组织和协调两个会话进程之间的通信

表示层(Presentation Layer)提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。 数据压缩/解压缩和加密/解密(提供网络的安全性)也是表示层可提供的功能之一。

我们常见应用层的网络服务协议有:HTTP、FTP、TFTP、SMTP、SNMP、DNS、TELNET、HTTPS、POP3、DHCP。

TCP/IP 四层/五层模型

事实上,TCP/IP 模型是 OSI 模型的简化版本

IP 地址

Internet 依靠 TCP/IP 协议,在全球范围内实现不同硬件结构、不同操作系统、不同网络系统的主机之间的互联 只有合法的 IP 地址才能接入互联网中并且与其他主机进行网络通信,IP 地址是软件地址,不是硬件地址,硬件 MAC 地址是存储在网卡中的,应用于局域网中寻找目标主机。

根据 IP 地址中网络地址和主机地址两部分分别占多少位的不同,将 IP 地址划分为 5 类 以上就给大家介绍了这 5 类 IP 地址,其中在 A、B、C 三类地址中,各保留了一个区域作为私有地址: A 类地址:10.0.0.0~10.255.255.255。默认网络掩码为:255.0.0.0 A 类地址分配给规模特别大的网络使用。A 类地址用第一组数字表示网络地址,后面三组数字作为连接于网络上的主机对应的地址。分配给具有大量主机而局域网络个数较少的大型网络,譬如 IBM 公司的网络。

B 类地址:172.16.0.0~172.31.255.255。默认网络掩码为:255.255.0.0 B 类地址分配给一般的中型网络。B 类地址用第一、二组数字表示网络地址,后面两组数字代表网络上的主机地址。

C 类地址:192.168.0.0~192.168.255.255。默认网络掩码为:255.255.255.0 C 类地址分配给小型网络,如一般的局域网和校园网

A 类地址的第一组数字为 1~126。 B 类地址的第一组数字为 128~191。 C 类地址的第一组数字为 192~223。

1.直接广播(Direct Broadcast Address):向某个网络上所有的主机发送报文。 A、B、C 三类地址的广播地址结构如下: ⚫ A 类地址的广播地址为: XXX.255.255.255 (XXX 为 A 类地址中网络地址对应的取值范围,譬如:120.255.255.255)。 ⚫ B 类地址的广播地址为:XXX.XXX.255.255(XXX 为 B 类地址中网络地址的取值范围,譬如139.22.255.255)。 ⚫ C 类地址的广播地址为:XXX.XXX.XXX.255(XXX 为 C 类地址中网络地址的取值范围,譬如203.120.16.255)。

2.受限广播地址 受限广播地址是在本网络内部进行广播的一种广播地址,TCP/IP 规定,32 比特全为“1”的 IP 地址用于本网络内的广播,也就是 255.255.255.255。

3.多播地址 多播地址用在一对多的通信中,即一个发送者,多个接收者,不论接受者数量的多少,发送者只发送一次数据包。

4.环回地址 环回地址(Loopback Address)是用于网络软件测试以及本机进程之间通信的特殊地址。把 A 类地址中的 127.XXX.XXX.XXX 的所有地址都称为环回地址 主要用来测试网络协议是否工作正常的作用。比如在电脑中使用 ping 命令去 ping 127.1.1.1 就可以测试本地 TCP/IP 协议是否正常。

5)0.0.0.0 地址 IP 地址 32bit 全为 0 的地址(也就是 0.0.0.0)表示本网络上的本主机,只能用作源地址。 监听 0.0.0.0 的端口,就是监听本机中所有 IP 的端口。

网络标识 = IP 地址 & 子网掩码 2 个 IP 地址的网络标识相同,那么它们就处于同一网络。譬如 192.168.1.50 和 192.168.1.100,这 2 个都是 C 类地址,对应的子网掩码为 255.255.255.0,很明显,这两个 IP 地址与子网掩码进行按位与操作时得到的结果(网络标识)是一样的,所以它们处于同一网络。

TCP/IP 协议

TCP/IP 协议它其实是一个协议族,包含了众多的协议,譬如应用层协议 HTTP、FTP、MQTT…以及传输层协议 TCP、UDP 等这些都属于 TCP/IP 协议 所以,我们一般说 TCP/IP 协议,它不是指某一个具体的网络协议,而是一个协议族

HTTP 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网数据通信的基础。HTTP 的应用最为广泛,譬如大家经常会打开网页浏览器查询资料,通过浏览器便可开启 HTTP 通信。 HTTP 协议工作于客户端(用户)、服务器端(网站)模式下,浏览器作为 HTTP 客户端通过 URL 向HTTP 服务端即 WEB 服务器发送请求。Web 服务器根据接收到的请求后,向客户端发送响应信息。借助这种浏览器和服务器之间的 HTTP 通信,我们能够足不出户地获取网络中的各种信息。

FTP 协议的英文全称为 File Transfer Protocol,简称为 FTP,它是一种文件传输协议,从一个主机向一个主机传输文件的协议 FTP 协议同样也是基于客户端-服务器模式,在客户端和服务器之间进行文件传输

TCP 协议

关于 TCP 协议我们需要理解的重点如下: ①、TCP 协议工作在传输层,对上服务 socket 接口,对下调用 IP 层; ②、 TCP 是一种面向连接的传输协议,通信之前必须通过三次握手与客户端建立连接关系后才可通信; ③、TCP 协议提供可靠传输,不怕丢包、乱序。

①、TCP 协议采用发送应答机制,即发送端发送的每个 TCP 报文段都必须得到接收方的应答,才能认为这个 TCP 报文段传输成功。 ②、TCP 协议采用超时重传机制,发送端在发送出一个 TCP 报文段之后启动定时器,如果在定时时间内未收到应答,它将重新发送该报文段。 ③、由于 TCP 报文段最终是以 IP 数据报发送的,而 IP 数据报到达接收端可能乱序、重复、所以 TCP协议还会将接收到的 TCP 报文段重排、整理、再交付给应用层。

⚫面向连接的 TCP 是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一个 TCP连接,否则将无法发送数据,通过三次握手建立连接 ⚫确认与重传 当数据从主机 A 发送到主机 B 时,主机 B 会返回给主机 A 一个确认应答 在一定的时间内如果没有收到确认应答,发送端就可以认为数据已经丢失,并进行重发。由此,即使产生了丢失,仍然可以保证数据能够到达对端,实现可靠传输。 ⚫全双工通信 TCP 连接一旦建立,就可以在连接上进行双向的通信。 ⚫基于字节流而非报文 将数据按字节大小进行编号,接收端通过 ACK 来确认收到的数据编号 ⚫流量控制(滑动窗口协议) TCP 流量控制主要是针对接收端的处理速度不如发送端发送速度快的问题,消除发送方使接收方缓存溢出的可能性。 滑动窗口是接受数据端使用的窗口大小,用来告诉发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。 对所有数据帧按顺序赋予编号,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收。 ⚫差错控制 TCP 协议除了确认应答与重传机制外,TCP 协议也会采用校验和的方式来检验数据的有效性,主机在接收数据的时候,会将重复的报文丢弃,将乱序的报文重组,发现某段报文丢失了会请求发送方进行重发,因此在 TCP 往上层协议递交的数据是顺序的、无差错的完整数据。 ⚫拥塞控制 如果网络上的负载(发送到网络上的分组数)大于网络上的容量(网络同时能处理的分组数),就可能引起拥塞 判断网络拥塞的两个因素:延时和吞吐量。 拥塞控制机制是:开环(预防)和闭环(消除)。 TCP 拥塞控制的几种方法:慢启动,拥塞避免,快重传和快恢复。

当数据由上层发送到传输层时,数据会被封装为 TCP 数据段,我们将其称为 TCP 报文(或 TCP 报文段),TCP 报文由 TCP 首部+数据区域组成,一般 TCP 首部通常为 20 个字节大小 四次挥手即终止 TCP 连接,就是指断开一个 TCP 连接时,需要客户端和服务端总共发送 4 个包以确认连接的断开。在 socket 编程中,这一过程由客户端或服务端任一方执行 close 来触发。 由于 TCP 连接是全双工的,因此,每个方向都必须要单独进行关闭 当一方完成数据发送任务后,发送一个 FIN 来终止这一方向的连接,收到一个 FIN 只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个 TCP 连接上仍然能够发送数据,直到这一方向也发送了 FIN。

所以 TCP 协议传输数据的整个过程就如同下图所示: UDP 是 User Datagram Protocol 的简称,中文名是用户数据报协议,是一种无连接、不可靠的协议,同样它也是工作在传输层。 它只是简单地实现从一端主机到另一端主机的数据传输功能,这些数据通过 IP 层发送,在网络中传输,到达目标主机的顺序是无法预知的

UDP 协议的特点: ①、无连接、不可靠; ②、尽可能提供交付数据服务,出现差错直接丢弃,无反馈; ③、面向报文,发送方的 UDP 拿到上层数据直接添加个 UDP 首部,然后进行校验后就递交给 IP 层,而接收的一方在接收到 UDP 报文后简单进行校验,然后直接去除数据递交给上层应用; ④、速度快,因为 UDP 协议没有 TCP 协议的握手、确认、窗口、重传、拥塞控制等机制,UDP 是一个无状态的传输协议,所以它在传递数据时非常快,即使在网络拥塞的时候 UDP 也不会降低发送的数据。

它的实时性是非常好,常用于实时视频的传输,比如直播、网络电话等,因为即使是出现了数据丢失的情况,导致视频卡帧,这也不是什么大不了的事情

端口号的概念

一台主机通常只有一个 IP 地址,但主机上运行的网络进程却通常不止一个,譬如 Windows 电脑上运行着 QQ、微信、钉钉、网页浏览器等,这些进程都需要进行网络连接,它们都可通过网络发送/接收数据。通常端口号来确定 端口号用来在一台主机中唯一标识一个能上网(能够进行网络通信)的进程,端口号的取值范围为 0~65535。

socket 编程基础

socket是内核向应用层提供的一套网络编程接口,用户基于 socket 接口可开发自己的网络相关应用程序。 进程间通信机制(socket IPC)。socket IPC 通常使用客户端<—>服务器这种模式完成通信,多个客户端可以同时连接到服务器中,与服务器之间完成数据交互。

socket 编程接口介绍

包含两个头文件: #include <sys/types.h> /* See NOTES */ #include <sys/socket.h>

int socket(int domain, int type, int protocol); socket()函数类似于 open()函数,它用于创建一个网络通信端点(打开一个网络通信),如果成功则返回一个网络文件描述符,通常把这个文件描述符称为 socket 描述符(socket descriptor)

该函数包括 3 个参数,如下所示: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind()函数用于将一个 IP 地址或端口号与一个套接字进行绑定(将套接字与地址进行关联)。

调用 bind()函数将参数 sockfd 指定的套接字与一个地址 addr 进行绑定,成功返回 0,失败情况下返回-1

struct sockaddr { 
        
sa_family_t sa_family;
char sa_data[14];
}

struct sockaddr_in { 
        
sa_family_t sin_family; /* 协议族 */
in_port_t sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP 地址 */
unsigned char sin_zero[8];
};

一般我们在使用的时候都会使用 struct sockaddr_in 结构体。指向 sockaddr_in 的结构体的指针也可以指向 sockadd 的结构体,并代替它

使用示例

struct sockaddr_in socket_addr;
memset(&socket_addr, 0x0, sizeof(socket_addr)); //清零

int socket_fd = socket(AF_INET, SOCK_STREAM, 0);//打开套接字
if (0 > socket_fd) { 
        
perror("socket error");
exit(-1);
}

//填充变量
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket_addr.sin_port = htons(5555);
//将地址与套接字进行关联、绑定
bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(socket_addr));

close(socket_fd); //关闭套接字

listen()函数只能在服务器进程中使用,让服务器进程进入监听状态,等待客户端的连接请求 一般在 bind()函数之后调用,在 accept()函数之前调用

int listen(int sockfd, int backlog); 无法在一个已经连接的套接字执行 listen()。

参数 backlog 用来描述 sockfd 的等待连接队列能够达到的最大值。内核会在自己的进程空间里维护一个队列,这些连接请求就会被放入一个队列中,服务器进程会按照先来后到的顺序去处理这些连接请求 当一个客户端的连接请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,本次请求会被丢弃不作处理。

服务器调用 listen()函数之后,就会进入到监听状态,等待客户端的连接请求,使用 accept()函数获取客户端的连接请求并建立连接。 如果调用 accept()函数时,并没有客户端请求连接(等待连接队列中也没有等待连接的请求),此时 accept()会进入阻塞状态,直到有客户端连接请求到达为止。

当有客户端连接请求到达时,accept()函数与远程客户端之间建立连接,accept()函数返回一个新的套接字。这个套接字代表了服务器与客户端的一个连接。 ==参数 addr 是一个传出参数,参数 addr 用来返回已连接的客户端的 IP 地址与端口号等这些信息。==可以把 arrd 和 addrlen 均置为空指针 NULL。

为了能够正常让客户端能正常连接到服务器,服务器必须遵循以下处理流程: ①、调用 socket()函数打开套接字; ②、调用 bind()函数将套接字与一个端口号以及 IP 地址进行绑定; ③、调用 listen()函数让服务器进程进入监听状态,监听客户端的连接请求; ④、调用 accept()函数处理到来的连接请求。

下面这个是客户端的函数 客户端调用 connect()函数将套接字 sockfd 与远程服务器进行连接,参数 addr 指定了待连接的服务器的 IP 地址以及端口号等信息

客户端通过 connect()函数请求与服务器建立连接,对于 TCP 连接来说,调用该函数将发生 TCP 连接的握手过程,并最终建立一个 TCP 连接, 而对于 UDP 协议来说,调用这个函数只是在 sockfd 中记录服务器IP 地址与端口号,而不发送任何数据。

一旦客户端与服务器建立好连接之后,我们就可以通过套接字描述符来收发数据了 可以调用 read()或 recv()函数读取网络数据,调用 write()或 send()函数发送

IP 地址格式转换函数

对于人来说,我们更容易阅读的是点分十进制的 IP 地址,譬如 192.168.1.110、192.168.1.50,这其实是一种字符串的形式, 但是计算机所需要理解的是二进制形式的 IP 地址,所以我们就需要在点分十进制字符串和二进制地址之间进行转换。

点分十进制字符串和二进制地址之间的转换函数主要有:inet_aton、inet_addr、inet_ntoa、inet_ntop、inet_pton 这 五 个 , 在 我 们 的 应 用 程 序 中 使 用 它 们 需 要 包 含 头 文 件 <sys/socket.h> 、 <arpa/inet.h> 以 及<netinet/in.h>。

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#define IPV4_ADDR "192.168.1.222"
int main(void)
{ 
        
struct in_addr addr;
inet_pton(AF_INET, IPV4_ADDR, &addr);
printf("ip addr: 0x%x\n", addr.s_addr);
exit(0);
}

int main(void)
{ 
        
struct in_addr addr;
char buf[20] = { 
        0};
addr.s_addr = 0xde01a8c0;
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
printf("ip addr: %s\n", buf);
exit(0);
}

socket 编程实战

编写服务器应用程序的流程如下: ①、调用 socket()函数打开套接字,得到套接字描述符; ②、调用 bind()函数将套接字与 IP 地址、端口号进行绑定; ③、调用 listen()函数让服务器进程进入监听状态; ④、调用 accept()函数获取客户端的连接请求并建立连接; ⑤、调用 read/recv、write/send 与客户端进行通信; ⑥、调用 close()关闭套接字。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT 8888 //端口号不能发生冲突,不常用的端口号通常大于 5000

int main(void)
{ 
        
struct sockaddr_in server_addr = { 
        0};
struct sockaddr_in client_addr = { 
        0};
char ip_str[20] = { 
        0};
int sockfd, connfd;
int addrlen = sizeof(client_addr);
char recvbuf[512];
int ret;
/* 打开套接字,得到套接字描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sockfd) { 
        
perror("socket error");
exit(EXIT_FAILURE);
}
/* 将套接字与指定端口号进行绑定 */
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (0 > ret) { 
        
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 使服务器进入监听状态 */
ret = listen(sockfd, 50);
if (0 > ret) { 
        
perror("listen error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 阻塞等待客户端连接 */
connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
if (0 > connfd) { 
        
perror("accept error");
close(sockfd);
exit(EXIT_FAILURE);
printf("有客户端接入...\n");
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
printf("客户端主机的 IP 地址: %s\n", ip_str);
printf("客户端进程的端口号: %d\n", client_addr.sin_port);
/* 接收客户端发送过来的数据 */
for ( ; ; ) { 
        
// 接收缓冲区清零
memset(recvbuf, 0x0, sizeof(recvbuf));
// 读数据
ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
if(0 >= ret) { 
        
perror("recv error");
close(connfd);
break;
}
// 将读取到的数据以字符串形式打印出来
printf("from client: %s\n", recvbuf);
// 如果读取到"exit"则关闭套接字退出程序
if (0 == strncmp("exit", recvbuf, 4

标签: hf520变送器特点hf520变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台