嵌入式Linux——文件类型
目录
普通文件
目录文件
目录文件的权限与管理
字符设备文件和块设备文件
符号链接文件
查看符号链接
删除符号链接
修改符号链接
管道文件
匿名管道(Anonymous Pipe)
匿名管道的特点:
使用示例:
命名管道(Named Pipe)
命名管道的特点:
使用示例:
套接字文件
套接字的基本概念
Unix 域套接字(Unix Domain Socket)
特点:
创建和使用 Unix 域套接字
示例代码:
网络套接字(Network Socket)
特点:
创建和使用网络套接字
示例代码:
Linux 下一切皆文件,文件作为Linux 系统设计思想的核心理念,在Linux 系统下显得尤为重要。 在 Windows 系统下,操作系统识别文件类型一般是通过文件名后缀来判断,譬如 C 语言头文件.h、C语言源文件.c、.txt 文本文件、压缩包文件.zip 等,在 Windows 操作系统下打开文件,首先会识别文件名后缀得到该文件的类型,然后再使用相应的调用相应的程序去打开它;譬如.c 文件,则会使用 C 代码编辑器去打开它;.zip 文件,则会使用解压软件去打开它。
但是在Linux 系统下,并不会通过文件后缀名来识别一个文件的类型,话虽如此,但并不是意味着大家可以随便给文件加后缀;文件名也好、后缀也好都是给“人”看的,虽然Linux 系统并不会通过后缀来识别文件,但是文件后缀也要规范、需要根据文件本身的功能属性来添加,譬如 C 源文件就以.c 为后缀、C 头文件就以.h 为后缀、shell 脚本文件就以.sh 为后缀、这是为了我们自己方便查看、浏览。
普通文件
普通文件就是分为文本文件和二进制文件:
文本文件就是由ASCII文本字符构成的,虽说本真上还是0和1组成,但是他们排列得到的时人类可读的文本文件。
二进制文件是正儿八经的0和1,他们是计算机用来识别处理的文件。
关于文件,可以使用stat来查看一个文件的文件类型:
[root@VM-12-17-centos learn_linux]# stat open.txt File: ‘open.txt’ Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: fd01h/64769d Inode: 528367 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2024-11-20 19:17:48.998832078 +0800 Modify: 2024-11-20 19:17:48.752830223 +0800 Change: 2024-11-20 19:17:48.752830223 +0800 Birth: -
这是笔者尝试的,你也可以试试看
目录文件
目录文件(Directory File)是文件系统中用于组织和管理其他文件的特殊类型文件。它本质上是一个包含指向其他文件或子目录的条目的文件。目录文件的作用类似于文件夹,用于存储文件路径信息,帮助操作系统快速定位文件的存储位置。目录文件并不直接存储数据,而是存储文件名和文件元数据(如文件位置、权限、大小等)的映射。
在 Linux 文件系统中,目录文件通常被视为一个特殊的文件,它是文件系统结构的基本组成部分之一。每个目录都可以包含若干个文件或子目录,操作系统通过目录文件来构建文件系统的层级结构。通过目录文件,用户和程序可以方便地对文件进行管理、存取和操作。
一个目录文件通常包含一个列表,其中每一项表示一个文件或子目录。每一项包含以下信息:
-
文件名:文件或子目录的名称。
-
文件的元数据:例如文件的 inode(索引节点)编号,用于标识文件在磁盘上的位置及其他信息(如权限、大小等)。
在传统的 UNIX 文件系统中,目录文件的结构可以简单地理解为一个包含若干条目(文件名与 inode 映射)的数组。每个条目记录一个文件或子目录的名称及其对应的 inode。
对于目录文件,操作系统通常提供一系列操作,包括:
-
创建目录:通过
mkdir
命令,操作系统会在指定位置创建一个新的目录文件。该目录文件本身不包含数据,而是为空目录,等待被填充文件或子目录。 -
删除目录:通过
rmdir
命令,操作系统可以删除空目录。如果目录不为空,则需要先删除其中的文件或子目录,或者使用rm -r
命令递归删除。 -
打开和关闭目录:在程序中,目录文件可以像普通文件一样被打开和关闭。通过
opendir()
和closedir()
函数,程序可以打开和关闭目录,获取目录中的文件列表。 -
读取目录内容:可以使用
readdir()
函数读取目录中的每一项。这些项通常包含文件名和文件对应的 inode 编号,通过 inode 可以访问文件的更多信息。
目录文件的具体实现依赖于文件系统的类型,不同的文件系统采用不同的方式来存储和管理目录内容。在大多数现代文件系统(如 ext4、NTFS)中,目录文件通常是一个特殊的文件类型,内部存储的是一个结构化的条目列表,每个条目指向一个文件的 inode 或子目录。
以 ext4 文件系统为例,目录文件由多个目录项(directory entry)组成,每个目录项包括文件名、文件的 inode 号和其他元数据。目录项按顺序排列,操作系统通过遍历这些目录项来查找文件或子目录。
每个文件在文件系统中都有一个与之关联的 inode(索引节点),它包含文件的元数据(如权限、大小、创建时间等),以及文件的数据块位置。然而,目录文件本身并不直接包含文件的数据,而是通过 inode 来引用其他文件或子目录。目录文件实际上是 inode 的一个映射表,操作系统通过 inode 查找文件的存储位置及其他信息。
目录文件与 inode 的关系可以通过一个简单的例子来理解:当用户通过命令行或图形界面创建一个新文件时,操作系统首先会为该文件分配一个 inode,并将该 inode 的编号以及文件名存储在父目录的目录文件中。此后,操作系统通过该目录文件与 inode 之间的映射关系来访问文件的实际数据。
目录文件提供了文件系统的层次结构,用户通过目录文件可以对文件进行分类管理。目录文件通常用于:
-
组织文件:文件系统通过层级结构组织文件,每个目录都可以包含多个子文件和子目录。通过这种方式,文件系统可以有效地管理大量的文件,并方便用户进行文件分类和访问。
-
查找文件:操作系统可以根据目录文件的条目快速定位到文件的 inode,从而访问文件的实际数据。这种结构大大提高了文件查找的效率,尤其是在包含大量文件的大型文件系统中。
-
支持路径解析:目录文件支持路径解析功能,例如
/home/user/Documents/file.txt
,每个目录文件表示路径中的一部分,操作系统通过解析路径中的各个目录,最终定位到文件。
目录文件的权限与管理
与普通文件一样,目录文件也有权限控制,决定了哪些用户可以访问、修改或删除目录。目录文件的权限通常包括:
-
读取权限(r):允许用户列出目录中的文件。
-
写入权限(w):允许用户向目录中添加、删除或重命名文件。
-
执行权限(x):允许用户进入目录进行操作。
如果目录没有执行权限,用户将无法访问该目录中的文件,尽管用户可能具有读取或写入权限。同样,目录的所有者或管理员可以修改目录文件的权限,以控制哪些用户可以访问或修改目录。
字符设备文件和块设备文件
字符设备文件和块设备文件是 Linux 操作系统中两种基本的设备文件类型。设备文件是指操作系统中用于表示外部设备的特殊文件,它们与普通文件有本质的区别,主要用于与硬件设备进行交互。字符设备文件和块设备文件分别处理不同类型的设备交互,理解它们的区别与特点对于掌握 Linux 系统的底层操作至关重要。
字符设备文件是用于表示字符设备的特殊文件。字符设备是指那些一次只能处理一个字符流的设备,数据的读写是按字符为单位进行的。字符设备文件提供了一种简单的接口,通过它可以与字符设备进行数据交换。常见的字符设备包括串口设备、键盘、鼠标、打印机等。
在 Linux 系统中,字符设备文件通常位于 /dev
目录下,文件的命名方式一般是设备的名称或标识符,如 /dev/ttyS0
(表示串口设备),/dev/console
(表示控制台设备)。这些设备文件是通过设备驱动程序与内核直接进行交互的,内核通过驱动程序将用户程序发出的字符流数据传递给硬件设备,或者将硬件设备返回的数据传递给用户程序。
字符设备文件的核心特性之一是它们采用的是“字符流”的方式进行数据传输,数据的读取和写入是顺序进行的,没有固定的“块”结构。对于字符设备,内核通常提供了基本的读、写、打开和关闭等操作。字符设备的驱动程序会处理这些操作并实现对设备的具体控制。
在操作系统内部,字符设备文件与应用程序之间的交互是通过系统调用实现的。比如,应用程序可以通过 read()
和 write()
系统调用来与字符设备进行数据传输。这些系统调用会触发内核中字符设备驱动程序的相应操作,从而实现与硬件的交互。
字符设备的驱动程序通常需要实现一些基本的操作函数,如打开设备、关闭设备、读数据、写数据等。这些操作函数通常会被注册到 Linux 内核的设备模型中,使得内核能够在用户程序发出请求时,自动调用相应的驱动程序函数。
块设备文件是用于表示块设备的特殊文件。块设备是指那些一次可以读取或写入一个固定大小数据块的设备,数据的传输通常是按块(block)为单位进行的。与字符设备相比,块设备能够更高效地处理大量数据,且数据传输是按固定的块结构进行的,通常可以随机访问。常见的块设备包括硬盘、固态硬盘(SSD)、CD-ROM 驱动器等。
在 Linux 系统中,块设备文件通常也位于 /dev
目录下,文件的命名方式一般是设备的名称或标识符,如 /dev/sda
(表示第一个硬盘),/dev/sdb
(表示第二个硬盘)。与字符设备文件不同,块设备文件代表的设备支持更复杂的操作,包括分区、格式化、挂载等。
块设备文件的特性之一是它们采用的是“块”结构的数据传输方式。每个块的大小通常是 512 字节或 4 KB,这取决于具体的硬件设备。在块设备的操作过程中,数据按块的粒度进行读取和写入,内核和硬件之间的交互通常是批量处理的,而不是逐字符传输。这使得块设备在处理大容量数据时效率更高,特别是在存储设备如硬盘、固态硬盘等中,块设备可以更快速地进行顺序和随机访问。
块设备的驱动程序通常需要处理对设备的读写请求。每当用户程序请求读取数据时,内核会将请求转发给块设备的驱动程序,驱动程序根据请求的块号读取相应的数据并将其返回给应用程序。类似地,写操作也会将数据写入指定的块。块设备驱动程序还可能需要处理数据的缓存、同步、磁盘调度等问题。
字符设备和块设备的区别主要体现在数据传输方式、访问粒度、设备类型和性能等方面。
-
数据传输方式:字符设备按字符流传输数据,一次只处理一个字符或字节;块设备按块(block)传输数据,通常一次处理较大的数据块(通常是 512 字节或 4 KB),支持更高效的顺序和随机访问。
-
访问粒度:字符设备的访问粒度较小,通常为一个字符或字节;块设备的访问粒度较大,通常为一个数据块,适合大规模数据的读写。
-
设备类型:字符设备适用于那些数据流量较小且不需要高效随机访问的设备,如键盘、鼠标、串口等;块设备则通常用于存储设备,适合大容量数据的存储与管理,如硬盘、固态硬盘等。
-
性能:由于块设备采用块结构传输数据,能够在访问大数据时提供更高的性能,尤其是在顺序访问模式下。字符设备则更适合流式传输,性能相对较低。
-
驱动程序:字符设备的驱动程序通常实现的是基于字符流的操作,功能较为简单,主要处理设备的读写操作;而块设备的驱动程序则需要处理更多复杂的操作,如磁盘调度、缓存管理、分区管理等。
字符设备主要适用于需要逐字符或逐字节交互的场合。例如,键盘、鼠标、串口设备等,用户通过这些设备与计算机进行交互,通常涉及较小的数据量和流式数据传输。字符设备的接口简单,驱动程序也相对简单,因此适用于那些数据传输要求不高的设备。
块设备则更适合存储设备,如硬盘、固态硬盘、光盘等。这些设备通常需要处理大量数据,并且数据的读写操作是按块进行的。块设备能够提供较高的性能,尤其是在需要进行顺序读写的场景下,如大规模文件存储、数据库操作等。块设备的管理复杂性较高,通常需要实现更为复杂的调度算法和缓存策略,但在处理大数据时具有显著的优势。
符号链接文件
符号链接文件(Symbolic Link,简称 symlink),也叫软链接,是一种特殊的文件类型,它包含指向其他文件或目录的路径。与硬链接不同,符号链接并不直接指向文件的 inode,而是存储一个文本字符串,这个字符串是另一个文件或目录的路径。符号链接为用户和程序提供了一种间接访问文件的方式,允许通过一个链接访问目标文件或目录。
符号链接的最重要特性是它可以跨越文件系统的边界,允许一个文件或目录在不同位置之间进行快捷的引用。符号链接类似于 Windows 操作系统中的快捷方式或 MacOS 中的 alias,它们允许用户方便地访问文件或目录,而不需要在文件系统中复制文件本身。
符号链接通过保存一个指向目标文件或目录路径的文本字符串来实现间接访问。当访问符号链接时,操作系统会自动将符号链接解析为其目标路径,继而访问目标文件或目录。这种方式与硬链接不同,硬链接是通过创建一个指向相同 inode 的引用来实现的,而符号链接则是通过路径字符串来实现引用。
符号链接文件本身是一个独立的文件,它存储的路径可以指向任何文件或目录,不论该目标文件或目录是否在同一文件系统中。符号链接的创建、删除、修改与普通文件类似,但它本身只保存一个文本路径,而不是文件的数据。
在 Linux 系统中,创建符号链接通常使用 ln
命令的 -s
选项。其基本语法如下:
ln -s <目标文件> <符号链接名称>
例如,创建一个指向文件 /home/user/example.txt
的符号链接 example_link.txt
:
ln -s /home/user/example.txt example_link.txt
这个命令会在当前目录下创建一个符号链接 example_link.txt
,它指向 /home/user/example.txt
。如果访问符号链接 example_link.txt
,系统会自动解析它,并读取 /home/user/example.txt
中的内容。
如果目标是一个目录,也可以使用符号链接。例如,创建一个指向 /home/user/data
目录的符号链接:
ln -s /home/user/data data_link
-
路径引用:符号链接保存的是目标文件或目录的路径,而不是文件的实际内容。这意味着,如果目标文件被移动或删除,符号链接就会失效,变成“悬挂的”链接。
-
可跨文件系统:符号链接可以跨越不同的文件系统,指向外部设备或挂载点中的文件,而硬链接则只能在同一文件系统内创建。
-
可以指向目录:符号链接不仅可以指向文件,还可以指向目录。通过符号链接,用户可以在文件系统中创建便捷的目录访问路径。
-
符号链接本身是一个文件:符号链接是一个独立的文件,它拥有自己的 inode,且其内容是目标路径的文本字符串。
-
软链接与硬链接的区别:符号链接与硬链接的主要区别在于,硬链接是指向文件的 inode,而符号链接是指向文件路径的文本字符串。硬链接在目标文件被删除后仍然有效,因为它们指向相同的 inode,而符号链接则会失效。
查看符号链接
可以使用 ls -l
命令查看符号链接文件的详细信息。符号链接的文件类型通常以 l
开头,后面跟着符号链接的路径。示例:
ls -l example_link.txt
输出可能如下:
lrwxrwxrwx 1 user user 26 Apr 1 10:30 example_link.txt -> /home/user/example.txt
这表示 example_link.txt
是一个符号链接,指向 /home/user/example.txt
。
删除符号链接
删除符号链接可以使用 rm
命令,和删除普通文件一样:
rm example_link.txt
需要注意的是,删除符号链接只会删除链接文件本身,而不会删除目标文件。如果目标文件被删除或移动,符号链接仍然会存在,但指向的路径不再有效。
修改符号链接
修改符号链接通常意味着更新它所指向的目标文件或目录的路径。这可以通过先删除旧的符号链接,然后创建一个新的符号链接来实现:
rm example_link.txt ln -s /home/user/new_example.txt example_link.txt
符号链接在多种场景下都有应用,尤其在文件系统管理、软件安装与配置、文件访问等方面具有重要作用。
-
简化路径访问:符号链接可以简化对复杂路径的访问。例如,可以创建一个符号链接,将某个深层目录的路径简化为更容易记住的路径。
-
共享文件和资源:符号链接可以用于不同位置共享同一文件或目录。比如,多个程序可能需要访问同一个配置文件,通过符号链接可以避免复制文件并保持文件的一致性。
-
版本控制:在开发和部署过程中,符号链接可以用来指向不同版本的文件或目录。例如,可以创建一个指向最新版本的符号链接,在版本更新时只需要更新符号链接指向的目标路径,而不需要修改使用该符号链接的所有程序。
-
软件安装与配置:许多软件包在安装时会创建符号链接,以便在不同版本的文件之间切换,或者将配置文件链接到系统的标准配置位置。例如,在某些 Linux 发行版中,软件包可能会在
/usr/bin
中创建指向实际程序的符号链接。 -
跨文件系统引用:符号链接可以跨越不同的文件系统,将一个文件系统中的资源链接到另一个文件系统。例如,在多个磁盘分区中共享文件数据时,可以通过符号链接实现跨分区的文件引用。
尽管符号链接在许多情况下非常有用,但它们也有一些限制和潜在的风险:
-
悬挂链接:如果目标文件被删除或移动,符号链接将变得无效,形成悬挂链接(dangling link)。此时,访问符号链接会导致错误,程序可能无法找到目标文件。
-
循环链接:符号链接可以创建指向自身的链接,或者形成一个循环引用。这种情况下,如果程序不小心跟踪符号链接,可能会导致无限循环和栈溢出。
-
权限问题:符号链接本身具有权限设置,但在访问符号链接的目标文件时,最终的访问权限取决于目标文件的权限。如果符号链接的目标没有适当的权限,则即使符号链接本身是可访问的,访问目标文件时也会失败。
管道文件
管道文件(Pipe File)是 Linux 文件系统中的一种特殊文件类型,主要用于在不同进程之间进行通信。管道提供了一种进程间通信(IPC, Inter-Process Communication)的机制,允许一个进程的输出直接作为另一个进程的输入,进而实现数据的传递。管道文件有两种形式:匿名管道和命名管道。
管道的核心作用是允许数据在两个或多个进程之间传递,而无需将数据存储到中间文件中。它的工作原理类似于管道流体的流动:数据从一个进程流出,通过管道进入另一个进程。这种机制可以有效地实现进程间的协作,并且具有高效、低延迟的特点。
在 Linux 系统中,管道文件分为两类:匿名管道(Anonymous Pipe)和命名管道(Named Pipe)。这两者在使用方式、功能和作用上有所不同。
匿名管道(Anonymous Pipe)
匿名管道是最常见的一种管道,它在创建时没有名字,也无法在文件系统中看到,通常用于父子进程或兄弟进程之间的通信。匿名管道是由操作系统内核提供的进程间通信机制,通常通过以下两个系统调用来创建:
-
pipe()
:创建一个匿名管道,并返回一个文件描述符数组,数组的第一个元素是管道的读取端,第二个元素是管道的写入端。 -
read()
和write()
:进程通过这些系统调用从管道中读取数据或向管道中写入数据。
匿名管道的特点:
-
无名性:匿名管道并没有一个文件名,因此在文件系统中无法直接看到。它仅仅在内存中存在。
-
单向通信:管道是单向的,数据只能从写端流向读端。为了实现双向通信,必须创建两个管道,或者使用双向管道(如套接字)。
-
通常用于父子进程间通信:匿名管道通常用于父子进程或兄弟进程之间的通信,它不需要通过文件系统来共享,因此非常适用于短期和简单的通信。
-
生命周期:匿名管道的生命周期与创建它的进程相关。当管道的读写端文件描述符都被关闭时,管道就会被销毁。
使用示例:
在 Linux 中,可以通过 pipe()
函数创建匿名管道,以下是一个简单的示例:
#include <stdio.h> #include <unistd.h> #include <string.h> int main() { int pipe_fd[2]; char buffer[128]; // 创建管道 if (pipe(pipe_fd) == -1) { perror("pipe"); return 1; } // 写端 if (fork() == 0) { // 子进程向管道写入数据 close(pipe_fd[0]); // 关闭读端 write(pipe_fd[1], "Hello, pipe!", 13); close(pipe_fd[1]); return 0; } // 读端 else { // 父进程从管道读取数据 close(pipe_fd[1]); // 关闭写端 read(pipe_fd[0], buffer, sizeof(buffer)); printf("Received from pipe: %s\n", buffer); close(pipe_fd[0]); return 0; } }
在这个例子中,父子进程通过管道传递数据。子进程写入数据到管道,父进程从管道中读取数据。
命名管道(Named Pipe)
命名管道,通常称为 FIFO(First In, First Out),与匿名管道不同,它是有名字的管道,并且存在于文件系统中。命名管道允许不相关的进程间通信,因为它通过文件系统中的路径来标识。命名管道可以被多个进程访问,允许它们进行数据交换,特别适用于需要跨进程或跨会话的通信。
命名管道通过 mkfifo()
系统调用来创建,该调用会在文件系统中创建一个文件,作为管道的接口。
命名管道的特点:
-
命名:命名管道在文件系统中有一个路径名,这意味着可以通过文件路径来访问它。通常位于
/tmp
目录下。 -
双向通信:虽然命名管道本身是单向的,但可以通过创建两个管道,或通过其他机制实现双向通信。
-
进程间通信:命名管道允许不相关的进程进行通信。任何具有读取或写入权限的进程都可以访问命名管道。
-
存在于文件系统中:命名管道是一个实际的文件,存在于文件系统中,当管道不再被使用时,系统管理员可以删除它。
使用示例:
创建并使用命名管道的示例:
# 创建命名管道 mkfifo /tmp/my_pipe
然后,进程可以通过读写这个管道文件进行通信。例如,使用 echo
和 cat
命令:
-
在一个终端中,向命名管道写入数据:
echo "Hello, named pipe!" > /tmp/my_pipe
-
在另一个终端中,读取命名管道的数据:
cat < /tmp/my_pipe
输出:
Hello, named pipe!
在这个例子中,/tmp/my_pipe
是命名管道,数据通过该管道在两个进程之间传递。
管道的工作机制基于内核提供的缓冲区。当数据从写端写入管道时,数据首先存储在内核缓冲区中,读取端可以随时从缓冲区中取出数据。若缓冲区已满,写操作会被阻塞,直到有足够的空间;若缓冲区为空,读操作会被阻塞,直到有数据写入。
对于命名管道,文件系统会维护一个路径名,进程可以通过该路径名来访问管道。命名管道与普通文件不同,它们不存储数据,而是提供进程间通信的通道。
-
进程间通信:管道最常见的应用是进程间通信,特别是当多个进程需要共享数据时,管道提供了一种高效、简洁的方式来传递数据。
-
数据流传输:管道常用于数据流的传输,例如,在管道中传输文件内容、网络数据等。它可以简化数据流转,不需要将中间数据写入磁盘文件。
-
联合工具(pipelining):在 UNIX/Linux 系统中,管道是实现命令行工具之间协作的核心机制。多个命令可以通过管道连接,形成一个数据处理管道。例如,
ps | grep
将进程列表通过管道传递给grep
,从而筛选出匹配的进程。 -
并行处理:管道可以用于并行处理任务,在大数据处理系统中,管道提供了任务之间的高效数据传递路径,支持多进程并行计算。
管道文件存在限制:
-
缓冲区限制:管道的容量有限,默认情况下通常是 64 KB。数据写入时如果管道已满,写操作会被阻塞,直到缓冲区有空余空间。
-
单向通信:管道是单向的,通常只能从一端传输数据。如果需要双向通信,必须创建两个管道,或者使用其他机制(如套接字)来实现。
-
进程生命周期:匿名管道的生命周期通常与创建它的进程相关,当进程终止时,管道也随之销毁。
套接字文件
套接字文件(Socket File)是 Linux 文件系统中的一种特殊文件类型,用于进程间通信(IPC, Inter-Process Communication)。通过套接字文件,不同的进程可以在同一台计算机内或跨计算机进行数据交换,套接字文件不仅广泛应用于网络编程中,还可用于在本地机器上通过高效的通信协议实现进程间通信。套接字为网络通信提供了抽象接口,支持多种通信协议,主要分为两种类型:Unix 域套接字(Unix Domain Socket)和网络套接字(Network Socket)。
套接字的基本概念
套接字是操作系统提供的一种机制,用于在进程间建立双向通信通道。套接字并不像普通文件那样存储数据,而是充当数据交换的媒介。通过套接字,进程可以发送和接收数据,进程间通过套接字建立连接并交换数据。套接字文件在 Linux 文件系统中通常会表现为一个文件节点,可以通过路径来访问。
根据通信的范围和应用场景,套接字文件主要分为两类:Unix 域套接字和网络套接字。
Unix 域套接字(Unix Domain Socket)
Unix 域套接字是用于同一台机器内不同进程之间的通信。它不通过网络协议栈,而是利用操作系统内核提供的本地通信机制,因此其通信速度非常高。Unix 域套接字常常用于需要低延迟或高效通信的应用,尤其是在同一台机器上的进程间通信中。
特点:
-
高效性:由于通信不经过网络协议栈,Unix 域套接字通常比基于网络的通信更高效,延迟较低。
-
仅限本地通信:Unix 域套接字只能在同一台机器上使用,无法进行跨网络的通信。
-
文件路径标识:Unix 域套接字文件是文件系统中的一个节点,通过路径(如
/tmp/my_socket
)进行访问。
创建和使用 Unix 域套接字
-
创建套接字:使用
socket()
系统调用创建一个套接字,套接字的协议族是AF_UNIX
,通信类型通常是SOCK_STREAM
或SOCK_DGRAM
(流式套接字或数据报套接字)。 -
绑定套接字:使用
bind()
系统调用将套接字绑定到本地路径上,这个路径会成为套接字文件的位置。 -
监听和连接:服务器端调用
listen()
开始监听客户端的连接请求,客户端通过connect()
发起连接请求。 -
数据传输:数据可以通过
send()
和recv()
等系统调用在连接的套接字上进行传输。
示例代码:
服务器端(Unix 域套接字):
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <string.h> #define SOCKET_PATH "/tmp/my_socket" int main() { int server_fd, client_fd; struct sockaddr_un addr; char buffer[100]; // 创建 Unix 域套接字 server_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (server_fd == -1) { perror("socket"); exit(1); } // 绑定套接字到文件路径 memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, SOCKET_PATH); if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("bind"); exit(1); } // 开始监听 if (listen(server_fd, 1) == -1) { perror("listen"); exit(1); } // 等待客户端连接 printf("Waiting for connection...\n"); client_fd = accept(server_fd, NULL, NULL); if (client_fd == -1) { perror("accept"); exit(1); } // 接收数据 read(client_fd, buffer, sizeof(buffer)); printf("Received: %s\n", buffer); // 关闭套接字 close(client_fd); close(server_fd); unlink(SOCKET_PATH); // 删除套接字文件 return 0; }
客户端(Unix 域套接字):
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <string.h> #define SOCKET_PATH "/tmp/my_socket" int main() { int client_fd; struct sockaddr_un addr; const char *message = "Hello, server!"; // 创建套接字 client_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (client_fd == -1) { perror("socket"); exit(1); } // 连接到服务器 memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, SOCKET_PATH); if (connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("connect"); exit(1); } // 发送数据 write(client_fd, message, strlen(message) + 1); // 关闭套接字 close(client_fd); return 0; }
网络套接字(Network Socket)
网络套接字用于在不同计算机之间通过网络协议(如 TCP/IP)进行通信。它支持跨计算机通信,并且可以使用多种网络协议(如 TCP、UDP)进行数据传输。
特点:
-
跨计算机通信:网络套接字可以用于不同计算机之间的通信,通常基于 IP 地址和端口号进行标识。
-
协议支持:网络套接字支持多种通信协议,最常用的为 TCP 和 UDP。
-
可扩展性:网络套接字能够处理大规模的客户端-服务器通信,例如 Web 服务器和数据库服务器等应用。
创建和使用网络套接字
-
创建套接字:通过
socket()
系统调用创建一个网络套接字,通常使用AF_INET
或AF_INET6
协议族(IPv4 或 IPv6),SOCK_STREAM
(TCP)或SOCK_DGRAM
(UDP)通信类型。 -
绑定套接字:使用
bind()
系统调用将套接字绑定到 IP 地址和端口上,形成可识别的地址。 -
监听和连接:服务端使用
listen()
和accept()
监听客户端的连接请求,客户端使用connect()
向服务器发起连接请求。 -
数据传输:通过
send()
和recv()
或read()
和write()
系统调用进行数据传输。
示例代码:
TCP 服务器端(网络套接字):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #define PORT 8080 int main() { int server_fd, client_fd; struct sockaddr_in addr; char buffer[1024]; // 创建套接字 server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("socket"); exit(1); } // 设置服务器地址 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(PORT); // 绑定套接字 if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("bind"); exit(1); } // 监听连接 if (listen(server_fd, 3) == -1) { perror("listen"); exit(1); } // 接受客户端连接 printf("Waiting for connections...\n"); client_fd = accept(server_fd, NULL, NULL); if (client_fd == -1) { perror("accept"); exit(1); } // 读取客户端数据 read(client_fd, buffer, sizeof(buffer)); printf("Received: %s\n", buffer); // 关闭套接字 close(client_fd); close(server_fd); return 0; }
TCP 客户端(网络套接字):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #define PORT 8080 int main() { int client_fd; struct sockaddr_in addr; const char *message = "Hello, server!"; // 创建套接字 client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd == -1) { perror("socket"); exit(1); } // 设置服务器地址 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接到服务器 if (connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("connect"); exit(1); } // 发送数据 write(client_fd, message, strlen(message) + 1); // 关闭套接字 close(client_fd); return 0; }