IP地址
1.1 特点
1.IP地址是internet中的主机表示,用二进制实现
2.internet中的主机要跟别的主机通信必须要有一个IP地址,通过IP区分主机
3.IP地址 IPV4(32位) IPV6(128位)
4.每个数据包必须携带源IP和目标IP
5.表示方法是 点分十进制,是由四个0-255的数组成的,每一个数相当于一个八位二进制
- 二级划分
2.1 普通地址
1. A类:(0.0.0.0-127.255.255.255)(子网掩码:255.0.0.0或 0xFF000000)
2. B类:(128.0.0.0-191.255.255.255)(子网掩码:255.255.0.0或0xFFFF0000)
3. C类:(192.0.0.0-223.255.255.255)(子网掩码:255.255.255.0或 0xFFFFFF00)
4. D类:(224.0.0.0- 239.255.255.255)是多播地址。
5. E类:是保留地址。该类IP地址的最前面为“1111”,所以地址的取值取值于240~247之间。
2.2 特殊地址
1. 0.0.0.0: 在服务器中, 0.0.0.0指的是本机上所有的IPV4地址(自动匹配本机上的所有IPV4地址)
2. 127.0.0.1:回环地址/本机地址,一般用来测试使用,所有发往该类地址的数据包都应该被原样送回;
3. 网络地址: 每一个网段主机号全为0的地址: 192.168.50.0 网络地址
4. 广播地址: 主机号最大的地址为该网段的广播地址
5. 全网广播地址: 255.255.255.255: 这个地址不能随便用 , 会影响整个网络 - 类似于国家主席
- 子网掩码
3.1 特点
1. 子网掩码长度于IP地址一样 32位/四个字节
2. 网络号全为1 主机号全为0
3.2 作用
将一个IP划分为网络地址和主机地址
- 三级划分
二级地址:IP = 网络号+主机号
三级地址:IP = 网络号+子网掩码+主机号
- 网络模型
5.1 OSI模型
1. 物理层 将二进制转换为高低电平
2. 数据链路层 将数据包转化为数据帧并检测
3. 网络层 源IP目标IP 寻址
4. 传输层 进程标识:一个用来接收一个用来发送
5. 会话层 链接其他主机
6. 表示层 加密、格式化数据
7. 应用层 有很多API接口
5.2 TCP/IP模型
1. 应用层 应用协议和应用程序的集合
2. 传输层 决定数据交给机器的哪个任务(进程)去处理,通过端口寻址
3. 网络层 提供端对端的传输,可以理解为通过IP寻址机器
4. 物理和数据链路层 屏蔽硬件差异(驱动),向上层提供统一的操作接口
- UDP和TCP
6.1共同点
都是在传输层,都是全双工通信
6.2 TCP传输协议
是一种面向连接的可靠通信协议
传输到一般没网了就得重新开始
例如QQ微信的登录或者改密码界面
6.3 UDP传输协议
是一种无连接的不可靠通信协议
传输到一办没网了还可以继续传输
例如是QQ微信的视频通话
- socket套接字
7.1 概念
1.可以认为是一个编程接口
2.也可以认为是一种特殊的文件描述符
3.socket是一种通信机制,并不仅限于TCP/IP协议
4.面向连接
5.无连接
7.2 功能
在内核空间创建两个缓存区,一个接收缓存区,一个发送缓存区,用户可以接收到块空间的文件描述符
7.3 类型
流式套接字:提供一个面向链接、可靠的数据传输服务,数据无差错、无重复的发送且发送顺序接收。
数据套接字:提供无连接服务
原始套接字
7.4 端口号port
1.为了区分一台主机接收到的数据包应该给哪个进程来进行处理,使用端口号来区别
(通过 IP地址 找到哪台主机 通过 port端口号 来找到哪台主机的哪个进程)
2.TCP端口号与UDP端口号独立 (UDP port为8888,TCP port也可为8888 )
3.端口号一般由IANA (Internet Assigned Numbers Authority) 管理
4.端口(sin_prot)用2个字节表示 2byte (IP地址占4个字节)
众所周知端口(被占用的端口): 1~1023 (1~255之间为众所周知端口,通常由UNIX系统占用) 比如 文件传输端口 TFTP 端口号为 69
已登记端口:1024~49151 (----可用来建立与其它主机的会话----)
动态或私有端口:49152~65535 --固定某些服务使用--
7.5 字节序(大小端的判断)
小端 低地址存放低字节
大端 低地址存放高字节
7.6 端口地址转换
网络传输中,使用的是网络字节序
终端输入打印 使用的是主机字节序
htonl/htons主机字节序(host)到网络(network)字节序
头文件: #include<arpa/inet.h>
u_long htonl (u_long hostlong) //long 4byte - 端口号一般为4byte,基本用不到这个函数
u_short htons (u_short short) //short 2byte - 端口号2byte, 一般使用这个函数
ntohl/ntohs 网络字节序到主机字节序
头文件: #include<arpa/inet.h>
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short); //short 2byte - 端口号2byte, 一般使用这个函数
7.7 IP地址转换
inet_addr() 主机字节序 转化为 网络字节序
头文件: #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h>
声明: typedef uint32_t in_addr_t; (通过vi -t 追一下)
struct in_addr {
in_addr_t s_addr;
};
in_addr_t inet_addr(const char *strptr); //该参数是字符串
功能: 主机字节序(点分十进制形式的IP) 转为网络字节序
参数: const char *strptr: 字符串
返回值: 返回一个无符号长整型数(无符号32位整数用十六进制表示),
否则NULL
7.8 TCP编程
7.8.1 socket 创建套接字
int socket(int domain, int type, int protocol);
头文件: #include <sys/types.h> #include <sys/socket.h>
功能:创建套接字
参数:
domain:协议族
AF_UNIX, AF_LOCAL 本地通信
AF_INET ipv4
AF_INET6 ipv6
type:套接字类型
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
protocol:协议 - 填0 自动匹配底层 ,根据type
系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:
成功 文件描述符 0 -> 标准输入 1->标准输出 2->标准出错 3->socket
失败 -1,更新errno
7.8.2 bind 绑定套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件: #include<sys/types.h> #include<sys/socket.h>
#include<netinet/in.h> #include<netinet/ip.h>
功能:绑定 协议? ip? 端口 ? 让别人识别
参数:
sockfd:套接字
addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信当时socket第一个参数确定,需要强转)
(结构体之间的强转,不可以直接使用普通数据类型强转,因为结构体字节对齐原则,具体大小不一致,会数据丢失,所以可使用结构体指针,对结构体地址进行强转)
addrlen:结构体大小
返回值:成功 0 失败-1,更新errno
通用结构体:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
ipv4通信结构体:
struct sockaddr_in {
sa_family_t sin_family; ----协议族
in_port_t sin_port; ----端口
struct in_addr sin_addr; ----ip结构体
};
struct in_addr {
uint32_t s_addr; --ip地址
};
本地通信结构体:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX 本地通信 */
char sun_path[108]; /* 在本地创建的套接字路径 */
};
7.8.3 listen 监听
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:同一时间可以响应客户端请求链接的最大个数,不能写0.
不同平台可同时链接的数不同,一般写6-8个
返回值:成功 0 失败-1,更新errno
7.8.4 accpt 阻塞等待连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr: 链接客户端的ip和端口号
如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小
如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符; //用于通信
失败:-1,更新errno新errno
7.8.5 recv 接受缓存区的内容
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0,相当于read()函数
MSG_DONTWAIT 非阻塞
返回值:
< 0 失败出错 更新errno
==0 表示客户端退出
>0 成功接收的字节个数
7.9 UDP 编程
7.9.1 函数接口(recvfrom sendto)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
sockfd:套接字描述符
buf:接收缓存区的首地址
len:接收缓存区的大小
flags:0 接收数据 并 阻塞
MSG_DONTWAIT: 设置非阻塞
src_addr: 发送端的网络信息结构体的指针(对方的 caddr)
addrlen:发送端的网络信息结构体的大小的指针(对方的 caddr)
返回值:
成功接收的字节个数
接收到的数据为0 : 0
失败:-1
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
参数:
sockfd:套接字描述符
buf:发送缓存区的首地址
len:发送缓存区的大小
flags:0 发送消息并阻塞
src_addr:接收端的网络信息结构体的指针
addrlen:接收端的网络信息结构体的大小
返回值:
成功发送的字节个数
失败:-1
Linux下的四种IO模型
- 阻塞式IO
阻塞式IO 是 最普遍的IO
特点:最简单,最常用,但是效率低
目前学习函数中,有阻塞功能的如下:
读阻塞: read , recv , recvfrom
写阻塞: write , send
accept connect
TCP: 有链接 : 有发送缓存区,有接收缓存区
UDP: 无连接 : 没有发送缓存区,但是有接受缓存区 (不会出现TCP粘包)
- 非阻塞式IO
特点: 当一个应用程序使用了非阻塞式的套接字,则需要使用一个循环不断地测试文件是否有数据可读, 这样应用程序不断地测试,CPU不断地去轮询, 会占用大量的CPU资源
fcntl函数
声明: int fcntl (int fd, int cmd, ...arg);
头文件: #include<fcntl.h> #include<unistd.h>
功能:设置文件描述符的属性
参数:fd:文件描述符
cmd: 操作功能选项 (可以定义个变量,通过vi -t F_GETFL 来找寻功能赋值 )
F_GETFL:获取文件描述符的状态信息
//不需要第三个参数,返回值为获取到的属性
F_SETFL:设置文件描述符的状态信息 - 需要填充第三个参数
//需要填充第三个参数 O_RDONLY, O_RDWR ,O_WRONLY ,O_CREAT
O_NONBLOCK 非阻塞 O_APPEND追加
O_ASYNC 异步 O_SYNC 同步
O_NOATIME 读取文件时不更新文件访问时间
arg:文件描述符的属性 如果需要设置文件描述符的状态,则需要该参数
返回值: 特殊选择:根据功能选择返回 (int 类型)
其他: 成功0 失败: -1;
使用: int flag;
// 1.获取该文件描述符0 (标准输入) 的原属性 :标准输入原本具有阻塞的功能
int flag = fcntl(0, F_GETFL); //获取文件描述符原有信息后,保存在flag变量内
// 2.修改对应的位nonblock(非阻塞)
int flag |= O_NONBLOCK; ( flag = flag | O_NONBLOCK)
// 3. 将修改好的属性写回去 (0 标准输入 -- 阻塞 改为 非阻塞)
fcntl (0, F_SETFL, flag); //文件描述符 设置状态 添加的新属性
- 信号驱动
特点: 异步通知模式, 需要底层驱动的支持
//1.设置将APP进程号提交给内核驱动
fcntl(fd,F_SETOWN,getpid());//F_SETOWN将进程号交给内核驱动
//getgid 进程号
//2.设置异步通知
int flags;
flags = fcntl(fd, F_GETFL); //获取原属性
flags |= O_ASYNC; //设置异步 O_ASUNC 通知
fcntl(fd, F_SETFL, flags); //修改的属性设置进去
//3.signal捕捉SIGIO信号 --- SIGIO:信号驱动,自定义信号驱动
signal(SIGIO,handler);
头文件: #include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler)
功能:信号处理函数(注册信号)
参数: int signum:要处理的信号(要修改的信号)
sighandler_t handler: 函数指针: void(*handler)(int) (修改的功能:)
SIG_IGN:忽略该信号。
SIG_DFL:采用系统默认方式处理信号。
handler:------void handler(int num) 自定义的信号处理函数指针
返回值:成功:设置之前的信号处理方式
失败:SIG_ERR
IO多路复用
4.1 selct
头文件: #include<sys/select.h> #include<sys/time.h>
#include<sys/types.h> #include<unistd.h>
声明: int select(int nfds, fd_set *readfds, fd_set *writefds,\
fd_set *exceptfds, struct timeval *timeout);
功能:监测是哪些文件描述符产生事件;,阻塞等待产生.
参数:nfds: 监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得+1)
readfds: 读事件集合; // 键盘鼠标的输入,客户端连接都是读事件
writefds: 写事件集合; //NULL表示不关心
exceptfds:异常事件集合; //NULL 表示不关心
timeout: 超时检测 //如果不做超时检测:传 NULL
超时时间检测: 当程序执行到该语句时,我们设定好时间,如果规定时间 内未完成函数功能, 返回一个超时的信息,我们可以根据该信息设定相应需求;
返回值: <0 出错 >0 表示有事件产生;
------------ 如果设置了超时检测时间:&tv ------------
<0 出错 >0 表示有事件产生; ==0 表示超时时间已到;
超时时间检测的结构体如下:
struct timeval {
long tv_sec; 以秒为单位,指定等待时间
long tv_usec; 以毫秒为单位,指定等待时间
};
select附带的 宏函数:
void FD_CLR(int fd, fd_set *set); //将set集合中的fd清除掉
int FD_ISSET(int fd, fd_set *set); //判断fd是否在set集合中产生事件
void FD_SET(int fd, fd_set *set); //将fd加入到集合中
void FD_ZERO(fd_set *set); //清空集合
基本流程:
1. 先构造一张有关文件描述符的表(集合、数组);
2. 清空表
3. 将你关心的文件描述符加入到这个表中;
4. 调用select函数。
(当这些文件描述符已准备好进行I/O操作的时候,,函数才返回(阻塞))
5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);
6. 做对应的逻辑处理;
Select特点:
1. 一个进程最多只能监听1024个文件描述符 (千级别) 0 - 1023
2. select被唤醒之后要重新轮询(0-1023)一遍驱动,效率低(消耗CPU资源)
3. select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低
(一个进程0~4G:0~3G是用户态,3G~4G是内核态,两个状态来回切换 拷贝是非常耗时,耗资源的) ;
规则:
1. 头文件检测1024个文件描述符 0-1023
2. 在select中0~2存储 标准输入、标准输出、标准出错
3. 监测的最大文件描述个数为fd+1(如果fd = 3,则最大为 4) : //因为从0开始的
4. 事件产生,产生的文件描述符会置1;没有产生事件的会置0 (轮询)
5. select每次轮询都会清空表(置零的清空) //需要在select前备份临时表
4.2 poll
特点:
1. 优化文件描述符个数的限制;
(根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组元素个数为1,如果想监听100个,那么这个结构体数组的元素个数就为100,多少文件描述符由程序员自己来决定)
2. poll被唤醒之后需要重新轮询一遍驱动,效率比较低(消耗CPU)
3. poll不需重新构造文件描述符表(也不需清空表),只需要从用户空间向内核空间拷贝一次数据(效率相对比较高)
流程:
1.先创建结构体数组
2.添加文件描述符以及触发方式
3.保存数组内最后一个有效元素的下标
4.调用函数poll
5.判断表内是否有当前文件描述符
6.根据不同的文件描述符触发不同事件
声明:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
头文件: #include<poll.h>
功能: 监视并等待多个文件描述符的属性变化
参数:
1.struct pollfd *fds: 关心的文件描述符数组,大小自己定义
若想检测的文件描述符较多,则建立结构体数组struct pollfd fds[N];
struct pollfd{
int fd; //文件描述符
short events; //等待的事件触发条件----POLLIN读时间触发
short revents; //实际发生的事件(未产生事件: 0 ))
}
2. nfds: 最大文件描述符个数
3. timeout: 超时检测 (毫秒级):1000 == 1s 如果-1,阻塞 如果0,不阻塞
返回值: <0 出错 >0 表示有事件产生;
如果设置了超时检测时间:&tv
<0 出错 >0 表示有事件产生; ==0 表示超时时间已到;
4.3 epoll
select 和 poll 都是同步 (轮询): 一件事一件事的去做
epoll 是 异步(轮询): 多个事情可以同时执行
epoll的提出--》它所支持的文件描述符上限是系统可以最大打开的文件的数目;
eg:1GB机器上,这个上限10万个左右。
每个fd上面(树的节点fd)有callback(回调函数)函数,只有产生事件的fd才有主动调用callback,不需要轮询。
(该回调函数, 就类似于手机的取件短信)
注意:
Epoll处理高并发,百万级,不关心底层怎样实现,只需要会调用就可以。
--- 机制作为了解即可 ---
epoll的机制:
1.红黑树: 是特殊的二叉树(每个节点带有属性),Epoll怎样能监听很多个呢?首先创建树的根节点,每个节点都是一个fd以结构体的形式存储(节点里面包含了callback函数)
2.链表:当某一个文件描述符产生事件后,会自动调用callback函数,通过回调callback函数添加到链表对应的事件(读时间还是写事件),链表为事件链表。
epoll的使用:
1.创建红黑树 - 创建红黑树的根节点
2.添加属性和文件描述符到树上
3.阻塞等待事件的产生,一旦产生事件,则进行处理
4.根据链中准备处理的文件描述符 进行处理
函数接口:
epoll_create
头文件:#include <sys/epoll.h>
声明:int epoll_create(int size);
功能:创建红黑树根节点(创建epoll实例)
返回值:成功时返回epoll文件描述符,失败时返回-1。
epoll_ctl
声明: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制epoll属性
参数:
epfd: epoll_create函数的返回句柄。//一个标识符
op:表示动作类型,有三个宏:
EPOLL_CTL_ADD:注册新的fd到epfd中
EPOLL_CTL_MOD:修改已注册fd的监听事件
EPOLL_CTL_DEL:从epfd中删除一个fd
3. 要操作的文件描述符
4. typedef union epoll_data {
int fd; //要添加的文件描述符
uint32_t u32; typedef unsigned int
uint64_t u64; typedef unsigned long int
} epoll_data_t;
struct epoll_event {
uint32_t events; 事件
epoll_data_t data; //共用体(看上面)
};
event事件:
EPOLLIN: 表示对应文件描述符可读
EPOLLOUT:可写
EPOLLPRI:有紧急数据可读;
EPOLLERR:错误;
EPOLLHUP:被挂断;
EPOLLET:触发方式,边缘触发;(默认使用边缘触发)
ET模式:表示状态的变化;
NULL: 删除一个文件描述符使用,无事件
返回值:成功:0, 失败:-1
epoll_wait
声明: int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
功能:等待事件的产生,类似于select的用法
参数: epfd:句柄;
events:用来保存从链表中拿取响应事件的集合;
maxevents: 表示每次在链表中拿取响应事件的个数;
timeout:超时时间,毫秒,0立即返回 ,-1阻塞
返回值: 成功: 实际从链表中拿出的数目 失败时返回-
三者的特点以及区别
服务器模型
9.1 循环服务器
【1】 循环服务器 (同一时刻,只能连接一个客户端,进行通信)
特点: 循环服务器,同一时刻,只能处理一个客户端请求;
缺点: 循环处理客户端, 不能做耗时动作(一直循环一直工作);
TCP服务器流程:
socket()
bind();
listen()
while(1)
{
accept()
while(1)
{
recv();
}
close(accpetfd);
}
UDP服务器流程:
socket();
bind();
while(1)
{
recvfrom();
}
close();
9.2 并发服务器
1.多进程实现并发(最好不用)
socket()
bind();
listen();
while(1)
{
accept();
if(fork() == 0) //子进程
{
while(1)
{
process();
}
close(client_fd);
exit();
}
else
{
}
}
2. 线程实现
每来一个客户端连接, 开一个子线程来专门处理客户端的数据, 实现简单, 资源占用少;
socket()
bind();
listen();
while(1)
{
accept();
pthread_create();
}
函数接口:
创建线程
#include<pthread.h>
原型:
int pthread_create
(pthread_t *thread , const pthread_attr_t*arr , void*(* routine)(void *) , void *arg)
功能: 创建线程
参数: pthread_t *thread: 指针,要指向一个地址。创建的线程对象,地址
pthread_attr_t: 线程的属性,为NULL表示默认属性
void*(* routine)(void *): 线程函数(函数指针),传入函数名
void*arg: 给线程函数传参,以地址的形式,不需要传参可以设NULL
返回值: 成功 : 0 失败 : err no
退出线程
格式:
Int pthread_exit(void *retval)
功能: 用于退出线程的执行
参数: retval: 线程退出时返回的值
返回值: 成功: 0
失败: errno
使用: pthread_exit(NULL);
将线程设置为游离态,自动回收线程资源,不阻塞
pthread_detach
int pthread_detach(pthread_t thread);
功能:将线程设置为游离态,等线程退出系统自动回收线程资源
参数:
thread:线程tid
返回值:成功0,失败非0
使用: pthread_detach(tid);
线程实现并发的代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
void *mythread(void *arg)//int acceptfd
{
//先把void * 强转为 int *, 然后取该指针的值
int acceptfd = *((int *)arg); //acceptfd
char buf[128] = "";
int recvbyte;
while(1)
{
recvbyte = recv(acceptfd,buf,sizeof(buf),0);
if(recvbyte < 0)
{
perror("recv is err:");
return NULL;
}
else if(recvbyte == 0)
{
printf("client is exit\n");
break;
}
else
{
printf("%s\n",buf);
}
}
close(acceptfd);
//线程退出函数
pthread_exit(NULL)
;}
int main(int argc, char const *argv[])
{
if(argc != 2)
{
printf("please input %s <port>",argv[0]);
return -1;
}
//1.创建流式套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("sockfd is err:");
return -1;
}
//2.填充ipv4结构体
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int len = sizeof(caddr);
if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
{
perror("bind is err:");
return -1;
}
//3.监听
if(listen(sockfd,5)<0)
{
perror("listen is err:");
return -1;
}
while(1)
{
int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
if(acceptfd < 0)
{
perror("accept is err:");
return -1;
}
printf("client: ip %s port %d\n",\
inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
pthread_t tid;
pthread_create(&tid,NULL,mythread,&acceptfd);
//将子线程设置为游离态, 不阻塞等待该线程的退出;
//一旦该线程退出, 则会自动回收自己的资源
pthread_detach(tid);
}
close(sockfd);
return 0;
}
并发服务器总结:
多进程:
优点: 服务器更稳定 , 父子进程资源独立, 安全性高一点
缺点: 消耗资源
多线程:
优点: 资源开销小, 线程共享同一个进程的资源
缺点: 安全性较差
IO多路复用:
优点: 节省资源, 减小系统开销,性能高;
缺点: 代码复杂性高
网络超时检测
使用网络超时事件检测的原因:
1) 避免进程在没有数据时无限制的阻塞。
2)当设定的时间到, 进程从原操作进行返回,然后继续执行
10.1 函数的参数可以设置超时
10.1.1 select 超时检测
头文件: #include<sys/select.h> #include<sys/time.h>
#include<sys/types.h> #include<unistd.h>
声明: int select(int nfds, fd_set *readfds, fd_set *writefds,\
fd_set *exceptfds, struct timeval *timeout);
功能:监测是哪些文件描述符产生事件;,阻塞等待产生.
参数:nfds: 监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得+1)
readfds: 读事件集合; // 键盘鼠标的输入,客户端连接都是读事件
writefds: 写事件集合; //NULL表示不关心
exceptfds:异常事件集合; //NULL 表示不关心
timeout: 超时检测 //如果不做超时检测:传 NULL
超时时间检测: 当程序执行到该语句时,我们设定好时间,如果规定时间 内未完成函数功能, 返回一个超时的信息,我们可以根据该信息设定相应需求;
返回值: <0 出错 >0 表示有事件产生;
------------ 如果设置了超时检测时间:&tv ------------
<0 出错 >0 表示有事件产生; ==0 表示超时时间已到;
超时时间检测的结构体如下:
struct timeval {
long tv_sec; 以秒为单位,指定等待时间
long tv_usec; 以毫秒为单位,指定等待时间 1s = 1000us
};
struct timespec {
long tv_sec; 以秒为单位
long tv_nsec; 以纳秒为单位 1s = 1000000ns
};
编辑
10.1.2 poll超时检
声明:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
头文件: #include<poll.h>
功能: 监视并等待多个文件描述符的属性变化
参数:
1. struct pollfd *fds: 关心的文件描述符数组,大小自己定义
若想检测的文件描述符较多,则建立结构体数组struct pollfd fds[N];
struct pollfd{
int fd; //文件描述符
short events; //等待的事件触发条件----POLLIN读时间触发
short revents; //实际发生的事件(未产生事件: 0 ))
}
2. nfds: 最大文件描述符个数
3. timeout: 超时检测 (毫秒级):1000 == 1s 如果-1,阻塞 如果0,不阻塞
返回值: <0 出错 >0 表示有事件产生;
如果设置了超时检测时间:&tv
<0 出错 >0 表示有事件产生; ==0 表示超时时间已到;
编辑
10.1.3 epoll超时检测 -epoll也可以实现超时时间检测
声明: int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
功能:等待事件的产生,类似于select的用法
参数: epfd:句柄;
events:用来保存从链表中拿取响应事件的集合;
maxevents: 表示每次在链表中拿取响应事件的个数;
timeout:超时时间,毫秒级别,0立即返回 ,-1阻塞
返回值: < 0 出错 >0 实际从链表中拿出的数目
如果设置了超时检测:
< 0出错 >0实际从链表中拿出的数目 ==0 表示超时或者没事件产生
编辑
10.2 setsockopt 设置套接字属性
10.2.1 socket属性
头文件: #include<sys.socket.h>
#include<sys/types.h>
#include<sys/time.h>
int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t optlen)
功能:获得/设置套接字属性
参数:
sockfd:套接字描述符
level:协议层
optname:选项名
optval:选项值
optlen:选项值大小
返回值: 成功: 0 失败 -1
选项名称 | 说明 | 数据类型 |
========= SOL_SOCKET 应用层 ========== | ||
SO_BROADCAST | 允许发送广播数据 | int |
SO_DEBUG | 允许调试 | int |
SO_DONTROUTE | 不查找路由 | int |
SO_ERROR | 获得套接字错误 | int |
SO_KEEPALIVE | 保持连接 | int |
SO_LINGER | 延迟关闭连接 | struct linger |
SO_OOBINLINE | 带外数据放入正常数据流 | int |
SO_RCVBUF | 接收缓冲区大小 | int |
SO_SNDBUF | 发送缓冲区大小 | int |
SO_RCVLOWAT | 接收缓冲区下限 | int |
SO_SNDLOWAT | 发送缓冲区下限 | int |
SO_RCVTIMEO | 接收超时 | struct timeval |
SO_SNDTIMEO | 发送超时 | struct timeval |
SO_REUSEADDR | 允许重用本地地址和端口 | int |
SO_TYPE | 获得套接字类型 | int |
SO_BSDCOMPAT | 与BSD系统兼容 | int |
========== IPPROTO_IP IP层/网络层 ========== | ||
IP_HDRINCL | 在数据包中包含IP首部 | int |
IP_OPTINOS | IP首部选项 | int |
IP_TOS | 服务类型 | int |
IP_TTL | 生存时间 | int |
IP_ADD_MEMBERSHIP | 将指定的IP加入多播组 | struct ip_mreq |
============ IPPRO_TCP 传输层 ============== | ||
TCP_MAXSEG | TCP最大数据段的大小 | int |
TCP_NODELAY | 不使用Nagle算法 | int |
编辑
编辑
设置 接收超时
设置超时检测操作的结构体:
struct timeval {
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
struct timeval tm={2,0};
setsockopt(acceptfd,SOL_SOCKET,SO_RCVTIMEO,&tm,sizeof(tm));
//设置超时之后时间一旦到达,会打断接下来的阻塞,直接错误返回
int recvbyte = recv(acceptfd, .......);
设置端口和地址重用(在绑定bind上面写)
int optval=1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
10.3 alarm定时器
如果设定alarm(2), 则说明 2s后, 会有一个 SGIALRM信号产生
sleep和alarm的区别:
alarm 不会在alarm语句上暂停,会继续执行下面的语句,定时时间到了会结束该进程
sleep 在该sleep语句上睡眠一段时间
10.3.1 sigaction 修改信号的行为
头文件: #include <signal.h>
声明: int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:对接收到的指定信号处理
参数: 1. signum 信号
2. //act为设置新行为 oldact为设置旧行为
结构体如下:
struct sigaction {
void (*sa_handler)(int); //函数指针
其他的结构体成员如mark(信号集),flag(对信号的标记)都不常用
};
===============需要定义一个函数接收====================
void handler()
{
printf("timeout .....\n");
}
一般,给目标设置新的属性,流程都为:
先获取原来的属性
修改属性
将属性写回去
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig)
{
printf("1111111\n");
}
int main(int argc, const char *argv[])
{
//1.定义结构体变量
struct sigaction act;
//2.获取原来的属性
sigaction(SIGALRM,NULL,&act);
//3.修改属性
act.sa_handler = handler;
//4.写回属性
sigaction(SIGALRM,&act,NULL);
char buf[128] = "";
while(1)
{
alarm(2);
if(fgets(buf,sizeof(buf),stdin) == NULL)
{
perror("fgets is err:");
continue;
}
printf("!!!!!!!\n");
}
return 0;
}
- 广播 -UDP
广播地址: 主机号最大的地址;
192.168.1.2 - 192.168.1.255
只有 用户数据包使用套接字(UDP) 才能实现广播
使用情况:
局域网 搜索协议 :
比如说 家里的智能产品, 使用手机可以搜索出附近的智能产品 , 这就是一个局域网搜索协议
11.1 基于setsockopt 实现广播
广播发送者 - 客户端:
1. 建立一个数据包套接字
2. 利用setsockopt设置套接字属性
3. 填充该结构体
4. 循环发送数据包
简易步骤代码:
int sockfd = sock(AF_INET,SOCK_DGRAM,0);
int opt = 1;
setsockopt(sockfd,协议层,选项名,数据类型,大小);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr,sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
sendto(sockfd,buf,sizeof(buf),0,\
(struct sockaddr *)&addr,sizeof(addr));
广播接收者 - 服务器:
1. 创建一个套接字
2. 绑定广播IP
3. 等待接收数据
简易代码实现步骤:
sockfd = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");//广播地址或0.0.0.0
socklen_t len = sizeof(caddr);
bind......
recvfrom(sockfd,buf,sizeof(buf),0,\
(struct sockaddr *)&caddr,&len);
缺点: 广播发送给所有主机, 过多的的网络会发送大量的网络带宽,造成广播风暴!
广播风暴: 网络长时间被大量数据包占用, 无法造成通信,网络会变得缓慢,甚至崩溃
机制:
通过向广播地址发送UDP数据包,将数据包发送给网络中的所有主机。当一个主机发送广播消息时,
该消息会被路由器转发到网络中的所有子网。然后,每个子网上的主机都会接收到该广播消息,
并转发给它们的相邻主机。这个过程会一直持续下去,直到广播消息传播到整个网络中的所有主机,
如果这个现象一直循环如此, 则会造成广播风暴。
组播 -UDP
12.1 概念:同样也是基于setsockopt实现的文件属性设置
12.2 组播的地址
二级划分 D类IP: 224.0.0.1 - 239.255.255.255
//多播结构体
struct ip_mreq{
struct in_addr imr_multiaddr; //指定多播组IP
struct in_addr imr_interface; //本地IP,通常指定为 INADDR_ANY--0.0.0.0
}
struct in_addr{
_be32 s_addr; //IP地址(大端)
}
//核心代码 ------------------------------------
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1"); //填充多播组IP
mreq.imr_interface.s_addr = inet_addr("0.0.0.0"); //自动获取本机IP
//改变套接字属性
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
12.3 组播的客户端
1.创建套接字
2.加入多播组
3.接收者绑定组播IP地址
4.等待接收数据
简易代码实现
socket()
struct ip_mreq mreq; //组播的结构体变量
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]); //组IP
mreq.imr_interface.s_addr = inet_addr("0.0.0.0"); //本地IP
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
//3. 填充结构体: 绑定组播ip和端口
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //组IP
socklen_t len = sizeof(caddr);
12.4 组播的服务器
1.创建数据报套接字
2.指定接收方地址为 组播地址 如 224.0.0.10 以及端口信息
3.发送数据包
- 广播和组播的区别
广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响通信。
组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
组播方式既可以发给多个主机,又能避免像广播那样带来过多的负载
本地套接通信字
14.1 特性
· unix网络编程最开始有的编程都是一台主机内进程和进程之间的编程。(本地通信)
· socket可以用于本地间进程通信,创建套接字时使用本地协议AF_LOCAL或AF_UNIX
本地通信不需要ip和端口,无法进行两个主机通信
· 分为流式套接字和数据报套接字 //可以使用流式套接字或者数据包套接字
· 和其他进程间通信相比使用方便、效率更高,常用于前后台进程通信。
· unix域套接字编程,实现本间进程的通信,依赖的是s类型的文件;
核心流程
#include <sys/socket.h>
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; /* 本地协议 AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* 本地路径 s类型的套接字文件 */
};
unix socket = socket(AF_UNIX, type, 0); //type可以为流式套接字或数据包套接字
//unix 写为 int 就可以
struct sockaddr_un myaddr;
myaddr.sun_family = AF_UNIX; //填充UNIX域套接字
strcpy(saddr.sun_path,"./myunix"); // 创建套接字的路径
recv代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//1.创建套接字
int sockfd = socket(AF_UNIX,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("sock is err:");
return -1;
}
system("rm ./myunix -f");// 两者都是删除地址的,二选一即可
unlink("./myunix");
//2. 填充结构体
struct sockaddr_un saddr;
saddr.sun_family = AF_UNIX;
strcpy(saddr.sun_path,"./myunix");
if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
{
perror("bind is err:"); return -1;
}
if(listen(sockfd,5) < 0)
{
perror("listen is err:"); return -1;
}
int acceptfd = accept(sockfd,NULL,NULL);
if(acceptfd < 0)
{
perror("acceptfd < 0"); return -1;
}
char buf[128] = "";
int recvbyte;
while(1)
{
recvbyte = recv(acceptfd,buf,sizeof(buf),0);
if(recvbyte < 0)
{
perror("recv is err:"); return -1;
}
else if(recvbyte == 0)
{
printf("exit\n");
break;
}
else
{
printf("buf: %s\n",buf);
}
}
close(sockfd);
close(acceptfd);
return 0;
}
send代码:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//1.创建套接字
int sockfd = socket(AF_UNIX,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("sock is err:");
return -1;
}
//2. 填充结构体
struct sockaddr_un saddr;
saddr.sun_family = AF_UNIX;
strcpy(saddr.sun_path,"./myunix");
if(connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
{
perror("connect is err:");
return -1;
}
char buf[128] = "";
int recvbyte;
while(1)
{
fgets(buf,sizeof(buf),stdin);
if(buf[strlen(buf) -1] == '\n')
buf[strlen(buf) -1] = '\0';
send(sockfd,buf,sizeof(buf),0);
}
close(sockfd);
return 0;
}
14.2 wireshark抓包工具
抓包工具的使用:
虚拟机 : sudo apt-get install wireshark
windows: 小飞机
注:抓包的过程,就是抓网卡流经的一些数据。启动时不加sudo找不到网卡,没有办法找到内容。
wireshark可以用来抓包,必须是流经网卡的包:
两台不同的主机通信 或 两台不同的操作系统(windows linux)之间 才可以进行抓包
1.运行linux下的服务器
2.打开windows下的小飞机
3.打开抓包工具
4。过滤无关的包
5.小飞机模拟客户端, 与linux下的服务器通信
编辑
流程
编辑
编辑
编辑
编辑
编辑
编辑
编辑
抓包工具头的分析
编辑
14.3 以太网的完整帧格式
编辑
对于网络层最大数据帧长度是1500字节(MTU: 最大传输单元)
对于链路层最大数据长度是1518字节(1500(网络层)+14(以太网)+4(CRC检错))
粘包、拆包发生原因:
发生TCP粘包或拆包有很多原因,常见的几点:
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
粘包、拆包解决办法:
解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,
通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补 \0填充),
这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
https://www.cnblogs.com/111testing/p/12810253.html
14.3.1以太网头
以太网中封装了 目的mac地址 以 及 源mac地址, 还有ip类型, 以太网又称之为mac头 -快递员
切换网络时, ip地址会改变, mac地址不会改变
编辑
14.3.2 IP头
笔试题: type类型都有哪些?
0x0800 只接收发往本机的mac的ip类型的数据帧
0x0806 只接收发往本机的ARP类型的数据帧
ARP: ARP协议用于将IP地址解析为MAC地址。
当一个计算机需要向另一个计算机发送数据时,它需要知道目标计算机的MAC地址,而不是IP地址。
RARP: RARP协议则是与ARP相反的过程,它用于将MAC地址解析为IP地址。
0x8035 只接受发往本机的RARP类型的数据帧
0x0003 接收发往本机的MAC所有类型: ip,arp,rarp数据帧, 接收从本机发出去的数据帧,
当打开混杂模式打开的情况下,会接收到非发往本地的MAC数据帧
编辑
编辑
14.3.3 UDP头
编辑
14.3.4 TCP头
Seq: 序列号 用于 非应答包 的数据段
Ack: 应答号 用于应答 非应答包
非应答包: SYN握手包 FIN挥手包 PSH数据报
在 PSH中: Ack = Seq + len;
在 SYN和FIN中: Ack = Seq + 1;
SYN: 握手包, 建立连接的时候才会产生的包
FIN: 挥手包, 断开连接的时候才会产生的包
PSH: 数据包, 传输数据的时候才会产生的包
ACK: 应答包 应答时产生
编辑
14.4 三次握手
1.个人理解
第一次发送者会发送给接受一个发送包,也就是握手包,然后接受者收到后会把这个握手包返回给发送者,顺便说我知道了,这个我知道了也就是一个应答包,然后发送者再给接受者说个ok,也就是应答包,所以一共是三次。
2。官方理解
第一次握手:客户通过调用connect进行主动打开(active open)。客户端发送一个SYN(表示同步)分节(SYN=J),它告诉服务器客户将在连接中发送到数据的初始序列号。并进入SYN_SEND状态,等待服务器的确认。
第二次握手:服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器以单个字节向客户发送SYN和对客户SYN的ACK(表示确认),此时服务器进入SYN_RECV状态。
第三次握手:客户收到服务器的SYN+ACK。向服务器发送确认分节,此分节发送完毕,客户服务器进入ESTABLISHED(确认)状态,完成三次握手。
编辑
14.5 四次挥手
1.个人理解
第一次发送者会发送给接受一个发送包,也就是挥手包,然后接受者收到后会把这个挥手包返回给发送者,然后再说一次我知道了,这个我知道了也就是一个应答包,然后发送者再给接受者说个ok,也就是应答包,所以一共是四次。
2。官方理解
第一次挥手:主动关闭方发送一个FIN给被动方,进入FIN_WAIT状态;
第二次挥手:被动方接收到FIN包,给主动方发送一个ACK包;并进入CLOSE_WAIT状态,主动方接收到ACK包后,进入FIN_WAIT状态,如果有数据没有发送完毕,则继续发送,一直到发送完毕;
第三次挥手:被动方发送一个FIN包,进入LAST_ACK状态。
第四次挥手:主动关闭方收到FIN包,回复一个FIN包。被动关闭方收到主动关闭方的ACK后关闭连接。
编辑
个人理解(图画)
SQL数据库
15.1 数据库简介
常用的数据库
大型数据库 :Oracle
中型数据库 :Server是微软开发的数据库产品,主要支持windows平台
小型数据库 : mySQL是一个小型关系型数据库管理系统。开放源码 (嵌入式不需要存储太多数据)
SQLite基础
SQLite的源代码是C,其源代码完全开放。它是一个轻量级的嵌入式数据库。
SQLite有以下特性:
零配置一无需安装和管理配置;
储存在单一磁盘文件中的一个完整的数据库;
数据库文件可以在不同字节顺序的机器间自由共享;
支持数据库大小至2TB(1024G = 1TB); 嵌入式足够
足够小,全部源码大致3万行c代码,250KB;
比目前流行的大多数数据库对数据的操作要快;
创建SQLite数据库:
手工创建
使用sqlite3工具,通过手工输入SQL命令行完成数据库创建.
-----------------两种方式都会用得到,重点是代码创建--------------------
代码创建
在代码中常动态创建数据库 在程序运行过程中,当需要进行数据库操作时,应用程序会首先尝试打开数据库,
此时如果数据库并不存在,程序则会自动建立数据库,然后再打开数据库
15.2 虚拟机中sqlite3的安装
1. sudo apt-get update
2. sudo apt-get install sqlite3 // 安装软件
3. sudo apt-get install libsqlite3-dev //数据库开发支持库
4. sudo apt-get install sqlitebrowser //数据库操作软件
5. sqlite3 my.db //测试是否安装成功
6. .quit 退出
15.3 基础SQL语句使用
操作数据库
sql菜鸟教程https://www.runoob.com/sql/sql-tutorial.html)
sql数据类型:
int\INT(整数类型) char\TEXT(字符或字符串类型) float\REAL(小数类型)
注意:sql语句不严格检查大小写
(1)命令的方式操作数据
创建一个数据库:sqlite3 数据库文件名(后缀为db的文件)//sqlite3 student.db -- 打开数据库 - student
两种命令
1、sqlite3系统命令
都是以.开头的,.help查看所有支持的命令
常用.help(帮助)
.exit(退出)
.tables(查看该数据库有几个表)
.schema 表名:查看具体某个表的结构 (不加表明为全部表结构)
ctrl + l 清屏
sqlitebrowser 表名字.db 切换为图形界面
2、SQL命令: 命令结尾记得加入 ;
sql数据类型 int/INT,char/TEXT,float/REAL.
命令+表或值; ----->分号代表一句命令结束
(1)create table stu(id int,name char,score float); 创建一个表
注:在数据库中创建表时,数据库不严格检查类型,SQL命令以“;”结尾。
create table stu(id int primary key,name char,score float);
primary key:主键修饰,代表唯一
(2)删除一个表
drop table 表名;
(3)向表内插入数据(也就是向表内某个字段赋值,也就是填充表内字段)
insert into stu values(1,"xiaoming",99.9);
(4)查询表内数据
select * from stu;查询整个stu表内内容
select * from stu where name="xiaoming";
查询stu表中名字为xiaoming的学生信息
select * from stu where name="lisi" and id = 2;
查询stu表中名字为lisi且id为2的学生信息
select * from stu limit 2;查询表内前两条信息
select * from stu order by score desc;降序排列
select * from stu order by score asc;升序排列
注:from后跟表,where后跟条件,*代表所有字段,可改变成任意字段
例如 select score from stu where name="xiaoming";
查询stu表内xiaoming的分数
(5)修改表内数据
update 表名 set <f1=value1>, <f2=value2>... where id=value;
eg:
update stu set id=3 where name="lisi";
将stu表内name为lisi的id字段更改为3.
(6)增加表内字段
alter table 表名 add column 字段名字 字段类型 default 数值;
alter table stu add column class int default 1;
将stu表增加一个字段名字为calss,类型为int,默认为1;
(7)删除表内字段
1)create table student as select id,name,score from stu;
创建一个student表正如stu里面包含id name score三个字段。
2)drop table stu;删除表名
3)alter table student rename to stu;
修改表名,将student改为stu
after table stu drop column class;
删除class列数据
(8)删除表具备条件的一行数据(一行内的所有字段)
delete from stu where id=1;
删除stu表中id为1的一行数据
delete from stu ;
删除stu表所有数据
15.4 sqlite3 API接口
https://www.sqlite.org/index.html
15.5 sqlite3的使用
- 创建一个学生库
编辑
- 创建表
编辑
- 插入内容
编辑
- 查询内容
编辑
- 设置主键
编辑
- 删除表
编辑
- 打开图形化数据库
编辑
15.6 sqlite3的编程
15.6.1 打开数据库
头文件: #include <sqlite3.h>
编译方式: gcc sqlite1.c -lsqlite3
打开数据库
sqlite3_open
头文件: #include<sqlite3.h>
声明: int sqlite3_open(char *path, sqlite3 **db);
功能:打开sqlite数据库 (或新建数据库)
参数:
path: 数据库文件路径
db: 指向数据库句柄的指针 - 该二级指针指向一级指针地址,最终会存储打开的数据库的首地址
(参数是二级指针,那么填什么? 一级指针的地址)
返回值:成功返回0,失败返回错误码(非零值)
使用: sqlite3 *db;
if(sqlite3_open("stu.db",&db) != 0)
{
fprintf(stderr,”err: %s\n”,sqlite3_errmsg(db));
}//sqlite3自带的打印错误信息
值传递: 获取到值
地址传递: 修改值得使用地址传递
15.6.2 关闭数据库
关闭数据库
sqlite3_close
声明: int sqlite3_close(sqlite3 *db);
功能:关闭sqlite数据库
返回值:成功返回0,失败返回错误码
使用: sqlite3_close(db);
15.6.3 执行sql语句
功能:执行sql语句;
原型:
int sqlite3_exec(
sqlite3 *db, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *arg, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);
参数:
sqlite3 *db:指定要操作的数据库句柄指针;
char *sql:指定要执行的sql语句;
int (*callback)(void*,int,char**,char**):回调函数;
该函数指针可以指向返回值是int类型,参数列表是(void*,int,char**,char**)的函数
void *arg:传递给回调函数的第一个参数;
- 类似线程函数传参,需强转 - 不传参可以填NULL
char **errmsg:该二级指针指向的以一级指针会存储错误信息的首地址;
(定义一个一级指针就好了)
错误信息存储在静态存储区,存在着相应的源码,我们不用去深究
返回值:
成功,返回SQLITE_OK,其实就是0;
失败,返回error_code,其实就是非0
15.6.4 打印错误信息
打印错误信息
sqlite3_errmsg
声明:const char *sqlite3_errmsg(sqlite3 *db); //bd;
功能: 打印错误信息
返回值:返回错误信息
使用: fprintf(stderr,"sqlite3_open failed %s\n",sqlite3_errmsg(db))
15.6.5 sqlite3_exec
int sqlite3_exec 的回调函数:(一般在执行 需要终端打印数据的时候使用)
int callback(void *arg, int columns, char **column_text, char **column_name);
功能:sqlite3_exec每找到一条满足条件的记录,就会执行一次回调函数。
参数:
void *arg:sqlite3_exec的第四个参数传入;
int columns:查询结果的列数;
char **column_text:该二级指针指向的是一个数组,该数组是一个指针数组
数组中的元素都是char*类型的指针。指针指向查询结果的内容
char **column_name:该二级指针指向的是一个数组,该数组是一个指针数组
数组中的元素都是char*类型的指针。指针指向查询结果的列名;
返回值:
成功时候必须返回0,该返回值会返回给sqlite3_exec函数,
如果没有返回0,则sqlite3_exec会