1.网络协议头分析
数据的封装与传递过程
思考:
1.应用层调用send后,是如何把数据发送到另一台机器的某个进程的
2.接收的设备收到数据包后,如何处理给应用层
思考:在协议栈封装的过程中,这些头部信息具体有什么?
以太网完整帧
对于网络层最大数据帧长度是1500字节
对于链路层最大数据长度是1518字节(1500+14+CRC)
发送时候,ip层协议栈程序检测到发送数据和包头总长度超过1500字节的时候,会进行自动分包处理,接收端在ip层进行包重组,然后才继续往上传递
以太网头部
ip头
IHL:数据流控制
TCP头
Src: 源
Dst:目标
Seq:序列号
Ack:应答号(应答包的应答号)
数据包:
A:ACK:应答包
S:SYN:握手包(同步包),连接时产生
P:PSH:(PUSH)数据包,传输数据产生
F:FIN:挥手包,断开连接时产生
UDP头
length:固定udp数据包的长度(确保udp在传输的过程中不会粘包)
UDP不会造成粘包和拆包, TCP不会造成丢包
UDP是基于数据报文发送的,每次发送的数据包,在UDP的头部都会有固定的长度, 所以应用层能很好的将数据包分隔开, 不会造成粘包。
UDP不会造成拆包, 但会出现拆包, 这个拆包是在网络层的IP头进行的拆包(判断MTU)。
TCP是基于字节流的, 每次发送的数据报,在TCP的头部没有固定的长度限制,也就是没有边界,那么很容易在传输数据时,把多个数据包当作一个数据报去发送,成为了粘包,或者传输数据时, 要发送的数据大于发送缓存区的大小,或者要发送的数据大于最大报文长度, 就会拆包;
TCP不会丢包,因为TCP一旦丢包,将会重新发送数据包。(超时/错误重传)
为什么UDP会造成丢包:
UDP通信没有发送缓存区, 它不保证数据的可靠性。因此,UDP通信是将数据尽快发送出去,不关心数据是否到达目标主机. 但是UDP有接受缓存区, 因为数据发送过快, 如果接收缓存区内数据已满, 则继续发送数据, 可能会出现丢包。
丢包出现原因: 接收缓存区满 网络拥堵, 传输错误
2.UDP项目
服务器
#include "head.h"
link_t *link_creat();
void client_login(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
void client_quit(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
void client_chat(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
int main(int argc, char const *argv[])
{
// 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
// 绑定
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(caddr);
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err");
return -1;
}
printf("bind ok\n");
// 创建链表
link_t *p = link_creat();
printf("creat ok\n");
MSG_t msg;
// 可扩展功能,服务器可以给所有用户发送“公告”
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
while (1)
{
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
msg.type = chat;
strcpy(msg.name, "server");
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
}
}
else
{
while (1)
{
if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len) < 0)
{
perror("recvfrom err");
return -1;
}
printf("type:%d\n", msg.type);
switch (msg.type)
{
case login: // 上线函数
client_login(sockfd, p, msg, caddr);
break;
case chat: // 聊天函数
client_chat(sockfd, p, msg, caddr);
break;
case quit: // 下线函数
client_quit(sockfd, p, msg, caddr);
break;
default:
break;
}
}
}
close(sockfd);
return 0;
}
// 创建链表
link_t *link_creat()
{
// 给头结点开辟空间
link_t *p = (link_t *)malloc(sizeof(link_t));
if (p == NULL)
{
perror("malloc err");
return NULL;
}
// 初始化头结点
p->next = NULL;
return p;
}
// 客户端上线功能:将用户上线消息发送给其他用户,并将用户信息添加到链表中
void client_login(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{
printf("%s login....\n", msg.name);
// 先遍历链表
// 再插入新的节点
while (p->next != NULL)
{
p = p->next;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
link_t *pnew = (link_t *)malloc(sizeof(link_t));
pnew->addr = caddr;
pnew->next = NULL;
p->next = pnew;
return;
}
// 聊天功能:转发消息到在链表中除了自己的所有用户
void client_chat(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{
printf("%s says %s\n", msg.name, msg.text);
while (p->next != NULL)
{
p = p->next;
// strcmp:对比前后两个字符串中的内容是否一致
// memcmp:对比前后两个对应的地址(任意类型)的内容是否一致
if (memcmp(&(p->addr), &caddr, sizeof(caddr)))
{
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
}
return;
}
// 客户端退出功能:将用户下线消息发送给其他用户,将下线用户从链表中删除
void client_quit(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{
printf("%s quit.......\n", msg.name);
link_t *pdel = NULL;
while (p->next != NULL)
{
if (memcmp(&(p->next->addr), &caddr, sizeof(caddr)))
{
// 不是自己
p = p->next;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
else
{
// 是自己,删除节点
pdel = p->next;
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
return;
}
客户端
#include "head.h"
int main(int argc, char const *argv[])
{
int ret;
// 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
// 绑定
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(caddr);
MSG_t msg;
// 给服务器发送上线消息
printf("请输入用户名:");
fgets(msg.name, sizeof(msg.name), stdin);
if (msg.name[strlen(msg.name) - 1] == '\n')
msg.name[strlen(msg.name) - 1] = '\0';
msg.type = login;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) // 循环接受服务器消息
{
while (1)
{
ret = recvfrom(sockfd, &msg, sizeof(MSG_t), 0, NULL, NULL);
if (ret < 0)
{
perror("recv from err");
return -1;
}
if (msg.type == login)
printf("%s login.......\n", msg.name);
else if (msg.type == chat)
printf("%s says %s\n", msg.name, msg.text);
else if (msg.type == quit)
printf("%s quit.......\n", msg.name);
}
}
else // 循环发送消息
{
while (1)
{
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
if (!strcmp(msg.text, "quit"))
{
msg.type = quit;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
break;
}
else
{
msg.type = chat;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
}
}
kill(pid, SIGINT);
wait(NULL);
}
close(sockfd);
return 0;
}
头文件head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
//链表节点结构体:
typedef struct node
{
struct sockaddr_in addr; //data memcmp
struct node *next;
} link_t;
//类型
enum type_t
{
login,
chat,
quit,
};
//消息对应的结构体(同一个协议)
typedef struct msg_t
{
int type; //'L' C Q enum un{login,chat,quit};
char name[32]; //用户名
char text[128]; //消息正文
} MSG_t;
#endif
3.SQL数据库
1.数据库的概念
数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。
数据库是存放数据的仓库。它的存储空间很大,可以存放百万条、千万条、上亿条数据。但是数据库并不是随意地将数据进行存放,是有一定的规则的,否则查询的效率会很低。当今世界是一个充满着数据的互联网世界,充斥着大量的数据。即这个互联网世界就是数据世界。数据的来源有很多,比如出行记录、消费记录、浏览的网页、发送的消息等等。
2.常用的数据库
大型数据库 :Oracle
中型数据库 :Server是微软开发的数据库产品,主要支持windows平台
小型数据库 : mySQL是一个小型关系型数据库管理系统。开放源码 (嵌入式不需要存储太多数据)
mySQL与SQLite区别:
MySQL和SQLite是两种不同的数据库管理系统,它们在多个方面有所不同。
1. 性能和规模:MySQL通常用于大型应用程序和网站,它可以处理大量数据和高并发访问。SQLite则更适合于小型应用程序或移动设备,因为它是一个轻量级的数据库引擎,不需要独立的服务器进程,可以直接访问本地文件。
2. 部署和配置:MySQL需要单独的服务器进程来运行,需要配置和管理数据库服务器。而SQLite是一个嵌入式数据库,可以直接嵌入到应用程序中,不需要单独的服务器进程。
3. 功能和特性:MySQL提供了更多的功能和高级特性,比如存储过程、触发器、复制和集群支持等。SQLite则是一个轻量级的数据库引擎,功能相对较少,但对于简单的数据存储和检索已经足够。
4. 跨平台支持:SQLite在各种操作系统上都能够运行,而MySQL需要在特定的操作系统上安装和配置数据库服务器。
总之,MySQL适用于大型应用程序和网站,需要处理大量数据和高并发访问,而SQLite适用于小型应用程序或移动设备,对性能和规模要求没有那么高。
3.SQL基础
SQLite的源代码是C,其源代码完全开放。它是一个轻量级的嵌入式数据库。
SQLite有以下特性:
零配置一无需安装和管理配置;
储存在单一磁盘文件中的一个完整的数据库;
数据库文件可以在不同字节顺序的机器间自由共享;
支持数据库大小至2TB(1024G = 1TB);//嵌入式足够
足够小,全部源码大致3万行c代码,250KB;
比目前流行的大多数数据库对数据的操作要快;
创建:
手动:
使用sqlite3工具,手工输入命令
命令行输入
代码:
利用代码编程,调用接口
4.在虚拟机中安装sqlite3
1.安装服务
2.安装数据库
tar xf sqlite-autoconf-3460000.tar.gz
cd sqlite-autoconf-3460000
./configure
make
sudo make install
3.安装完成测试是否安装成功
sqlite3 -version
4.图形化工具的安装
sudo apt-get install sqlitebrowser
5.SQL语句的使用
命令的方式操作
格式:sqlite3 数据库文件名(stu.db)
(创建一个新的数据库)
两种命令:
1. sqlite3系统命令(类似Windows系统命令,开机关机等,都是以.开头的)
都是以 '.' 开头的
a. .help 查看所有支持的命令
b. .quit 退出
c. .tables 查看有哪些表
d. .schema stu2 查看表结构
2. SQL命令 (具体对数据库怎样操作,对数据库增删改查用SQL命令)
SQL命令是以 “;” 结尾
在库当中创建一个表
(在数据库里面不严格检查数据类型,char可以表示字符,也可以表示字符串
流程
1.创建一个表
create table stu(id int,name char,score float);
create table stu1(id int primary key, name char, score float);
注:把id字段设置为主键(在表中唯一);
字符串:char string text
小数:float real
不支持严格的类型检查的;
2》 删除一个表
drop table <table_name>
...>;
3》 向表里面插入数据
insert into <table_name> values(value1, value2,…);
insert into stu values(1,'xiaomingx',99.9);
//只插入部分字段 id name score
insert into stu(id,name) values(4,'xiaoming')
4》 查找数据
查询表中所有记录
select * from <table_name>;
(*表示查询所有的值)
按指定条件查询表中记录
select * from <table_name> where <expression>;
select * from stu where id=2;
select * from stu where id=2 and name='lisi';
select * from stu where id=1 or name='zhangsan';
select score from stu where name='LiSi' or id=3; //满足条件的某列
select name,score from stu where name='LiSi' or id=3;
select * from stu limit 5; //只查询前n条记录
select * from stu order by id desc; //按id从大到小进行排序
5》 修改(更新)数据
update <table_name> set <f1=value1>, <f2=value2>… where <expression>;
update stu set id=10 where id=1;
6》 增加字段
alter table <table> add column <field> <type> default …;
alter table stu add column class int default 1;
(表示添加了一列class,默认值为1)
7》 删除字段(在数据库当中其实不支持直接删除一个字段(及一列),
如果就想删除一列,那么需要三步骤)
1)
创建一个student表,从stu表当中复制id,name,score
2) drop table stu;
删除原有的stu表
3) alter table student rename to stu;
重命名
最后一列为1的被删除掉了。
8》删除一行
操作完以后可以图形化界面修改东西,然后在命令行去查看的时候就被修改了。
或者
为什么不用图形化界面而是使用命令方式操作:
因为嵌入式里面用C写代码,C代码里面想实现对数据库进行操作,
用的就上面的命令,而C里面你不能在里面嵌套图像化界面。
sqlite3编程
官方文档
头文件:#include <sqlite3.h>
编译:gcc sqlite1.c -lsqlite3
函数接口
1.打开数据库
int sqlite3_open(char *path, sqlite3 **db);
功能:打开sqlite数据库,如果数据库不存在则创建它
参数:path: 数据库文件路径
db: 指向sqlite句柄的指针
返回值:成功返回SQLITE_OK(0),失败返回错误码(非零值)
2.返回错误信息
char *sqlite3_errmsg(sqlite3 *db);
功能: 获取错误信息
返回值:返回错误信息
使用: fprintf(stderr,"sqlite3_open failed %s\n",sqlite3_errmsg(db));
3.关闭数据库
int sqlite3_close(sqlite3 *db);
功能:关闭sqlite数据库
返回值:成功返回SQLITE_OK,失败返回错误码
4.执行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 */
);
功能:执行SQL操作
参数:db:数据库句柄
sql:要执行SQL语句
callback:回调函数(满足一次条件,调用一次函数,用于查询)
再调用查询sql语句的时候使用回调函数打印查询到的数据
arg:传递给回调函数的参数
errmsg:错误信息指针的地址
返回值:成功返回SQLITE_OK,失败返回错误码
回调函数:
typedef int (*sqlite3_callback)(void *para, int f_num,
char **f_value, char **f_name);
功能:select:每找到一条记录自动执行一次回调函数
参数:para:传递给回调函数的参数(由 sqlite3_exec() 的第四个参数传递而来)
f_num:记录中包含的字段数目
f_value:包含每个字段值的指针数组(列值)
f_name:包含每个字段名称的指针数组(列名)
返回值:成功返回SQLITE_OK,失败返回-1,每次回调必须返回0后才能继续下次回调
不使用回调函数执行SQL语句(只用于查询)
int sqlite3_get_table(sqlite3 *db, const char *sql,
char ***resultp, int *nrow, int *ncolumn, char **errmsg);
功能:执行SQL操作
参数:db:数据库句柄
sql:SQL语句
resultp:用来指向sql执行结果的指针
nrow:满足条件的记录的数目(但是不包含字段名(表头 id name score))
ncolumn:每条记录包含的字段数目
errmsg:错误信息指针的地址
返回值:成功返回SQLITE_OK,失败返回错误码
#include <stdio.h>
#include <sqlite3.h>
int callback(void *buf, int num, char **value, char **name);
int main(int argc, char const *argv[])
{
sqlite3 *db;
// 1.打开数据库
if (sqlite3_open("./stu.db", &db) != SQLITE_OK)
{
// 打印错误信息
fprintf(stderr, "open err:%s\n", sqlite3_errmsg(db));
return -1;
}
// 数据库的操作
char *errmsg = NULL;
if (sqlite3_exec(db, "create table if not exists stu(id int ,name char, score float);", NULL, NULL, &errmsg) != 0)
{
fprintf(stderr, "create err:%s\n", errmsg);
return -1;
}
printf("create table okk\n");
// 插入数据
/*
if (sqlite3_exec(db, "insert into stu values(1,'huanhuan',99.99)", NULL, NULL, &errmsg) != 0)
{
fprintf(stderr, "insert err:%s\n", errmsg);
return -1;
}
printf("insert okk\n");
*/
int num;
printf("请输入学生人数");
scanf("%d", &num);
int id;
char name[32] = {0};
float score;
char sql[128] = {0};
for (int i = 0; i < num; i++)
{
printf("请输入学生学号 姓名 成绩:");
scanf("%d %s %f", &id, name, &score);
sprintf(sql, "insert into stu values(%d,'%s',%f)", id, name, score);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
{
fprintf(stderr, "insert err:%s\n", errmsg);
return -1;
}
printf("insert okk\n");
}
// 查询数据
/**/
if (sqlite3_exec(db, "select * from stu ;", callback, "hello", &errmsg) != 0)
{
fprintf(stderr, "select err:%s\n", errmsg);
return -1;
}
printf("select okk\n");
// 专门用于查询数据的函数:sqlite3_get_table
// row:行
// column:列
char **result = NULL;
int row = 0, column = 0;
sqlite3_get_table(db, "select * from stu ;", &result, &row, &column, &errmsg);
int k = 0;
printf("row:%d column:%d\n",row,column);
for (int i = 0; i <= row; i++)
{
for (int j = 0; j < column; j++)
printf("%s ", result[k++]);
putchar(10);
}
// 关闭
sqlite3_close(db);
return 0;
}
int callback(void *buf, int num, char **value, char **name)
{
// 每查询到一条符合条件的数据就会调用一次函数
static int i = 1;
printf("%s:%d\n", (char *)buf, i++);
// num:列数
// value:值
// name:列名
for (int j = 0; j < num; j++)
{
printf("%s ", name[j]);
}
putchar(10);
for (int j = 0; j < num; j++)
{
printf("%s ", value[j]);
}
putchar(10);
printf("***************************************\n");
return 0; // 必须存在,不然sqlite_exec函数会报错
}