Day7网络编程(网络协议头+UDP+SQL数据库)

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函数会报错

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值