news 2026/5/6 21:57:32

Linux应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux应用

指令

常用的Linux指令

ls命令执行什么功能,可以带哪些参数

功能

列出指定目录中的目录,以及文件

参数

-a:显示所有文件及目录(.开头的隐藏文件也会列出)

-l:除文件名外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出

-r:将文件以相反次序显示(原定英文字母次序)

-t:将文件依建立时间先后次序列出

-A同-a:但不列出“.”(目前目录)及".."(父目录)

-F:在列出的文件名称后加一符号。可执行档==>"*" 目录==>"/"

-R:若目录下有文件,则以下之文件皆依次列出

创建文件

vi或vim

touch

echo

vi file1.txt #直接创建并打开一个文件file1.txt


touch file2.txt #创建新的空文件file2.txt


echo "this is a new file" > file3.txt #创建文件file3.txt并将this is a new file写入
(说明:使用>指令覆盖文件原内容并重新输入内容,若文件不存在则创建文件。)


echo "add contents" >> file3.txt
#在已存在的文件补充写入新内容add contents(说明:使用>>指令向文件追加内容,原内容将保存。

less

more

cat

三者都是将文件内容输出到标准输出,其中less和more和分页显示,cat是显示全部

三者可以根据以及存在的文件创建新的文件

假设已经存在文件1.txt

cat 1.txt > 2.txt
less 1.txt > 3.txt
more 1.txt > 4.txt

more 命令只能向前翻页,无法向后翻页。用户只能使用Enter键和空格键向后翻动页面。

而less命令则更加灵活,用户可以使用上下箭头键或j,k键在文件中上下翻动。

cd 最主要的作用是切换目录,在 cd 后面跟>或>>再加上文件名就可以创建一个内容为空的文件。它和echo 的区别之处在于 echo 可写文件内容,而cd并不能。

cd > file3.txt #创建新的空文件file3.txt
cd >> file4.txt #创建新的空文件file4.txt

创建目录

mkdir runoob
#在工作目录下,建立一个名为 runoob 的子目录
mkdir -p runoob2/test #在工作目录下的 runoob2 目录中,建立一个名为 test 的子目录。若runoob2 目录原本不存在,则建立一个。(注:本例若不加 -p 参数,且原本 runoob2 目录不存在,则产生错误。)

查看文件内容有哪些命令

vi 文件名 #编辑方式查看,可修改
cat 文件名 #显示全部文件内容
more 文件名 #分页显示文件内容
less 文件名 #与 more 相似,更好的是可以往前翻页
tail 文件名 #仅查看尾部,还可以指定行数
head 文件名 #仅查看头部,还可以指定行数

查找文件内容用哪个命令

grep test *file #在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行


grep -r update /etc/acpi #查找指定目录/etc/acpi 及其子目录(如果存在子目录的话)下所有文件中包含字符串"update"的文件


grep -v test *test* #查找文件名中包含 test 的文件中不包含 test 的行
-n 选项表示输出匹配行的行号。
-w 选项表示只匹配整个单词,而不是字符串的一部分。
-r 选项表示递归搜索子目录。
-v 用于排除匹配的行,即显示不包含指定模式的行

查找文件用哪个命令

find . -name "*.c" #将当前目录及其子目录下所有文件后缀为 .c 的文件列出来
find . -ctime -20 #将当前目录及其子目录下所有最近 20 天内更新过的文件列出

查看内存,磁盘占用

内存占用

free -h:系统相关RAM使用情况(物理内存、交换内存)

top:查看系统CPU、进程、内存使用情况1

磁盘占用:

df -h:查看磁盘占用

常用的GCC指令

编译四个阶段

预处理(Pre-Processing)、编译(Compiling)、汇编(Assembling)、链接(Linking)

#include <stdio.h>
#define PI 3.14159


int main()

{
printf("PI = %f\n", PI);
return 0;
}

预处理

对源代码进行文本替换和宏展开,生成一个纯 C/C++ 代码

gcc -E test.c -o test.i #把预处理的结果导出到test.i文件

宏替换(#define)

头文件包含(#include)

条件编译(#ifdef,#ifndef,#endif)

删除注释

行号标记(用于调试)

tsst.i:

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "test.c"
# 1 "/usr/include/stdio.h" 1 3 4
... (stdio.h 的内容被展开)
# 3 "test.c" 2


int main()

{
printf("PI = %f\n", 3.14159);
return 0;
}

编译

将预处理后的代码翻译成汇编代码

gcc -S test.i -o test.s #编译器将test.i翻译成汇编语言,并将结果存储在test.s文件中。

词法分析(Lexical Analysis)
语法分析(Syntax Analysis)
语义分析(Semantic Analysis)
优化
生成汇编代码

test.s:

.file "test.c"
.section .rodata
.LC0:
.string "PI = %f\n"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp

movl $.LC0, %edi
movsd .LC1(%rip), %xmm0
movl $1, %eax
call printf
movl $0, %eax
popq %rbp
ret
.size main, .-main
.section .rodata
.align 8
.LC1:
.long 1374389535
.long 1074339512
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits

汇编

将汇编代码转换为机器码( .o 或 .obj 文件)

gcc -c test.s - o test.o #将汇编代码编译为目标文件(.o)但不链接

逐行解析汇编指令

生成目标文件(包含机器码、符号表、重定位信息)

链接

将多个目标文件合并,并解析外部引用(库函数),生成可执行文件(.exe或a.out)

gcc test.o -o test #将生成的目标文件test.o生成最终的可执行文件test

一步到位编译

gcc test.c -o test #将源文件test.c编译链接为可执行文件test

多文件编译

gcc test1.c test2.c -o test

警告处理

gcc -w test.c -o test # 忽略编译时的警告
gcc -Wall test.c -o test #编译后显示所有警告
gcc -Werror test.c -o test #在产生警告的地方停止编译

常用的GDB调试指令

开启调试:

gcc -g -o test test.c
gdb test

l n:显示行号

b n:断点设置

info b:查看断点信息

d + Num / d + breakpoints:删除指定 / 所有断点

enable / disable b (+Num):开启 / 禁用 全部断点(指定断点)

r(run):无断点直接运行到程序结束,加上断点去运行的话就会在打的断点处停下来

n(next):逐过程

s(step):逐语句,一次走一条代码,可进入函数,同时库函数也会进入

p(print) 变量名: 打印变量值

display:跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】

undisplay + 变量名编号:取消跟踪

until + 行号:运行指定行

finish:在一个函数内部,执行到当前函数返回,然后停下来等待命令

c(continue) : 从一个断点处,直接运行至下一个断点处

bt :查看底层函数调用的过程【函数压栈】

常用的驱动开发指令

加载/卸载驱动

insmod/modprobe #加载驱动
rmmod #卸载驱动

Linux驱动如何查看驱动模块中打印信息

dmesg

如何查看内核中已有的字符设备的信息

lsmod 和modprobe,lsmod可以查看模块的依赖关系,modprobe在加载模块时会加载其他依赖的模块。

如何创建设备

mknod /dev/zgl c 240 0 #用于创建一个名为/dev/zgl的字符设备文件。其中,c表示字符设备类型,240是主设备号,0是次设备号。

如何查看设备号

cat /proc/devices

如何查看正在使用的有哪些中断号

cat /proc/interrupt

文件I/O和标准I/O

文件I/O与标准I/O的区别

文件 I/O(Input/Output):也称为低级 I/O,它是操作系统提供的基本 I/O 操作接口,直接对文件描述符进行操作。文件描述符是一个非负整数,用于标识一个打开的文件。在 Unix/Linux 系统中,像open、read、write、close等系统调用都属于文件 I/O 操作。

标准 I/O:也称为高级 I/O,它是在文件 I/O 的基础上构建的一种更为方便的 I/O 库。标准 I/O 提供了缓冲机制,它会自动为输入输出操作分配缓冲区,减少系统调用的次数,提高效率。在 C 语言中,stdio.h头文件中定义的函数(如printf、scanf、fgets、fputs等)都属于标准 I/O。

标准I/O常用API

fopen

FILE *fopen (const char *__restrict __filename,const char *__restrict __modes)

参数解析

char *__restrict __filename: 字符串表示要打开文件的路径和名称
char *__restrict __modes: 字符串表示访问模式
(1)"r": 只读模式 没有文件打开失败
(2)"w": 只写模式 存在文件写入会清空文件,不存在文件则创建新文件
(3)"a": 只追加写模式 不会覆盖原有内容 新内容写到末尾,如果文件不存在则创建
(4)"r+": 读写模式 文件必须存在 写入是从头一个一个覆盖
(5)"w+": 读写模式 可读取,写入同样会清空文件内容,不存在则创建新文件
(6)"a+": 读写追加模式 可读取,写入从文件末尾开始,如果文件不存在则创建
return: FILE * 结构体指针 表示一个文件

fopen函数主要用来打开一个文件,第一个参数表示打开的文件路径和名称,第二个参数表示文件访问模式,例如可读或者可写,或者创建文件等,调用成功后放回一个结构体指针表示一个文件。

fclose

int fclose (FILE *__stream)

参数解析:

FILE *__stream: 需要关闭的文件
return: 成功返回0 失败返回EOF(负数) 通常失败会造成系统崩溃

fclose函数主要用来关闭一个指定的文件,调用成功后返回0

fputc

int fputc (int __c, FILE *__stream)

参数解析:

int __c: 写入的char按照AICII值写入 可提前声明一个char
FILE *__stream: 要写入的文件,写在哪里取决于访问模式
return: 成功返回char的值 失败返回EOF

fputc函数主要用来写入一个字符到指定的文件流,第一个参数填写字符(例如'A'),第二个参数填写文件。

fputs

int fputs (const char *__restrict __s, FILE *__restrict __stream)

参数解析:

char *__restrict __s: 需要写入的字符串
FILE *__restrict __stream: 要写入的文件,写在哪里取决于访问模式
return: 成功返回非负整数(一般是0,1) 失败返回EOF

fputs函数主要用来写入一个字符串到指定的文件流,第一个参数填写字符(例如'ABC'),第二个参数填写文件。

fprintf

fprintf (FILE *__restrict __stream, const char *__restrict __fmt, ...)

参数解析:

FILE *__restrict __stream: 要写入的文件,写在哪里取决于访问模式
char *__restrict __fmt: 格式化字符串
...: 变长参数列表
return: 成功返回正整数(写入字符总数不包含换行符) 失败返回EOF

示例:

File *file = fopen("example.txt", "w");
// 写入格式化的数据
int age = 25;
char name[] = "Alice";
fprintf(file, "Name: %s, Age: %d\n", name, age);

fprintf函数主要用来将格式化的数据写到指定的文件流中,第一个参数填写文件,第二个参数填写格式化字符串,第三个参数填写参数列表,比如整型("%d"),字符型(%c),字符串型("%s")参数。

fgetc

int fgetc (FILE *__stream)

参数解析:

FILE *__stream: 需要读取的文件
return: 读取的一个字节 到文件结尾或出错返回EOF

fgetc函数主要用来读取指定文件中的一个字节。

fgets

char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream)

参数解析:

char *__restrict __s: 接收读取的数据字符串
int __n: 能够接收数据的长度
FILE *__restrict __stream: 需要读取的文件
return: 成功返回字符串 失败返回NULL(可以直接用于while)

示例:

#include <stdio.h>


int main()
{
char buffer[100]; // 定义一个足够大的缓冲区
printf("请输入一行文本(最多99个字符):\n");
// 从标准输入读取字符串
if (fgets(buffer, sizeof(buffer), stdin) != NULL)
{
printf("您输入的内容是:\n%s", buffer);
}
else
{
printf("读取输入失败。\n");
}
return 0;
}

fgets函数主要用来读取一个字符串,第一个参数是接收读取的字符串buf,第二个参数是数据的长度,第三个是读取的文件,成功返回字符串,失败返回NULL。

fcanf

int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...)

参数解析:

FILE *__restrict __stream: 读取的文件
char *__restrict __format: 读取的匹配表达式
...: 变长参数列表 用于接收匹配的数据
return: 成功返回参数的个数 失败返回 0 报错或结束返回EOF

示例:

#include <stdio.h>


int main()
{

int age;
float height;
char name[50];
printf("请输入您的姓名、年龄和身高(格式:姓名 年龄 身高):\n");
// 从标准输入读取格式化的数据
if (fscanf(stdin, "%s %d %f", name, &age, &height) == 3)
{
printf("您输入的信息是:\n");
printf("姓名:%s\n", name);
printf("年龄:%d\n", age);
printf("身高:%.2f\n", height);
}
else
{
printf("输入格式错误。\n");
}
return 0;
}

fscanf函数主要用来从指定的文件流中读取格式化的数据,第一个参数是指定的文件流,第二个参数填写格式化字符串,第三个参数填写参数列表,比如整型("%d"),字符型(%c),字符串("%s")参数。

标准输入输出错误

stdin: 标准输入FILE *
stdout: 标准输出FILE * 写入这个文件流会将数据输出到控制台
stderr: 错误输出FILE * 一般用于输出错误日志文件 I/O 常用API

文件I/O常见API

open

int open (const char *__path, int __oflag, ...);

参数解析:

const char *__path: 文件路径
int __oflag: 用于指定打开文件的方式,可以是以下选项的组合:
(1) O_RDONLY: 以只读方式打开文件
(2) O_WRONLY: 以只写方式打开文件
(3) O_RDWR: 以读写方式打开文件
(4) O_CREAT: 如果文件不存在,则创建一个新文件
(5) O_APPEND: 将所有写入操作追加到文件的末尾
(6) O_TRUNC: 如果文件存在并且以写入模式打开,则截断文件长度为0
还有其他标志,如O_EXCL(当与O_CREAT一起使用时,只有当文件不存在时才创建新文件)、O_SYNC(同步I/O)、O_NONBLOCK(非阻塞I/O)等
可选参数: mode -> 仅在使用了O_CREAT标志且文件尚不存在的情况下生效,用于指定新创建文件的权限位 权限位通常由三位八进制数字组成,分别代表文件所有者、同组用户和其他用户的读写执行权限
return: (1) 成功时返回非负的文件描述符。
(2) 失败时返回-1,并设置全局变量errno以指示错误原因。

open函数主要用来打开一个指定的文件,第一个参数是文件的路径,路径可以是绝对路径或者相对路径,第二个参数是指定打开文件的方式,可以是只读,只写,或者可读可写等,第三个参数只有在第二个参数选择创建文件才生效,用来指定新创建文件的权限位,权限位通常由三位八进制数字组成,分别代表文件所有者、同组用户和其他用户的读写执行权限。

read

ssize_t read (int __fd, void *__buf, size_t __nbytes);

参数解析:

int __fd:一个整数,表示要从中读取数据的文件描述符
void *__buf:一个指向缓冲区的指针,读取的数据将被存放到这个缓冲区中
size_t __nbytes:一个size_t类型的整数,表示要读取的最大字节数 系统调用将尝试读取最多这么多字节的数据,但实际读取的字节数可能会少于请求的数量
return: (1) 成功时,read()返回实际读取的字节数 这个值可能小于__nbytes,如果遇到了文件结尾(EOF)或者因为网络读取等原因提前结束读取
(2) 失败时,read()将返回-1

read函数主要用来从指定的文件描述符中读取数据,第一个参数为指定的文件描述符,第二个参数存放读取数据的buf,第三个数据表示读取的字节数。

write

ssize_t write (int __fd, const void *__buf, size_t __n);

参数解析:

int __fd:一个整数,表示要写入数据的文件描述符
void *__buf:一个指向缓冲区的指针,写入的数据需要先存放到这个缓冲区中
size_t __n:一个size_t类型的整数,表示要写入的字节数 write()函数会尝试写入__n个字节的数据,但实际写入的字节数可能会少于请求的数量
return: (1) 成功时,write()返回实际写入的字节数 这个值可能小于__n,如果写入操作因故提前结束,例如: 磁盘满、网络阻塞等情况
(2) 失败时,write()将返回-1

write函数主要用来从指定的文件描述符中写入数据,第一个参数为指定的文件描述符,第二个参数存放写入数据的buf,第三个数据表示写入的字节数。

close

int close (int __fd);

参数解析:

int __fd:一个整数,表示要关闭的文件描述符
return: (1) 成功关闭时 返回0
(2) 失败时 返回-1

close函数主要用来关闭使用完的文件描述符。

exit()和_exit()

通常在父进程中使用 exit() ,以确保程序在退出前能执行清理操作,如关闭文件和刷新输出。
在子进程中使用 _exit() ,这可以防止子进程的终止影响到父进程(比如,防止子进程意外地刷新了父进程未写入的输出缓冲区)

进程

进程是资源分配的基本单位,在程序运行时创建

PCB进程控制块

进程控制块(PCB: Process Control Block)通过一个结构体存放进程运行的相关信息,例如:
进程id
进程的状态
进程的物理内存和虚拟内存
进程运行的时间
进程所执行程序的路径

进程五种状态

进程可以分为五个状态,分别是:
1. 创建状态
2. 就绪状态
3. 运行状态
4. 阻塞状态
5. 终止状态

创建状态
一个应用程序从系统上启动,首先就是进入创建状态,需要获取系统资源创建进程控制块完成资源分配。
就绪状态
在创建状态完成之后,进程已经准备好,但是还未获得处理器资源,无法运行。
运行状态
获取处理器资源,被系统调度,开始进入运行状态。如果进程的时间片用完了就进入就绪状态。
阻塞状态
在运行状态期间,如果进行了阻塞的操作,如耗时的I/O操作,此时进程暂时无法操作就进入到了阻塞状态,在这些操作完成后就进入就绪状态。
终止状态
进程结束或者被系统终止,进入终止状态进程的状态转换图

进程的状态转换图

fork和vfork区别

fork( )的子进程有自己独立的地址空间,拷贝父进程的代码段、数据段、堆、栈等;

vfork( )的子进程与父进程共享地址空间,包括代码段、数据段、堆、栈等;

fork( )的父子进程的执行次序不确定;

vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

僵尸进程,孤儿进程,守护进程

用 fork 函数创建一个正常运行的子进程(子进程 = 0,父进程 > 0),如果子进程退出,父进程没有及时调用wait或 waitpid 函数回收子进程的系统资源,该进程就变成僵尸进程,如果系统收回了,就是正常退出,如果父进程退出了但是子进程还在,该进程就变成孤儿进程,被 init 收养,如果父进程是故意被杀掉,子进程做相应处理后就是守护进程

进程间通信

管道

无名管道

无名管道是一种单向的、字节流的通信管道,它是一种匿名管道,无法通过文件系统来访问它,只能通过文件描述符在具有亲缘关系的进程之间使用,比如父子进程和兄弟进程。

无名管道通信步骤:

创建一个函数创建无名管道(pipe())

创建子进程(fork()),父子进程共享管道的读写端。

在父进程中关闭管道的读端,向管道的写端写入数据。

在子进程中关闭管道的写端,从管道的读端读取数据。

如果要双向传输的话就多创建一个管道,控制好父子进程管道的读写段即可。

有名管道

有名管道与无名管道不同,它是通过文件系统路径命名的管道,可以在进程之间进行通信

消息队列

消息队列是一种先进先出的消息缓冲区,用于在多个进程之间传递消息。

消息队列通信步骤:

创建或获取一个进程间通信键值 (IPC key)(ftok())

创建或打开一个消息队列 (msgget())

进程通过消息队列发送消息(msgsnd())

进程通过消息队列接收消息(msgrcv())

删除消息队列(msgctl())

缺点:

消息队列的缓存区域是在内核空间中开辟的,因此当进程发送或接收消息时,需要经过内核态和用户态之间的多次切换,多次状态间的切换会带来一定的开销,特别是在发送和接收大量数据时,切换开销可能会严重影响系统性能。

共享内存

共享内存是通过操作系统内核在不同进程之间共享内存区域的一种机制。在创建共享内存时,操作系统会分配一块内存区域,并将其映射到各个进程的地址空间中。进程可以直接读写这个内存区域,而不需要进行任何数据传输的操作。

共享内存的优点是速度快,因为不需要数据的复制操作,而且不需要操作系统进行上下文切换,所以它通常比其他 IPC 机制(如管道和消息队列)更快

共享内存通信步骤:

创建或获取一个进程间通信键值 (IPC key)(ftok())

创建共享内存(shmget())

将共享内存映射到进程的地址空间中(shmat())

解除共享内存映射(shmdt())

删除共享内存(shmctl())

信号量

信号量既可用于进程间同步,也可用于临界区资源的互斥访问。

当多个线程出现后,可能会遇到无序执行的问题和同时操作临界公共资源的问题,就可以用信号量,通过 PV 操作来阻塞或者唤醒某个线程,控制线程的执行顺序。

信号量通信步骤:

创建或获取一个进程间通信键值 (IPC key)(ftok())

创建或获取信号量(semget())

初始化信号量(semctl())

P 操作(等待)/ V 操作(释放)(semop())

删除信号量(semctl())

线程

对于进程而言,每一个进程都有一个唯一对应的PID号来表示该进程,而对于线程而言,也有一个“类似于进程的PID号”,名为tid,其本质是一个pthread_t类型的变量。线程号与进程号是表示线程和进程的唯一标识,但是对于线程号而言,其仅仅在其所属的进程上下文中才有意义。

线程使用

首先需要创建线程,一旦线程创建完成后,线程与线程之间会发生竞争执行,抢占时间片来执行线程逻辑。在创建线程时候,可以通过创建线程的第四个参数传入参数,在线程退出时亦可传出参数被线程回收函数所回收,获取到传出的参数。

互斥量

当多个线程出现后,会遇到同时操作临界公共资源的问题,当线程操作公共资源时需要对线程进行保护加锁,防止多个线程同时修改一个变量,等到该线程执行完毕后再次解锁,使其余线程再度开始竞争。互斥锁创建流程下图所示。

信号量

当多个线程出现后,同时会遇到无序执行的问题。有时候需要对线程的执行顺序做出限定,便引入了信号量,通过PV操作来控制线程的执行顺序。

自旋锁、互斥锁、读写锁

自旋锁是一种基于忙等待的锁机制。当一个线程尝试获取自旋锁时,如果锁已经被其他线程占用,它不会进入休眠状态,而是不断地循环检查锁是否被释放。自旋锁适用于锁持有时间非常短的场景,如果锁的持有时间较长,自旋锁会浪费大量的CPU资源,导致系统性能下降。

互斥锁是一种用于保护共享资源的锁机制。它确保同一时间只有一个线程可以持有该锁,从而保证对共享资源的互斥访问。当一个线程尝试获取互斥锁时,如果锁已经被其他线程占用,它会进入休眠状态,等待锁被释放。一旦锁被释放,线程会被唤醒并尝试获取锁。

读写锁是一种允许多个线程同时读取共享资源,但写操作需要互斥的锁机制。它分为读锁和写锁。读锁允许多个线程同时持有读锁,但不允许写锁持有。写锁是互斥的,同一时间只能有一个线程持有写锁,且写锁持有期间不允许其他线程持有读锁。

网络编程

TCP/UDP

TCP怎么保证可靠性

1. 序列号、确认应答、超时重传
数据到达接收方,接收方需要发出一个确认应答,表示已经收到该数据段,并且确认序号会说明它下一次需要接收的数据序列号。如果发送方迟迟未收到确认应答,那么可能是发送的数据丢失,也可能是确认应答丢失,这时发送方在等待一定时间后会进行重传。这个时间一般是
2*RTT(报文段往返时间)+一个偏差值。
2. 窗口控制与高速重发控制/快速重传(重复确认应答)
TCP会利用窗口控制来提高传输速度,意思是在一个窗口大小内,不用一定要等到应答才能发送下 一段数据,窗口大小就是无需等待确认而可以继续发送数据的最大值。如果不使用窗口控制,每一个没收到确认应答的数据都要重发。
使用窗口控制,如果数据段1001-2000丢失,后面数据每次传输,确认应答都会不停地发送序号为 1001的应答,表示我要接收1001开始的数据,发送端如果收到3次相同应答,就会立刻进行重发; 但还有种情况有可能是数据都收到了,但是有的应答丢失了,这种情况不会进行
重发,因为发送端知道,如果是数据段丢失,接收端不会放过它的,会疯狂向它提醒。

简述一下TCP建立连接和断开连接的过程

TCP建立连接和断开连接的过程

三次握手

TCP(传输控制协议)三次握手是TCP协议建立连接的过程,其主要目的是确保双方在通信之前已经准备好,并且能够互相通信。

三次握手主要是:
客户端发起连接请求
服务器响应连接请求
客户端确认连接

以下是三次握手的详细过程:
1. 客户端(主动方)向服务器(被动方)发送一个SYN(同步序列编号)报文段。这个报文段的SYN标志位被置为1,同时携带一个随机的序列号(Seq = x)。这个序列号是客户端为这次连接生成的初始序列号。(例如,客户端生成的初始序列号是1000,那么这个SYN报文段的序列号就是1000。)
2. 服务器收到客户端的SYN报文段后,如果同意建立连接,会发送一个SYN - ACK(同步 - 确认)报文段作为响应。这个报文段的SYN标志位和ACK(确认)标志位都被置为1。服务器会为这个连接分配资源,并且生成自己的初始序列号(Seq = y)。同时,它会将客户端的初始序列号(x)加1后作为确认号(ACK = x + 1)发送给客户端。(例如,服务器的初始序列号是2000,客户端的初始序列号是1000,那么服务器发送的SYN - ACK报文段的序列号是2000,确认号是1001。)

3. 客户端收到服务器的SYN - ACK报文段后,会发送一个ACK(确认)报文段给服务器。这个报文段的ACK标志位被置为1,序列号是客户端的初始序列号加1(Seq = x + 1),确认号是服务器的初始序列号加1(ACK = y + 1)。(例如,客户端的初始序列号是1000,服务器的初始序列号是2000,那么客户端发送的ACK报文段的序列号是1001,确认号是2001。)

四次挥手

TCP四次挥手是TCP协议关闭连接的过程,其目的是确保双方在关闭连接之前已经完成了数据的传输,并且能够安全地释放资源

四次挥手主要是:
客户端发起关闭请求
服务器确认客户端的关闭请求
服务器发起关闭请求
客户端确认服务器的关闭请求

以下是四次挥手的详细过程:

1. 客户端(主动关闭方)向服务器(被动关闭方)发送一个FIN(结束)报文段,表示客户端已经完成了数据的发送,想要关闭连接。这个FIN报文段的FIN标志位被置为1,同时携带一个序列号(Seq= u)。这个序列号是客户端在连接建立过程中生成的序列号,随着数据的发送和确认,序列号会不断变化。(例如,客户端在连接过程中发送了多个数据报文段,当前的序列号是3000,那么这个FIN报文段的序列号就是3000。)

2. 服务器收到客户端的FIN报文段后,会发送一个ACK(确认)报文段给客户端。这个ACK报文段的ACK标志位被置为1,序列号是服务器当前的序列号(Seq = v),确认号是客户端的序列号加1(ACK = u + 1)。(例如,服务器当前的序列号是4000,客户端的序列号是3000,那么服务器发送的ACK报文段的序列号是4000,确认号是3001。)

3. 服务器在确认客户端的关闭请求后,会等待一段时间(这个时间是服务器自己决定的,用于处理一些后续的事务,比如数据的清理等)。然后,服务器也会发送一个FIN报文段给客户端,表示服务器也完成了数据的发送,想要关闭连接。这个FIN报文段的FIN标志位被置为1,序列号是服务器的序列号加1(Seq = v + 1)。(例如,服务器的序列号是4000,那么这个FIN报文段的序列号就是4001。)

4. 客户端收到服务器的FIN报文段后,会发送一个ACK报文段给服务器。这个ACK报文段的ACK标志位被置为1,序列号是客户端的序列号加1(Seq = u + 1),确认号是服务器的序列号加1(ACK = v +2)。(例如,客户端的序列号是3000,服务器的序列号是4000,那么客户端发送的ACK报文段的序列号是3001,确认号是4002。)

TCP的三次握手和四次握手的原因是什么

为什么是三次握手?

1. TCP连接是双向的,客户端和服务器都可以发送和接收数据。三次挥手可以确保双方都完成了数据的发送。为了防止已失效的客户端连接请求报文段突然又送到服务器,因而产生错误。
2. 在网络中可能存在延迟、丢包等情况,假设客户端发送连接请求后因网络延迟很久才到达服务器,或者客户端发送连接请求后出现问题突然下线,如果只有两次握手,服务器会误认为是一个新的连接请求而建立连接,这会导致无效连接,浪费资源。

通俗来讲,过程就像打电话:
1. 第一次握手 (客户端 -> 服务器)
客户端:“喂,你好!听得到吗?” ( SYN )

(客户端心里想:我的消息发出去试试,看对方在不在线,线路通不通。)

2. 第二次握手 (服务器 -> 客户端)
服务器:“哎,我听得到!你呢?听得到我吗?” ( SYN-ACK )
(服务器心里想:我不仅收到了你的问候,我也回应了,并且也反问一下你,确认你也能听到我。

3. 第三次握手 (客户端 -> 服务器)
客户端:“嗯,我也听得到你!那我们开始聊正事吧!” ( ACK )
(客户端心里想:太好了,对方能听到我,我也能听到对方,沟通渠道完全畅通!)

为什么是四次挥手?

1. TCP协议是全双工通信,客户端和服务器都可以发送和接收数据。四次挥手可以确保双方都完成了数据的发送。
2. 假设是三次挥手时,首先释放了客户端到服务器方向的连接,此时TCP连接处于半关闭(Half-Close)状态, 这时客户端不能向服务器发送数据,而服务器还是可以向客户端发送数据。如果此时客户端收到了服务器的确认报文段后,就立即发送一个确认报文段,这会导致服务器向客户端还在发送数据时连接就被关闭。这样会导致客户端没有完整收到服务器所发的报文段

过程就像打完电话要挂断:

1. 第一次挥手 (客户端 -> 服务器)
客户端:“嗯,我要说的都说完了,我准备挂电话了哈。” ( FIN )
(客户端说完了,但它还可以接收数据。)
2. 第二次挥手 (服务器 -> 客户端)
服务器:“哦哦,好的,我知道你说完了。你稍等一下,我看看我这边还有没有要对你说的。”( ACK )
(服务器先给客户端一个确认,但自己可能还有数据没发送完。)
3. 第三次挥手 (服务器 -> 客户端)
服务器:“好了,我这边也都说完了,我也准备挂电话了。” ( FIN )
(服务器处理完所有数据,也告知客户端自己说完了。)
4. 第四次挥手 (客户端 -> 服务器)
客户端:“好的,那我们拜拜啦!” ( ACK )
(客户端收到服务器的结束请求,最后确认一下,然后双方正式挂断。)

为什么必须四次,三次不行吗

因为 TCP 连接是全双工的——就好比电话通话,你可以同时听我说,我也可以同时听你说。挂电话时,必须双方都确认说完了才行。

第二次和第三次挥手不能合并吗?有时候可以,但通常不行。因为当客户端说“我说完了”时,服务器可能还有数据要发送给客户端。所以服务器要先回复一个“我知道你说完了”(第二次挥手),然后赶紧把没说完的数据发完,最后再说“我也说完了”(第三次挥手)。这两个动作中间是有时间差的。

TCP,UDP的区别

1. TCP 是面向连接的,UDP 是面向无连接的
2. TCP 是面向字节流的,UDP 是基于数据报的
3. TCP 保证数据正确性,UDP 可能丢包
4. TCP 保证数据顺序,UDP 不保证

TCP,UDP的优缺点和适用场景

TCP传输数据比较可靠稳定,但是效率比较低,占用系统资源高,一般使用在文件传输,发送邮件等场景。

UDP传输数据不可靠,不稳定,但是速度比较快,效率比较高,一般使用在语音电话,视频聊天等场景,偶尔丢包卡顿不要紧

TCP相比UDP为什么是可靠的

1. 确认和重传机制
建立连接时三次握手同步双方的“序列号 + 确认号 + 窗口大小信息”,是确认重传、流控的基础传输过程 中,如果Checksum校验失败、丢包或延时,发送端重传。
2. 数据排序
TCP有专门的序列号SN字段,可提供数据re-order
3. 流量控制
窗口和计时器的使用。TCP窗口中会指明双方能够发送接收的最大数据量。
4. 拥塞控制
TCP的拥塞控制由4个核心算法组成。“慢启动”(Slow Start)、“拥塞避免”(Congestion
avoidance)、“快速重传 ”(Fast Retransmit)、“快速恢复”(Fast Recovery)

基于TCP的socket编程

服务端:
1. socket
2. bind
3. listen
4. accept
5. send / recv, write / read
6. close

服务端程序先用socket函数创建一个套接字,然后再用 bind 函数将 IP 地址和端口绑定在这个套接字上,用 listen 函数设置允许的最大连接数,用 accept 函数接收客户端上的连接,然后用 send 或者write 函数发送数据,用 recv 或者 read 函数接收数据,最后记得用 close 函数关闭网络连接。

客户端:
1. socket
2. connect
3. send / recv,read / write
4. close

客户端程序先用socket函数创建一个套接字,设置要连接的对方的IP地址和端口等属性,接着用 connect函数连接服务器,然后用 send 或者 write 函数发送数据,用 recv 或者 read 函数接收数据,最后记得用close 函数关闭网络连接。

基于UDP的socket编程

服务端:
1. socket
2. bind
3. sendto
4. recvfrom
5. close
服务端程序先用socket函数创建一个套接字,然后再用 bind 函数将 IP 地址和端口绑定在这个套接字上,使用 sendto 函数发送数据,使用 recvfrom 函数接收数据,最后用 close 函数关闭网络连接。
客户端:
1. socket
2. sendto
3. recvfrom
4. close
客户端程序先用socket函数创建一个套接字,设置要连接的对方的IP地址和端口等属性,然后使用
sendto 函数发送数据,使用 recvfrom 函数接收数据,最后用 close 函数关闭网络连接。

HTTP

什么是HTTP协议

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网
(WWW:World Wide Web)服务器传输超文本到本地浏览器的传送协议。HTTP协议适用于CS架构和BS架构,浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

CS架构是一种典型的两层结构,包括客户端和服务器两个部分。在这种架构中,客户端和服务器通过网络进行通信,每部分都有明确的职责。
BS架构是一种基于Web的三层或多层架构,主要通过Web浏览器作为客户端访问服务器上的应用程序。

HTTP和HTTPS的区别,HTTPS有什么优缺点

区别
1. HTTP协议是以明文的方式在网络中传输数据,而HTTPS协议传输的数据则是经过TLS加密后的,HTTPS具有更高的安全性
2. HTTPS在TCP三次握手阶段之后,还需要进行 SSL 的handshake,协商加密使用的对称加密密钥
3. HTTPS协议需要服务端申请证书,浏览器端安装对应的根证书
4. HTTP协议的默认端口是80,HTTPS协议的默认端口是443

HTTP建立连接过程是什么

HTTP 请求/响应的步骤如下:

1. 客户端连接到Web服务器

2. 发送HTTP请求

3. 服务器接受请求并返回HTTP响应

4. 释放连接TCP连接

5. 客户端浏览器解析HTML内容

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据

IO模型

IO 操作就是计算机的输入输出操作,在冯.诺依曼结构中,将计算机分成了5个部分,分别是运算器、控制器、存储器、输入设备和输出设备:

上图中的输入设备指的是鼠标和键盘等向计算机输入数据和信息的设备,输出设备指的是电脑显示器等用于计算机信息输出的设备。当敲击键盘(输入设备)任意按键后,按键的数据会传递给计算机,计算机CPU会对数据进行运算,运算完成之后会将数据输出到显示器(输出设备)上。

IO执行过程

在Linux 操作系统中,内核负责对计算机的资源进行管理和对进程进行调度。应用程序运 行于用户空间,而IO操作往往涉及硬件,由于硬件操作需要特权级权限,必须由操作系统在 内核空间中完成。也就是说,应用程序不能直接对硬件进行操作,只能通过内核提供的系统调 用接口(如read、write等)间接完成IO操作。此外,用户空间无法直接访问内核空间的数据, 因此在IO操作完成后,内核需要将数据从内核空间拷贝到用户空间供应用程序使用。

一个完整的IO过程需要包含以下三个步骤:
用户空间的应用程序向内核发起IO调用请求(系统调用)
内核操作系统准备数据,把IO设备的数据加载到内核缓冲区
操作系统拷贝数据,把内核缓冲区的数据拷贝到用户进程缓冲区

IO模型分类

在实际开发中,IO操作常常成为影响程序性能的关键因素。假设有一个场景:从磁盘读取 100MB 数据并处理,读取数据耗时20秒,处理数据也需要20秒。如果采用最传统的顺序流程——读取完再处理,那么整个流程耗时约40秒,效率明显偏低。

那么能不能在等待数据的同时对数据进行处理呢,这时候就轮到IO编程模型来出场了。

IO 模型根据实现的功能可以划分为阻塞IO、非阻塞IO、信号驱动IO、IO多路复用和异步 IO。根据等待IO的执行结果进行划分,前四个IO模型又被称为同步IO,

同步IO与异步IO是数据处理中的两种重要模式:

同步是指应用发起IO请求后,必须等待内核完成数据准备和传输操作,才能继续执行后 续任务。在此过程中,应用可能会被阻塞,直到数据从内核空间拷贝到用户空间。
异步IO允许应用在发起IO请求后立即返回,并继续执行其他任务。内核会在数据准备好并完成传输后,通过回调或信号通知应用操作已完成。应用无需主动等待数据的准备过程。

同步IO

查询方式(非阻塞)

APP调用open函数时,传入“O_NONBLOCK”表示“非阻塞”。

APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据,否则也会立刻返回错误

休眠-唤醒方式(阻塞)

APP调用open函数时,不要传入“O_NONBLOCK”。

APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据;否则APP就会在内核态休眠,当有数据时驱动程序会把APP唤醒,read函数恢复执行并返回数据给APP。

IO多路复用

IO 多路复用是一种同时监视多个文件描述符的技术,它可以通过单个进程来处理多个 IO 操作,提高系统的 IO 效率。在 IO 多路复用中,一个进程可以同时监视多个文件描述符,一旦其中任意一个文件描述符准备就绪,就会通知进程进行读写操作,从而实现了同时处理多个 IO 事件的功能。

以select()函数为例进行讲解,使用时需要向select()传入待监听的文件描述符集合以及超时时间。当执行select()时,系统会触发一次系统调用,内核将遍历检查这些描述符是否触发了目标事件(如可读、可写)。若检测到事件则立即返回,若未检测到事件,进程将进入阻塞状态并休眠,直到任一描述符就绪或超时为止。当select()返回后,用户空间需遍历所有描述符,逐一确认具体是哪个触发了事件,从而实现单线程同时管理多个 IO 操作的效果。

缺点:

select 监听的描述符有上限(一般描述符最大不超过1024),而且需要遍历究竟是哪 一个IO产生
了数据。因此IO较多时,效率不高,所以可以使用 epoll

信号驱动IO(异步通知)

信号驱动IO指的是进程会预先告知内核,当某个描述符发生事件时,内核要向该进程发送 SIGIO 信号进行通知,进程可以在信号处理函数中对该事件进行处理。

要使用信号驱动IO,需要应用程序和驱动程序配合,应用程序使用信号驱动IO的步骤有三步

注册信号处理函数,应用程序中使用signal()函数来注册SIGIO信号的信号处理函数。
使用fcntl()函数设置能够接收这个信号的进程。
使用fcntl()函数的F_SETFL参数打开FASYNC标志开启信号驱动IO。

异步通知方式首先要注册信号处理函数和编写信号处理函数,当驱动程序用数据时会主动发信号给应用程序,从而调用信号处理函数。

1.编写信号处理函数:

static void sig_func(int sig)
{
int val;
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}

2.注册信号处理函数:

signal(SIGIO, sig_func);

3.打开驱动:

fd = open(argv[1], O_RDWR);

4.把进程 ID 告诉驱动:

// 设置该文件描述符的拥有者为该进程
fcntl(fd, F_SETOWN, getpid());

5.使能驱动的 FASYNC 功能

// 获取文件描述符的状态标志
flags = fcntl(fd, F_GETFL);
// 设置文件描述符的状态标志
fcntl(fd, F_SETFL, flags | FASYNC);

异步IO

aio_read() 函数常常用于异步IO,当进程使用aio_read()读取数据时,如果数据尚未准备就绪就立即返回,不会阻塞。若数据准备就绪就会把数据从内核空间拷贝到用户空间的缓冲区中, 然后执行定义好的回调函数对接收到的数据进行处理。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/6 21:54:51

告别混乱!UG/UF函数与NXOpen API获取面类型数据对照表与选用指南

UG/UF函数与NXOpen API面类型获取技术决策指南 在Siemens NX二次开发领域&#xff0c;获取几何面类型是基础但关键的操作。当开发者面对遗留的UF函数库与现代NXOpen API时&#xff0c;常陷入技术选型的困境。本文将深入对比两种方法的实现逻辑、性能表现和适用场景&#xff0c;…

作者头像 李华
网站建设 2026/5/6 21:53:50

硬件工程师转驱动:我用瑞芯微平台和LT6911UXC踩过的I2C地址坑

硬件工程师转型驱动开发&#xff1a;从LT6911UXC的I2C地址困惑到思维跃迁 当示波器的探头换成代码编辑器&#xff0c;当万用表的读数变成调试日志&#xff0c;硬件工程师的转型之路往往始于一个简单的困惑。我至今记得第一次在瑞芯微平台上调试LT6911UXC时&#xff0c;那个看似…

作者头像 李华
网站建设 2026/5/6 21:45:28

长期使用中感受到的Taotoken平台API服务可用性总结

长期使用中感受到的Taotoken平台API服务可用性总结 1. 服务可用性观察 在持续数月的开发集成过程中&#xff0c;通过Taotoken平台调用大模型API的整体体验稳定可靠。日常开发场景下&#xff0c;API请求能够按预期完成&#xff0c;响应时间保持在合理范围内。平台提供的统一接…

作者头像 李华