linux系统编程知识点

Linux操作系统下文件共有7种:

目录、普通文件、链接文件、管道、套接字、字符设备、块设备

Linux 使用的进程间通信方式?

  • 1、管道(pipe)、流管道(s_pipe)、有名管道(FIFO)。
  • 2、信号(signal) 。
  • 3、消息队列。
  • 4、共享内存。
  • 5、信号量。
  • 6、套接字(socket) 

简单 Linux 文件系统?

在 Linux 操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。

也就是说在 Linux 系统中有一个重要的概念:一切都是文件。其实这是 Unix 哲学的一个体现,而 Linux 是重写 Unix 而来,所以这个概念也就传承了下来。在 Unix 系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。

Linux 支持 5 种文件类型,如下图所示:

设备驱动程序包括哪些功能函数?

  • open()
  • read()
  • write()
  • llseek()
  • realse()

  1. 插件:

sudo apt-get install terminator

相关使用:

竖分屏:ctrl + shift +o

横分屏:ctrl+shift+e

关闭:ctrl + shift +_ w

字体放大: ctrl + shift + ‘+’

字体缩小: ctrl + ‘-’

/bin  存放的是shell指令(可执行文件)

/etc  系统配置文件夹

/lib   库文件夹(函数原型)

/dev  设备文件夹

/home 普通用户文件夹

/root  管理员的家目录  

/usr/include 存放头文件

touch: 文件不存在进行创建

gedit: 编辑器

echo: 显示内容到屏幕

> 输出重定向   作用等同 fopen的w方式

adduser 新用户名

printf 输出缓冲区:

满足以下条件之一才可以显示到屏幕:

1》\n

2》程序正常结束

3》清理函数 fflush(stdout)

4》缓冲区满, 行缓冲区大小是1024字节

fopen  打开方式:

r:只读 文件不存在打开失败,光标在文件开头

r+:读写

w:只写 文件不存在,新建文件;文件存在,内容截取为0

光标在文件开头

w+:读写

a:追加的写 文件不存在,新建文件;文件存在,在文件末尾添加

光标在文件末尾

a+:读和追加的写

stdout:标准输出(屏幕)

stdin:标准输入(键盘)

stderr:标准出错 (屏幕)

ssize_t read(int fd, void *buf, size_t count);

形参:

fd:文件描述符

buf:读取内容存放的位置

count:要读取的字节数

返回值:

成功返回读取的字节数

失败返回-1

光标偏移函数

off_t lseek(int fd, off_t offset, int whence);

形参:

fd:文件描述符

offset:偏移量

whence:3个可用的宏

SEEK_SET:文件开头

SEEK_CUR:当前位置

SEEK_END:文件末尾

返回值:

返回光标距离文件开头的偏移量

time_t time(time_t *tloc);

函数功能:获取从1970-1-1 0:0:0 到现在的秒数值

形参:可以直接给NULL

如果tloc不是NULL,获取的秒数值存放到tloc指向的空间

返回值:返回距今的秒数值

char *ctime(const time_t *timep);

函数功能:返回日历时间字符串

struct tm *localtime(const time_t *timep);

函数功能:获取本地时间的描述

获取格林威治时间的描述

struct tm *gmtime(const time_t *timep);

参数、返回值使用和localtime一样

打开目录

DIR *opendir(const char *name);

形参:name:目录名

返回值:成功返回目录描述符,失败返回NULL

读取目录内容

struct dirent *readdir(DIR *dirp);

形参:dirp:目录描述符

返回值:返回读取到的文件信息

读到目录末尾或者失败返回NULL

注意:调用一次readdir 只能读到一个文件的信息

获取当前工作路径

char *getcwd(char *buf, size_t size);

形参:buf:获取的路径存放到buf指向的空间

size:buf的最大空间

返回值:返回获取的路径

char *getwd(char *buf);

形参:buf:获取的路径存放到buf指向的空间

返回值:返回获取的路径

目录跳转

int chdir(const char *path);

形参:path:要跳转到的路径

返回值:成功返回0 失败返回-1

进程概念

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

进程:正在运行的程序的实例, 是程序从执行到凋亡的过程   内存

程序:功能的描述    磁盘

动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的

并发性:任何进程都可以同其他进程一起并发执行

独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位

异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推

进程标识符  PID

1)PID是进程在系统中的唯一的标识

pid_t getpid(void);

函数功能:获取进程的ID

2)pid_t getppid(void);

函数功能:获取父进程的ID

结束进程

  1. 通过终端操作

1》前台指令  ctrl + c

2》利用信号  kill -9 进程号

  1. 程序中结束进程

1》return

2》exit

void exit(int status);

形参:填写进程退出状态,一般给0 表示正常退出

进程结束时 会清理I/O缓存

3》_exit

void _exit(int status);

形参:填写进程退出状态,一般给0 表示正常退出

进程结束时 不会清理I/O缓存

创建进程

1》通过终端指令 将程序加载到内存

例如:./a.out

2》通过API创建进程

pid_t fork(void);
返回值:成功 在父进程中返回子进程的进程号,在子进程中返回0

失败返回-1

fork函数特点:

父子进程先后执行顺序不确定

fork 子进程复制父进程资源,子进程拥有独立的空间(数据段、堆栈,代码段共享的)

进程等待

1)pid_t wait(int *wstatus);

阻塞等待子进程结束

形参:wstatus是传出参数,函数执行成功之后,wstatus指向的空间被自动填充对应进程的退出状态,一般给NULL

返回值:成功返回结束的子进程号 失败返回-1

pid_t waitpid(pid_t pid,  int  *wstatus,  int  options);

形参:

pid:  -1 任意子进程

>0 指定子进程

wstatus:传出参数,函数执行成功之后,wstatus指向的空间被自动填充对应进程的退出状态,一般给NULL

options: 0 表示阻塞

 WNOHANG 表示非阻塞

waitpid(-1, &wstatus, 0); 等价 wait(&wstatus)

返回值:

options=0;成功返回等待到的子进程号 失败返回-1

options=WNOHANG; 成功 子进程没有结束,返回0;子进程结束,返回对应的进程号 失败返回-1

fork特点:

父子进程执行的先后顺序无法确定

子进程复制父进程资源,子进程拥有自己独立的空间

创建进程 vfork

pid_t vfork(void);

  1. a child process and block parent

返回值:成功在父进程中返回子进程的进程号,在子进程中返回0

失败返回 -1

注意:子进程结束需要调用_exit、exit或者exec函数族

exec函数族

exec函数族称为偷梁换柱,在一个进程中执行另一个进程(用一个新的进程镜像替换当前进程镜像)

int execl(const char *pathname, const char *arg, .../* (char  *) NULL */);

int execlp(const char *file, const char *arg, .../* (char  *) NULL */);

int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);

int execv(const char *pathname, char *const argv[]);

int execvp(const char *file, char *const argv[]);

int execvpe(const char *file, char *const argv[],char *const envp[]);

l:list  执行可执行文件时 参数需要一一列举

int raise(int sig);

函数功能:给当前进程发送信号

等价于 kill(getpid(), sig);

3)unsigned int alarm(unsigned int seconds);

函数功能:在seconds秒之后给自己发送一个14(SIGALRM)信号

SIGCHLD信号

17信号用于父子进程,产生条件:

1.子进程结束

2.子进程遇到19信号,暂停

3.子进程从暂停状态恢复,遇到18信号

管道

以水管为例,一端确定之后,另一端也固定  为半双工通信

单工通信:只能单向传输 例如:收音机

半双工通信:支持双向通信,但是同一时刻只能单向传输,例如:对讲机

全双工通信:同一时刻双向传输  例如:打电话

管道为空,读会发生阻塞

管道为满,写会发生阻塞

管道分为无名管道和有名管道

无名管道

适用于亲缘关系的进程

int pipe(int pipefd[2]);

形参:

pipefd是传出参数,函数调用成功之后,pipefd中存放两个文件描述符,用于管道的读写;pipefd[0] 对应管道的读端 pipefd[1]对应的是管道的写端

返回值:成功返回0 失败返回-1

无名管道大小是64KB

无名管道没有实质文件存在,以缓存区作为管道空间

有名管道

适用于任意进程间通信

有名管道有真实文件存在,该文件为FIFO文件

普通文件 内容一直存放在文件中

FIFO文件 内容读走之后就不存在了 

1》创建管道

  1. 终端

mkfifo 管道名

注意:有名管道两端同时打开,才可以使用,只有一端打开时, open 阻塞

管道:分为无名管道和有名管道

管道两端同时打开的情况下,读取空管道,读取会阻塞;向满管道中写入内容,写会发生阻塞

管道特点:无序字节流

IPC 是systemV系统中的通信方式,IPC共有3中通信方式:共享内存、信号量集和消息队列

键值

系统中会有多个共享内存,区分具体共享内存,使用键值(key)

key_t ftok(const char *pathname, int proj_id);

形参:

pathname:存在的路径

proj_id:不能为0

返回值:

成功返回键值 失败返回-1

共享内存操作

1)创建/获取共享内存

int shmget(key_t key, size_t size, int shmflg);

形参:

key:键值

size:共享内存大小,单位:字节

shmflg:IPC_PRIVATE --- 意味着该共享内存仅在亲缘关系进程间可见

IPC_CREAT|权限 -- 表示共享内存可以在任意进程间可见

返回值:成功返回标识符(后期操作共享内存的标记),失败返回-1

将共享内存映射到当前进程空间

void *shmat(int shmid, const void *shmaddr, int shmflg);

形参:

shmid:shmget得到的返回值

shmaddr:映射到当前进程的地址区域,一般给NULL(由系统分配可用的地址)

shmflg:一般给0 表示可以读写

返回值:成功返回映射的地址,失败返回(void *)-1

  1. 内存拷贝

void *memcpy(void *dest, const void *src, size_t n);

功能:将src内容写入到dest空间中,写入n字节

  1. 解除映射

int shmdt(const void *shmaddr);

形参:shmaddr: shmat的返回值

注意:解除映射 只是断开当前进程和共享内存的联系,但是共享内存依旧存在

  1. 控制共享内存

int shmctl(int shmid, int cmd, struct shmid_ds*buf);

函数功能:取决于cmd,不同的cmd 函数功能不一样

cmd:

IPC_STAT 获取共享内存属性,获取的属性存放到buf指向的空间

IPC_SET 设置共享内存属性,此时需要填充buf结构体

IPC_RMID 删除共享内存 此时第三个参数忽略,填NULL

信号量集

信号量集 (Semaphore Set) 是一种用于多进程或多线程同步和互斥的进程间通信机制。它可以用来协调系统中不同进程或线程之间的操作,避免发生资源竞争等问题。

具体地说,信号量集包含了多个信号量 (Semaphore),每个信号量都是一个计数器,用来表示某种资源的可用数量。当某个进程或线程需要访问该资源时,需要先通过信号量集来申请相应的资源,并进行相应的操作。此时,信号量的值会减少,表示该资源已经被占用。当进程或线程使用完该资源后,需要通过信号量来释放该资源,使得其他进程或线程可以继续使用。

信号量集提供了多种操作,包括创建、打开、获取、设置和删除信号量等,可以通过系统调用函数 (如 semget, semop, semctl) 来实现。其中,semget 用于创建或打开一个信号量集,semop 用于获取或释放信号量,semctl 用于对信号量进行设置或删除等操作。

使用信号量集可以避免多个进程或线程同时访问共享资源而导致的竞争和冲突。但是,它也存在一些问题,例如可能会导致死锁、产生饥饿等问题,因此需要设计合理的同步机制来避免这些问题的发生。

信号量本质是可以用于多进程间的标志位

使用资源对信号量值-1,如果信号量值为0,此时程序阻塞,直到值大于0;使用完毕对信号量的值+1;

3.1 查看当前系统的信号量集

信号量集作用:实现同步和互斥

互斥:在同一时刻只能有一个进程/线程操作临界资源

同步:程序按照一定的顺序执行,例如 写入完成之后,才可以读取

信号量初始值定义1,该信号量称为二值信号量

PV操作: P操作对信号量值-1(消耗)  V操作对信号量值+1(还原)

多个信号量叫做信号量集

查看当前系统的信号量集

3.2 信号量集操作

1)创建/获取信号量集 标识符

int semget(key_t key, int nsems, int semflg);

形参:

key:键值

nsems:集合中信号量个数

semflg:

IPC_PRIVATE 此时信号量集仅用于亲缘关系

IPC_CREATE|权限 用于任意进程

返回值:成功返回标识符 失败返回-1

2)信号量集控制函数

int semctl(int semid, int semnum, int cmd, ...);

形参:

semnum:表示第几个信号量(信号量集中信号编号从0开始的)

cmd:

创建/获取消息队列标识符

int msgget(key_t key, int msgflg);

形参:

key:键值

msgflg:

IPC_PRIVATE   IPC_CREAT

返回值:成功返回标识符 失败返回-1

向消息队列写入消息

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

形参:

msqid:标识符

msgp:需要自定义结构体 (该结构体第一个成员必须是long型,其他成员自行决定)

struct msgbuf {

     long mtype;       /* message type, must be > 0 */

     char mtext[1];    /* message data */

};

msgsz:消息的大小 sizeof(struct msgbuf)-sizeof(long)

msgflg:一般给0 表示阻塞

读取消息队列中的消息

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

形参:

msqid、msgp、msgsz、msgflg参考msgsnd参数介绍

msgtyp:指明接受的消息的类型

0 获取消息队列中第一条消息

>0 获取对应类型中的第一条消息

消息队列控制函数

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

cmd:

IPC_RMID 删除消息队列 第三个参数给NULL

IPC_STAT 获取消息队列属性  传出参数,属性自动填充到第三个参数指向的空间

线程 

线程是系统程序执行的基本单位,一个进程中可以有多个线程

进程中至少会有一个线程(main线程)

同一个进程中的多线程 进程号一样的

同一个进程中的多线程 共享进程资源,但是拥有自己独立的栈区

线程的执行顺序不确定的

有关线程的API 全部以pthread开头

2.1 线程创建

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

                          void *(*start_routine) (void *), void *arg);

形参:

thread: 传出参数,线程创建成功之后 该控件被填充线程号

attr: 线程属性 一般给NULL,表示使用线程的默认属性

start_routine:线程入口函数

arg:传递给start_routine所指向的函数的参数

返回值:成功返回0 失败返回错误码

获取线程号

pthread_t pthread_self(void);

2.3 线程等待函数

int pthread_join(pthread_t thread, void **retval);

形参:

thread: 要等待的线程号

retval:承接线程退出时的值,如果不需要,给NULL

2.4 线程退出 

结束当前线程

void pthread_exit(void *retval);

2.5 线程取消函数 

int pthread_cancel(pthread_t thread);

形参:

thread:要结束的线程号

2.6 线程清理函数

清理函数成对函数 pthread_cleanup_push、 pthread_cleanup_pop

1>向栈中压入清理函数

void pthread_cleanup_push(void (*routine)(void *),void *arg);

形参:

routine:清理函数的入口

arg:传递给清理函数的参数

2>清理函数的出栈

void pthread_cleanup_pop(int execute);

形参:

execute: 给0 表示清理函数不调用

给非0 表示清理函数会调用

如果线程中使用pthread_exit或者其他线程使用pthread_cancel结束当前线程时,execute无论是多少,清理函数都会执行

2 线程互斥和同步

2.1互斥锁 

查看相关手册,需要在终端安装:sudo apt-get install manpages-posix-dev

1》 锁的初始化

动态初始化锁

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

形参:

mutex:传出参数 函数执行成功之后,mutex中存放锁的信息

attr:锁的属性,一般给NULL

静态初始化锁

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2》 加锁

阻塞加锁:如果锁已经上锁,此时pthread_mutex_lock会阻塞,直到锁释放

int pthread_mutex_lock(pthread_mutex_t *mutex);

非阻塞加锁:如果锁已经处于上锁状态,函数直接返回

int pthread_mutex_trylock(pthread_mutex_t *mutex);

3》 解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

4》销毁锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

2.2 条件变量

条件不满足,当前线程阻塞,直到条件满足,其他线程唤醒条件变量

1》初始化条件变量

动态初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

静态初始化:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2》等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

3》唤醒条件变量

int pthread_cond_signal(pthread_cond_t *cond);

4》销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

2.3 信号量 

1》初始化

int sem_init(sem_t *sem, int pshared, unsigned int value);

形参:

sem:传出参数 函数成功之后sem被填充

pshared: 给0

value:信号量初始值

2》消耗 P操作 信号量值-1

int sem_wait(sem_t *sem);

3》还原 V操作 信号量值+1

int sem_post(sem_t *sem);

4》信号量销毁

int sem_destroy(sem_t *sem);

3 线程通信 

3.1 全局变量

由于线程共享进程资源,可以通过全局变量实现通信

3.2 信号

给线程发送信号

int pthread_kill(pthread_t thread, int sig);

信号处理 依旧使用signal,信号处理方式依旧是 捕获、忽略、默认处理

3端口 (理解)

一台计算机可以同时提供多种网络服务,例如 Web 服务(网站)、FTP 服务(文件传输服务)、SMTP 服务(邮箱服务)等,仅有 IP 地址,计算机虽然可以正确接收到数据包,但是却不知道要将数据包交给哪个网络程序来处理,所以为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(Port Number)

端口的数据类型:unsigned short 范围:0-65535

端口分为

  1. 知名端口号

知名端口号是系统程序使用的端口号. 知名端口范围从 0 到 1023.

  1. 动态端口号

动态端口号是普通程序使用的端口号. 动态端口的范围是从 1024 到 65535. 当这个程序关闭时, 同时也就释放了所占用的端口号.

4 套接字 (理解)

4.1 什么是套接字

套接字是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

socket 的典型应用就是 Web 服务器和浏览器:浏览器获取用户输入的 URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。

UNIX/Linux 中的 socket 是什么?UNIX/Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。
请注意,网络连接也是一个文件,它也有文件描述符!我们可以通过 socket() 函数来创建一个网络连接,或者说打开一个网络文件,socket() 的返回值就是文件描述符。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,网络编程原来就是如此简单!

4.2 套接字主要分类

1)流套接字(SOCK_STREAM)

流套接字用于提供面向连接、可靠的数据传输服务,在代码中使用 SOCK_STREAM 表示。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议

2)数据报套接字(SOCK_DGRAM)

数据报套接字提供一种无连接的服务,在代码中使用 SOCK_DGRAM 表示。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP( User DatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

3)原始套接字(SOCK_RAW)

原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接。

5.1 TCP 

TCP 是面向连接的传输协议,建立连接时要经过三次握手,断开连接时要经过四次握手,中间传输数据时也要回复 ACK 包确认,多种机制保证了数据能够正确到达,不会丢失或出错。

  1. TCP的3次握手过程

第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据

主要原因:防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误

如果采用两次握手会出现以下情况:

客户端向服务器端发送的请求报文由于网络等原因滞留,未能发送到服务器端,此时连接请求报文失效,客户端会再次向服务器端发送请求报文,之后与服务器端建立连接,当连接释放后,由于网络通畅了,第一次客户端发送的请求报文又突然到达了服务器端,这条请求报文本该失效了,但此时服务器端误认为客户端又发送了一次连接请求,两次握手建立好连接,此时客户端忽略服务器端发来的确认,也不发送数据,造成不必要的错误和网络资源的浪费。

如果采用三次握手的话,就算那条失效的报文发送到服务器端,服务器端确认并向客户端发送报文,但此时客户端不会发出确认,由于客户端没有确认,由于服务器端没有接收到确认,就会知道客户端没有请求连接。

为什么不是四次?如果三次就能够确定正常连接,就没有必要在进行确认,来浪费资源了

  1. TCP四次挥手过程
  2. TCP四次挥手过程
  3. 数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。

    第一次挥手 客户端发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态

    第二次挥手 服务器端接收到连接释放报文后,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT 关闭等待状态

    第三次挥手 客户端接收到服务器端的确认请求后,客户端就会进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

    第四次挥手 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态,但此时TCP连接还未终止,必须要经过2MSL后(最长报文寿命),当客户端撤销相应的TCB后,客户端才会进入CLOSED关闭状态,服务器端接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成

  4. 挥手
  5. 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

  6. TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
  7. 虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。

第一次挥手 客户端发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态

第二次挥手 服务器端接收到连接释放报文后,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT 关闭等待状态

第三次挥手 客户端接收到服务器端的确认请求后,客户端就会进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次挥手 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态,但此时TCP连接还未终止,必须要经过2MSL后(最长报文寿命),当客户端撤销相应的TCB后,客户端才会进入CLOSED关闭状态,服务器端接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成

  • 挥手

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

  • TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

5.2 UDP

TCP                                                                                UDP

面向连接                                                                 非面向连接

可靠传输(无差错、不重复、按顺序到达)                   不保证数据可靠传输

传输速率较慢                                                                 传输速率较快

一对一                                                                                 一对一 一对多  多对多

2.1单播

消息发送者:创建套接字,知道对方的IP和端口就可以发送消息

消息接收者:创建套接字,绑定自身的IP和端口

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

type:SOCK_DGRAM

2>发送数据

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,

   const struct sockaddr *dest_addr, socklen_t addrlen);

形参:

dest_addr:存放的是对方的IP和端口

3>接收数据

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,

   struct sockaddr *src_addr, socklen_t *addrlen);

形参:

src_addr:是传出参数 保存的是消息的发送者的信息

2.2广播

广播:处于局部网中的所有终端都可以接收到消息 比如:校园广播

广播使用的特殊的IP地址:最后一位是255时的IP地址是给广播预留的IP地址

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

函数功能:设置套接字的选项

参数: sockfd: socket创建的套接字

level: 级别

SOL_SOCKET : 通用套接字

optname: 选项 由level决定

SO_BROADCAST 允许发送广播数据

optval: 设置参数 由optname决定

当optname为:

SO_REUSERADDR/SO_BROADCAST  optval 是bool类型值

0--失能(关闭此功能,系统默认关闭)

1--使能(开启此功能)   

optlen:optval的大小

2.3组播

多播(组播):对一组特定的主机发送消息 比如:直播

D类IP: 224.0.0.0~239.255.255.255

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

函数功能:设置套接字的选项

参数: sockfd: socket创建的套接字

level: 级别

IPPROTO_IP :IPv4 接口 例如组播设置

optname: 选项 由level决定

IP_ADD_MEMBERSHIP  加入组播

IP_MULTICAST_IF    创建组播

   

optval: 设置参数 由optname决定

struct ip_mreqn {

struct in_addr imr_multiaddr; /* 组播地址 */

struct in_addr imr_address;   /* 当前设备地址 */

int            imr_ifindex;   /* 物理地址 */

           };

物理硬件ID函数: if_nametoindex("ens33");

  

optlen:optval的大小

2 多路IO复用

I/O多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
    这个机制能够通过select/poll/epoll等来使用。这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的 I/O 操作都能在一个线程内并发交替地顺序完成

2.1 select

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

形参:

nfds:最大文件描述符+1

readfds:监测的读事件 描述符集合,例如TCP服务器端 只需要监测的读变化

writefds:监测的写事件 描述符集合,如果不需要监测对应的变化,赋 NULL

exceptfds:监测的异常事件 描述符集合

timeout:如果填写NULL,表示有描述符发生改变时 函数才会返回,没有描述符改变,该函数一直阻塞

如果填充该参数,只会在指定时间内监测

返回值:

成功返回发生变化的描述符的个数,失败返回-1

如果timeout不是NULL,超时时间到之后,没有描述符改变返回0

注意:集合中描述符发生改变之后,自动从集合中删除

相关函数:

void FD_CLR(int fd, fd_set *set);//将fd从集合中删除

int  FD_ISSET(int fd, fd_set *set);//判断集合中是否是fd发生改变

void FD_SET(int fd, fd_set *set);//将fd添加到集合中

void FD_ZERO(fd_set *set);//清空集合

2.2 epoll

发生改变的描述符可以直接得到

epoll 没有文件描述符个数限制

1》创建epoll句柄

int epoll_create(int size);

形参:Since  Linux  2.6.8, the size argument is ignored, but must be greater than zero;

返回值:

成功返回句柄,失败返回-1

2》设置epoll

int epoll_ctl(int epfd, int  op,  int  fd,  struct epoll_event *event);

形参:

epfd:epoll_ceate 的返回值

op:

EPOLL_CTL_ADD //添加事件

EPOLL_CTL_MOD//修改事件

EPOLL_CTL_DEL//删除事件

Epoll events:

EPOLLIN  读

EPOLLOUT 写

EPOLLERR 出错

返回值:成功返回0 失败返回-1

3》等待事件发生

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

形参:

events:传出参数 epoll_wait函数结束,events被填充发生变化的相关事件

maxevents:监测的最大事件个数

timeout:

>0 有限时间内的监测轮询

=0 非阻塞

-1 阻塞

返回值:返回发生变化的个数

2.3 poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

形参:

fds:结构体数组,关于监测的描述符的具体描述

events和revents可用值:

nfds:监测的描述符的个数

timeout:0 表示非阻塞

>0 指定毫秒数内监测

-1 阻塞等待描述符变化

返回值:成功返回描述符发生改变的个数

超时并且没有描述符发生改变返回0

失败返回-1

poll示例:

2 MySQL数据库

数据库:存储数据的仓库  组织、存储和管理数据的仓库

关系型数据库: 将复杂的数据结构归属为简单的二元关系(二维表格)

存储数据的基本单位是表格,表格存在于数据库

Mysql官网:MySQL :: MySQL Documentation

1》登录数据库

mysql -u root -h localhost -p

mysql终端输入指令 末尾带;

退出MySQL:  exit /  quit  /  \q

登录指令:

缺省-u 默认使用root登录

缺省-h 默认使用localhost

但是不能缺省-p

2》清理当前屏幕内容: ctrl + c  /  system clear

3》创建用户

create user ‘用户名’@‘主机名’ identified by ‘密码’;

4》删除用户

drop user ‘用户名’@‘主机名’;

5》设置/修改密码

set password = ‘新密码’; //修改登录者的密码

set password for ‘用户名’@’主机名’ = ‘新密码’

6》显示已有的数据库

show databases;

7》创建数据库

create database 库名;

8》使用数据库

use 库名;

9》显示数据库中已有的表

show tables;

10》删除数据库

drop database 库名;

11》创建表

create table 表名(列名1 列类型,列明2 列类型....)

12》显示表的框架(表头)

show columns from 表名;

describe 表名;

13》向表中添加数据

  1. insert into表名 values( , ); //向表格中插入一行信息
  2. 表格复制
  3. insert into 目标表名(列名1,列名2....) select列名,列名...from 源表名

  4. 14》显示表中数据

    select * from 表名;//显示表中所有数据

  5. 15》删除表中数据

    delete from 表名 where 条件

16》修改表中数据

update 表名 set 更新的数据 where 条件;

17》修改表的索引(表头)

  1. 添加一列

alter table 表名 add 列名 列类型

  1. 删除列

alter table 表名 drop 列名;

连接MySQL服务器

Before calling mysql_real_connect(), call mysql_init() to initialize the MYSQL structure

形参:

mysql: mysql_init函数返回值

host:主机名

user:登录MySQL的用户名

passwd:user对应的密码

db:数据库

port:一般赋0

unix_socket:一般给NULL

client_flag: 赋值为0

返回值:成功返回MYSQL* 数据,失败返回NULL

3》执行SQL指令

形参:

mysql:mysql_init函数返回值

stmt_str: 所要执行的指令

length:stmt_str的长度

返回值等于0执行成功

4》MySQL出错打印

返回值:失败返回错误描述,成功返回空字符串

5》存放查询结果

在mysql_real_query执行select、show、describe语句之后必须调用mysql_store_result函数存储结果

6》获取结果集的行数

7》获取结果集的列数

8》检索结果集的下一行

返回值:成功返回下一行信息,检索到没有数据 返回NULL;失败返回NULL

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值