Day1:
从零开始设计一个网络
如果要实现两台主机通信,怎么做?
如果要实现五台主机通信,怎么做?
缺点:隐私低。效率低。
MAC地址:48位(全球唯一地址),不好记
五类IP地址:
A类网络的地址范围:1.0.0.0 ~ 127.255.255.255
B类网络的地址范围:128.0.0.0 ~ 191.255.255.255
C类网络的地址范围:192.0.0.0 ~ 223.255.255.255
D类网络的地址范围:224.0.0.0 ~ 239.255.255.255
E类网络的地址范围:240.0.0.0 ~ 255.255.255.255
判断流程:
网段:主机和子网掩码&操作,结果为网段,网段一样即可通信。
若不一样,引入网关进行通信。(路由器会存入192.168.1.1和192.168.0.1等网络接口。)
Internet的历史
OSI参考模型和TCP/IP协议
网络协议:网络通信过程中,通信双方所共同遵从的约定。
网络体系结构:网络的层次结构和每层所使用协议的集合。
TCP/IP协议族
IP分片的过程:
MTU=IP头+TCP头+APP头
MSS=APP头+User data
TCP和UDP协议
TCP是面向连接的,可靠的传输层协议
UDP是无连接的,不保证可靠的传输层协议
TCP的三次握手:
TCP的四次挥手:
TCP可靠性如何保证:
预备知识
Socket(插座):
IP:地址
3)端口号:
区分同一个主机上的不同进程
TCP和UDP的端口号允许一样
字节序:
主机字节序:不同CPU架构的主机,在内存中存储多字节数字的方式,分为大端字节序和小端字节序。
系统调用
创建套接字
Socket:
绑定IP地址和端口号
设置监听套接字(把主动套接字改为被动套接字)
通常写5
listen()
4)等待接受客户端的连接
5)客户端连接服务器
6)发送消息
Send:
7)接收消息
Recv
8)关闭套接字:
9)基于TCP协议的服务器和客户端
Day2
抓包
安装软件
Sudu apt-get install wireshark
2)启动 wireshark
Sudo wireshark
3)抓包
TCP程序优化
./client 192.168.50.128 5001
Argc = 3
Argv[0] = “./client”
Argv[1] = “192.168.50.128”
Argv[2] = “5001”
服务器优化代码:
客户端优化代码:
3.基于UDP协议的服务器和客户端
UPD协议:
UDP是无连接的,不保证可靠性的传输层协议。
UDP为啥不可靠:
UDP协议头中没有序列号和确认序列号,没有消息确认机制,无法确认对端是否收到本端发送的消息。
UDP效验和允许关闭。
UDP的使用场景:
对于可靠性要求不高,对于实时性要求高的场景适合使用UDP,比如视频聊天,语音聊天
广播和组播场景只能使用UDP。
发送小尺寸数据(如对DNS服务器进行IP地址查询时)
如果想用UDP协议,还要保证可靠性,在怎么做?
应用层自己做消息的响应机制。
UDP服务器和客户端
Ssize_t sendto(int socket, void *message,size_t length,int flags,struct sockaddr *dest_addr, socklen_t dest_len);
功能:发送消息
参数1:套接字的文件描述符
参数2:发送缓冲区地址
参数3:发送消息的长度
参数4:发送方式,一般填0
参数5:发送的目的地址和端口
参数6:发送的目的地址结构的长度
Ssize_t recvfrom(int socket,void *buffer,size_t length,int flags,struct sockaddr *address, socklen_t *address_len);
功能:接收消息
参数1:套接字的文件描述符
参数2:接受缓冲区的地址
参数3:接受缓冲区的长度
参数4:接受方式 0代表阻塞接受
参数5:从哪个地址和端口接受
参数6:参数5地址结构的长度
4.IO模型
阻塞IO
对于发送函数:如果发送缓冲区不足,发送函数就会阻塞不返回。
对于接受函数:如果对端不发送消息,接受函数就会阻塞不返回。
优点:简单、常用。
缺点:效率低/ 如果IO通道损坏,那么进程会阻塞等待。
非阻塞IO
While(1)
{
Ret = recv();
If(ret < 0 && ret == EAGAIN)
{
Continue;
}
//处理读取的消息内容
}
对于接受函数:不断读取消息内容,如果对端没有发送消息,返回错误,需要继续读取。
对于发送函数:发送缓冲区不足,会返回错误,需要继续发送。
优点:可以防止进程阻塞在IO操作上。
缺点:极浪费CPU资源。
信号驱动IO
void sigFunc(int signo);
{
Recv(); //主要在收消息
}
Signal(SIGIO,sigFunc);
IO多路复用
Linux默认情况下一个进程做多能打开1024个文件,对应1024个文件描述符。
文件描述符的特点:
①文件描述符是非负整数
②文件描述符一般都是从小到大分配的
③进程启动的时候,默认会分配三个文件描述符,0,1,2.
IO多路复用不止针对套接字文件描述符,也针对普通的文件描述符。
IO多路复用的3种实现方式:
①select(3种最简单)
Int select (int n,fd_set *read_fds,fd_set *write_fds,fd_set *except_fds,struct timeval *timeout);
Select IO 多路复用的思想,分为3步:
①创建一个文件描述符集合,集合里有1024个bit位,每个bit位代表一路IO通道的文件描述符。如果关注该文件描述符,就将该文件描述符加入文件描述符集合,将文件描述符集合里对应该文件的bit位置1,其他bit位置0.
②调用select函数监控文件描述符集合,如果关注的文件描述符里IO通道对应的文件描述符上有数据发生,将该文件描述符对应的bit位置1,其余bit位置0,返回有数据发生的文
件描述符的数量。如果关注的文件描述符上都没有数据发生,select函数阻塞不返回。
③遍历文件描述符集合里关注的文件描述符,用FD_ISSET判断文件描述符对应的IO通道上是否有数据发生,如果FD_ISSET返回1代表文件描述符对应的IO通道上有数据发生,处理该IO通道上的数据。
②poll
③epoll(最复杂)
Day3:
服务器模型
循环服务器
①TCP循环服务器
服务器一次只能接入一个客户端,处理一个客户端的消息,该客户端如果不退出,其他客户端无法接入。(不常用)。
listenFd = socket();
Bind(listenFd,...);
Listen(listenFd,....);
While(1)
{
connFd = accept(listenFd,...);
While(1)
{
Recv(connFd,...);
Process();
Send(connFd,...);
}
}
②UDP循环服务器
只要处理每个客户端消息的时间不长,服务器可以同时处理多个客户端的请求。UDP循环服务器可以使用。
sockFd = socket();
Bind(sockFd);
While(1)
{
Recvfrom(sockFd,...);
Process();
Sendto(sockFd,...);
}
并发服务器
TCP多线程并发服务器:
客户端接入服务器后,服务器为客户端分配单独的线程,为该客户端提供服务。
TCP多进程并发服务器:
客户端接入服务器后,服务器为客户端分配单独的进程,为该客户端提供服务。
IO多路复用服务器
在服务器对每个客户端消息处理时间都不长的情况下,可以使用IO多路复用服务器。
网络超时检测
①设置套接字属性
Struct timeval tv;
Tv.tv_sec = 5; //单位是秒,设置5秒时间
Tv.tv_usec = 0; //单位是微秒
Setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));//设置接受超时
Recv()/recvfrom() //从socket读取数据
1秒 = 1000毫秒 = 10^6微秒
②select超时检测
Int select(int nfds,fd_set *readfds,fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
Struct fd_set rdfs;
Struct timeval tv = {5,0}; //设置5秒时间
返回值:
小于0,代表select出错
等于0,代表select超时返回
大于0,代表select检测到文件描述符上有数据发生
③用信号触发超市检测
Alarm是一个闹钟函数,在指定时间超时后会触发SIGALRM信号。
如果给信号设置SA_RESTART,慢系统调用被该信号打断后,慢系统调用会自动重启, 然后不会返回。
如果清楚信号的SA_RESTART,慢系统调用被该信号打断后,慢系统调用会返回出错,errno是EINTR。
Int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact);
参数1:信号
参数2:要设置的新的信号处理方式
参数3:原来老的信号处理方式
Void handler(int signo)
{
Reuturn;
}
Struct sigaction act;
Sigaction(SIGALRM,NULL,&act); //获取SIGALRM老的信号处理方式
Act.sa_handler = hadler; //设置信号处理回调函数
Act.sa_flags & = ~SA_RESTART; //清除SA_RESTART标志位
Sigaction(SIGALRM,&act,NULL); //设置SIGALRM新的信号处理方式
Alarm(5);
If(recv(....) < 0)......
广播
我们之前学到过消息的发送方式,都是单一的消息接收方,这种消息发送方式称为单播。
广播是主机发送的消息,局域网内所有主机都能收到的消息发送方式。
广播必须使用UDP编程。
网段内,主机号最大的地址是广播地址。比如:192.168.1.0/24网段内,广播地址是192.168.1.255。
所有网段的广播地址:255.255.255.255
发送方:
接收方:
Day4:
文件上传和下载
命令#文件名#文件长度
文件上传:put#1.txt#555
文件下载:get#1.txt
组播
组播是一台主机发送消息,只有加入多播组的主机才能收到消息。
组播必须使用UDP实现。
组播地址:224.0.0.0~239.255.255.255(除去网络地址和广播地址)
发送方:
接收方:
unix域套接字
unix域套接字用于本地进程间通信,通信效率仅次于共享内存,由于共享内存通常需要配合信号量做共享资源的同步和互斥,unix域套接字用起来通常比共享内存要好用。
使用AF_UNIX或者AF_LOCAL创建套接字。
unix域套接字既可以用SOCK_STREAM,也可以用SOCK_DGRAM。
客户端:
服务器:
数据库
数据:能够输入计算机并能被计算机程序识别和处理的信息集合。
数据库:在数据库管理系统管理和控制之下,存放在存储介质上的数据集合。
数据库相对文件的优点:
文件里存储的是字符串或者二进制数据,不是结构化的数据。读取文件里的数据,需要读取并解析整个文件,效率低。
程序和文件之间缺乏高度独立性。
SQLite数据库是一个用C语言写的数据库,是一个轻量级的嵌入式数据库。
SQLite有以下特性:
零配置一无需安装和管理配置;
储存在单一磁盘文件中的一个完整的数据库;
数据库文件可以在不同字节顺序的机器间自由共享;
支持数据库大小至2TB;
足够小,全部源码大致3万行c代码,250KB;
比目前流行的大多数数据库对数据的操作要快;
SQLite3相关网站:
官网:https://www.sqlite.org/index.html
菜鸟教程:https://www.runoob.com/sqlite/sqlite-c-cpp.html
SQLite3的数据类型:
SQLite3的约束:
SQLite3软件安装:
安装SQLite3命令行工具
sudo apt-get install sqlite3
安装SQLite3的开发库
sudo apt-get install libsqlite3-dev
SQLite3的系统命令:
SQLite3的系统命令也叫点命令,命令是以点开头
打开数据库,书库不存在则创建数据库
sqlite3 数据库文件名
例如:sqlite3 test.db
查询数据库帮助信息
.help
查询数据库的表
.tables
查询数据库文件的路径
.databases
查询数据库表的创建语句
.schema
退出数据库
.quit
.exit
SQL语句:
SQL语句不以点开头,要以分号结尾。
创建表
create table 表名(字段1 类型 约束, 字段2 类型 约束, …);
create table stu(id integer primary key not null, name text not null, age integer);
插入表记录
insert into 表名 values(字段1的值, 字段2的值, …);
insert into stu values(1001, 'zhangsan', 20);
insert into 表名(字段1, 字段n,…) values(字段1的值, 字段n的值,…);
insert into stu(id, name) values(1002, 'lisi');
查询表记录
select * from 表名;
select * from stu;
select 字段m, 字段n … from 表名;
select id, name from stu;
select * from 表名 where 查询条件;
select * from stu where id=1001;
select * from stu where id=1001 or name='lisi';
select * from stu where id=1001 and name='zhangsan';
更新表记录
update 表名 set 字段m=字段m的新值, … where 查询条件;
update stu set name='wangwu', age=21 where name='lisi';
删除表记录
delete from 表名 where 查询条件;
delete from stu where name='wangwu';
删除表
drop table 表名;
drop table stu;
插入表字段
alter table 表名 add column 字段 类型;
alter table stu add column score integer;
删除表字段
SQLite3不支持直接删除表字段,可以通过创建临时表改名成原表表名的方式删除表字段。
create table 临时表名 as select 字段m, 字段n, … from 原表名;
drop table 原表名;
alter table 临时表名 rename to 原表名;
create table stu1 as select id, name, score from stu;
drop table stu;
alter table stu1 rename to stu;
SQLite3的编程接口:
int sqlite3_open(char *path, sqlite3 **db);
功能:打开数据库,如果数据库不存在则创建数据库
参数1:数据库文件的路径(包含数据库文件名)
参数2:指向数据库句柄的指针
返回值:成功返回0(SQLITE_OK),失败返回错误码
int sqlite3_close(sqlite3 *db);
功能:关闭数据库
参数1:数据库的句柄
返回值:成功返回0(SQLITE_OK),失败返回错误码
int sqlite3_exec(sqlite3 *db, const char *sql, sqlite3_callback callback, void *, char **errmsg);
功能:执行SQL操作
参数1:数据库的句柄
参数2:SQL语句
参数3:回调函数,用于返回SQL查询语句的查询结果
参数4:传递给sqlite3_callback回调函数的参数
参数5:错误信息
返回值:成功返回0(SQLITE_OK),失败返回错误码
typedef int (*sqlite3_callback)(void *para, int f_num, char **f_value, char **f_name);
功能:返回SQL语句查询结果,每找到一条记录,返回一次查询结果
参数1:传递给回调函数的参数
参数2:查询结果中字段的数目
参数3:包含每个字段值的指针数组
参数4:包含每个字段名称的指针数组
返回值:成功返回0,失败返回-1
int sqlite3_get_table(sqlite3 *db, const char *sql, char ***resultp, int *nrow, int *ncolumn, char **errmsg);
功能:执行SQL操作
参数1:数据库的句柄
参数2:SQL语句
参数3:用来指向SQL语句执行结果的指针
参数4:满足条件的记录的数目
参数5:每条记录包含的字段数目
参数6:错误信息
返回值:成功返回0(SQLITE_OK),失败返回错误码
const char *sqlite3_errmsg(sqlite3 *db);
功能:获取错误信息
参数1:数据库的句柄
返回值:返回错误信息
void sqlite3_free(void *errmsg);
功能:释放错误信息
参数1:错误信息指针
void sqlite3_free_table(char **result);
功能:释放用sqlite3_get_table函数查询到的结果
参数1:用sqlite3_get_table函数查询到的结果
Day5:
学生信息管理系统
表设计:
stu
字段设计:
id:学号
name:姓名
score:成绩
功能设计:
插入学生记录
更新学生记录
查询学生记录
删除学生记录