【Linux/C++网络篇(一) 】网络编程入门:一文搞懂 TCP/UDP 编程模型与 Socket 网络编程

【Linux/C++网络篇(一) 】网络编程入门:一文搞懂 TCP/UDP 编程模型与 Socket 网络编程

⭐️在这个怀疑的年代,我们依然需要信仰

个人主页:YYYing.

⭐️Linux/C++进阶系列专栏:【从零开始的linux/c++进阶编程】

系列上期内容:【Linux/C++多线程篇(二) 】同步互斥机制& C++ 11下的多线程

系列下期内容:暂无


目录

引言:程序如何“联网”?

网络编程基本概念

一、字节序

二、IP地址

IP地址的分类

特殊的IP地址

点分十进制

三、端口号

端口号的分类

网络编程基础

一、套接字(socket)的概念

二、基于TCP面向连接的通信方式

 📖 bind函数

 📖 listen函数

 📖 accept函数

 📖 recv、send数据收发

 📖 close关闭套接字

 📖 connect连接函数

三、TCP的C/S模型

服务端代码实现

客户端代码实现

四、基于UDP面向无连接的通信方式

 📖 recvfrom、sendto数据收发

五、UDP的C/S模型

服务端代码实现

客户端代码实现

结语

---⭐️封面自取⭐️---



引言:程序如何“联网”?

        你有没有想过,你写的 C/C++ 程序是怎么跟千里之外的服务器交换数据的?聊天软件、网页浏览、在线游戏……这些应用程序背后都依赖着网络编程。而网络编程的核心就是 Socket(套接字)——它就像程序打开的一扇“网络窗户”,数据从这扇窗户进出。

        那么我们现在就从零开始,通过代码实战,彻底搞懂网络通信的核心模型。

网络编程基本概念

        关于TCP和UDP的讲解我们计网篇章的传输层部分讲的内容已经足够了,我们本篇章的基础概念主要是围绕着字节序、ip和端口号来展开

一、字节序

        字节序:计算机在存储多字节整数时,根据主机的CPU处理架构不同,我们将主机分成大端存 储的主机和小端存储的主机

  • 大端存储:内存地址低位存储的是数据的高位
  • 小端存储:内存地址的低位存储的是数据的低位

        我们不妨可以来验证一下我们主机是大端存储还是小端存储

  • 使用指针来判断
#include <iostream> int main(int argc, const char *argv[]) { //定义一个整形变量 int num = 0x12345678; //定义一个字符类型的指针,指向整形变量的起始地址 char *ptr = (char *)&num; //对ptr所指向的字节中的内容进行判断,如果是0x12则说明是大端存储 //如果是0x78则说明是小端存储 if(*ptr == 0x12){ cout<<"big endian"<<endl; } else if(*ptr == 0x78){ cout << "little endian"<<endl; } std::cout << "Hello, World!" << std::endl; return 0; } 
  • 使用共用体来判断
#include <iostream> #include <cstdio> using namespace std; //定义一个共用体类型:多个成员共享一个成员的空间,共享的是所占内存空间最大的那个成员 union Info{ int num; //四字节整数 char ch; //一字节 }; int main(int argc, const char *argv[]) { //定义一个共用体变量 union Info temp; //给其整形成员赋值 temp.num = 0x12345678; //判断其ch成员 if(temp.ch == 0x12){ cout<<"big endian"<<endl; }else if(temp.ch == 0x78){ cout<<"little endian"<<endl; } std::cout << "Hello, World!" << std::endl; return 0; }

        而由于不同主机之间存储方式不同,可能会出现,小端存储的主机中的多字节整数,在网络传输过程中,明明没有出现任何问题,但是,由于大小端存储问题,导致,多字节整数传输出现错误。

        基于此,我们引入的网络字节序的概念,规定网络字节序都是大端存储的。

        无论发送端是大端存储还是小端存储,在传输多字节整数时,一律先转换为网络字节序。经由网络传输后,到达目的主机后,在转换为主机字节序即可。

        系统给大家提供了一套有关网络字节序和主机字节序之间相互转换的函数

  • 主机:host
  • 网络:network
  • 转换:to
#include <arpa/inet.h> //将4字节整数主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序 uint32_t htonl(uint32_t hostlong); //将2字节整数主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序 uint16_t htons(uint16_t hostshort); //将4字节整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序 uint32_t ntohl(uint32_t netlong); //将2字节整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序 uint16_t ntohs(uint16_t netshort); 

        那么我们何时使用网络字节序转换函数呢?

  • 在进行多字节整数网络传输时,需要使用字节序转换函数,最典型的就是我们的端口号
  • 在进行单字节整数传输时,不需要使用
  • 在网络中传输字符串时,也不需要使用

二、IP地址

        此处在我们之前计网篇章也讲过了,但此处我们再提及下个别概念

  • ip地址是主机在网络中的唯一标识,由两部分组成,分别是网络号和主机号。
    • 网络号:确定计算机所从属的网络
    • 主机号:标识该设备在该网络中的一个编号

        在网络传输过程中,给网络传输载体必须添加的信息,指定源ip地址和目的ip地址,以便于找到目的主机

IP地址的分类

        IPv4:是使用4字节无符号整数表示的一个ip地址,取值范围 [0, 2^32-1] 一共有四十多亿个,很明显不够,我们采用相关技术进行扩充局域网扩充:为了解决ip地址不够用,让多个主机共享一个ip地址 WAN:wide area network (广域网) LAN:local area network(局域网)

        IPv6:是使用16字节无符号整数表示的一个ip地址,取值范围 【0, 2^128-1】

        注意:IPv6是不兼容IPv4的。

网络类型取值范围网络号个数主机号个数用途
A类网络1.0.0.0 --- 127.255.255.2552^72^24已经保留不供给使用
B类网络128.0.0.0 --- 191.255.255.2552^142^16名地址网管中心
C类网络192.0.0.0 --- 223.255.255.2552^212^8家庭、校园、公司使用
D类网络224.0.0.0 --- 239.255.255.255------组播IP
E类网络240.0.0.0 --- 255.255.255.255------保留、实验室使用

特殊的IP地址

1、网络号 + 全为0的主机号:表示该网络,不分配给任何主机使用,例如:192.168.10.0



2、网络号 + 全为1的主机号:表示当前网络的广播地址,也不分配给任何主机使用,例如: 192.168.10.255



3、网络号 + 主机号为1:默认表示网关,当然可以自己制定网关ip



4、127.0.0.0:本地环回ip,当没有网络时,用于测试当前主机的ip



5、0.0.0.0:表示当前局域网中的任意一个主机号



6、255.255.255.255:一般表示广播地址

点分十进制

        为了方便记忆,我们将ip地址的每一个字节单独计算出十进制数据,并用点进行分割,这种方式,称为点分十进制,在程序中使用的是字符串来存储的。但是,ip地址的本质是4字节无符号整数,在网络中进行传输时,需要使用的是4字节无符号整数,而不是点分十进制的字符串。此时,就需要引入关于点分十进制数据向4字节无符号整数转换的相关函数

        地址:address

        网络:network

        转换:to

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //将点分十进制的ip地址转换为4字节无符号整数的网络字节序, //参数时点分十进制数据,返回值时4字节无符号整数 in_addr_t inet_addr(const char *cp); //将4字节无符号整数的网络字节序,转换为点分十进制的字符串 char *inet_ntoa(struct in_addr in);

三、端口号

        端口号(port)是一个 2 字节的无符号整数表示的数字,取值范围 【0, 65535】,为了区分同一个主机之间的每个进程的,使用端口号来进行标识

        为什么不使用进程号标识,而使用端口号?

        答:因为进程号是进程的唯一标识,当同一个应用程序,关闭再打开后,并不是同一个进程号了,但是是同一个应用程序

        所以,端口号标识的是我们的应用程序,当一个应用程序关闭再打开后,端口号不变

        引入端口号后,网络通信的两个重要因素就集结完毕:ip 地址 : 端口号ip地址可以在网络中,唯一确定对端的主机地址通过端口号能够找到该主机中指定的对端应用程序

端口号的分类

        1、0~1023 :众所周知的 “VIP”端口号:被特殊的应用程序已经占用了的。

        2、1024 ~ 49151:用户可分配的端口号

        3、49152~65535:动态分配或系统自动分配的端口号


网络编程基础

一、套接字(socket)的概念

        Socket 是操作系统提供的一个接口,它封装了底层网络细节。程序员通过 Socket 函数创建、绑定、连接、发送、接收数据,就像操作文件一样方便。

        我们可以调用函数:socket(),创建一个用于通信的套接字端点,并返回该端点对应的文件描述符

        在通信端点中,有两个缓冲区,分别对应发送缓冲区和接受缓冲区

函数原型int socket(int domain, int type, int protocol);
头文件

sys/types.h

sys/socket.h

功能为通信创建一个端点,并返回该端点对应的文件描述符,文件描述符的使用原则是最小未分配 原则
参数说明

参数1:协议族,常用的协议族如下

        AF_UNIX, AF_LOCAL     本地通信,同一主机的多进程通信

        AF_INET             提供IPv4的相关通信方式

        AF_INET6               提供IPv6的相关通信方式

具体可以看man 7 ip

参数2:通信类型,指定通信语义,常用的通信类型如下

        SOCK_STREAM     支持TCP面向连接的通信协议

        SOCK_DGRAM       支持UDP面向无连接的通信协议

参数3:通信协议,当参数2中明确指定特定协议时,参数3可以设置为0,但是有多个协议共同使用时,需要用参数3指定当前套接字确定的协议

返回值成功返回创建的端点对应的文件描述符,失败返回-1并置位错误码

二、基于TCP面向连接的通信方式

        在网络通信过程中,有两种通信方式,分别是基于BS模型的,即浏览器服务器模型,和基于CS模型,即客户端服务器模型,我们本篇章,使用的是基于CS模型

        那么通信原理就是下图

        值得一提的是,我们此处服务器用完listen启动监听状态,并不是我们服务器自身在等,而是相当于我们的服务器招了一位经理,这位经理来帮我们处理发来的连接请求,且这位经理还有着队列结构,当有人发来连接后,我们的经理就会看此时服务器有没有人在连接中,如果有就将其放在队列中,直到服务器的对端关闭了后再进行接受连接。

        造成这一点的主要原因是上面的示例是单线程阻塞模型,同一时间只能服务一个客户端(此处的 TCP 示例中无法同时处理多个新连接,需要配合多进程/多线程/IO多路复用)。

        你可以把这一套操作想象成电话机:

  • 创建 Socket = 买一部电话机
  • Bind = 给电话机分配一个号码(端口)
  • Listen/Accept = 等待别人打进来
  • Connect = 主动拨打别人的号码
  • Send/Recv = 通话内容

 📖 bind函数

函数原型int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件

sys/types.h

sys/socket.h

功能为套接字分配名称,给套接字绑定ip地址和端口号
参数说明

参数1:要被绑定的套接字文件描述符

参数2:通用地址信息结构体,对于不同的通信域而言,使用的实际结构体是不同的,该结构体的目 的是为了强制类型转换,防止警告

        通信域为:AF_INET而言,ipv4的通信方式

        struct sockaddr_in {

              sa_family_t    sin_family; /* 地址族: AF_INET */      

              in_port_t      sin_port;   /* 端口号的网络字节序 */

              struct in_addr sin_addr;   /* 网络地址 */

        };

        /* Internet address. */

        struct in_addr {

              uint32_t       s_addr;     /* ip地址的网络字节序 */

        };

        通信域为:AF_UNIX而言,本地通信

        struct sockaddr_un {

              sa_family_t sun_family;               /* 通信域:AF_UNIX */

              char        sun_path[UNIX_PATH_MAX];    /* 通信使用的文件 */

        };

参数3:参数2的大小

返回值成功返回0,失败返回-1并置位错误码

 📖 listen函数

函数原型int listen(int sockfd, int backlog);
头文件

sys/types.h

sys/socket.h

功能将套接字设置成被动监听状态
参数说明

参数1:套接字文件描述符

参数2:挂起队列能够增长的最大长度,一般为128

返回值成功返回0,失败返回-1并置位错误码

 📖 accept函数

函数原型int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
头文件

sys/types.h

sys/socket.h

功能阻塞等待客户端的连接请求,如果已连接队列中有客户端,则从连接队列中拿取第一个,并创 建一个用于通信的套接字
参数说明

参数1:服务器套接字文件描述符

参数2:通用地址信息结构体,用于接受已连接的客户端套接字地址信息的

参数3:接收参数2的大小

返回值成功发那会一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码

 📖 recv、send数据收发

函数原型ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t send(int sockfd, const void *buf, size_t len, int flags);
头文件

sys/types.h

sys/socket.h

sys/types.h

sys/socket.h

功能从套接字中读取消息放入到buf中向套接字文件描述符中将buf这个容器中的内容写入
参数说明

参数1:通信的套接字文件描述符

参数2:要存放数据的起始地址

参数3:读取的数据的大小

参数4:读取标识位,是否阻塞读取

      0:表示阻塞等待

      MSG_DONTWAIT:非阻塞

参数1:通信的套接字文件描述符

参数2:要发送的数据的起始地址

参数3:发送的数据的大小

参数4:发送标识位,是否阻塞发送

      0:表示阻塞等待

      MSG_DONTWAIT:非阻塞

返回值

可以是大于0:表示成功读取的字节个数

可以是等于0:表示对端已经下线(针对于TCP通信)

失败返回-1,并置位错误码

成功返回发送字节的个数

失败返回-1,并置位错误码


 📖 close关闭套接字

函数原型int close(int fd);
头文件

unistd.h

功能关闭套接字文件描述符
参数说明要关闭的套接字文件描述符
返回值成功返回0,失败返回-1并置位错误码

 📖 connect连接函数

函数原型int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件

sys/types.h

sys/socket.h

功能将指定的套接字,连接到给定的地址上
参数说明

参数1:要连接的套接字文件描述符

参数2:通用地址信息结构体

参数3:参数2的大小

返回值成功返回0,失败返回-1并置位错误码

三、TCP的C/S模型

服务端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #define SER_PORT 8888 #define SER_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于连接的套接字文件描述符 int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd == -1){ perror("socket error"); return -1; } printf("socket success sfd = %d\n", sfd); // 2、绑定ip地址与端口号 struct sockaddr_in sin; sin.sin_family = AF_INET; // 此处绑定数字都为网络字节序 sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){ perror("bind error"); return -1; } printf("bind success\n"); // 3、启动监听 if(listen(sfd, 128) == -1){ perror("listen error"); return -1; } printf("listen success\n"); // 4、阻塞等待客户端的连接请求 struct sockaddr_in cin; socklen_t socklen = sizeof(cin); int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen); if(newfd == -1){ perror("accept error"); return -1; } printf("[%s:%d]:已连接成功!!!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port)); // 5、数据收发 char rbuf[128] = ""; while(1){ bzero(rbuf, sizeof(rbuf)); int res = recv(newfd, rbuf, sizeof(rbuf), 0); if(res == 0){ printf("对端已下线\n"); break; } printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf); strcat(rbuf, "*_*"); if(send(newfd, rbuf, strlen(rbuf), 0) == -1){ perror("send error"); return -1; } printf("send success\n"); } // 6、关闭套接字 close(newfd); close(sfd); return 0; }

客户端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; #define SER_PORT 8888 #define SER_IP "192.168.160.129" #define CLI_PORT 9999 #define CLI_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于通信的客户端套接字文件描述符 int cfd = socket(AF_INET, SOCK_STREAM, 0); if(cfd == -1){ perror("socket error"); return -1; } printf("socket success cfd = %d\n", cfd); // 3 // 2、绑定ip与端口号(可选) struct sockaddr_in cin; cin.sin_family = AF_INET; cin.sin_port = htons(CLI_PORT); cin.sin_addr.s_addr = inet_addr(CLI_IP); if(bind(cfd, (struct sockaddr*)&cin, sizeof(cin)) == -1){ perror("bind error"); return -1; } printf("绑定成功\n"); // 3、链接服务器 struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){ perror("connect error"); return -1; } printf("连接服务器成功\n"); // 4、数据收发 char wbuf[128] = ""; while(1){ bzero(wbuf, sizeof(wbuf)); fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = 0; // 将换行改掉 if(send(cfd, wbuf, sizeof(wbuf), 0) == -1){ perror("send error"); return -1; } if(recv(cfd, wbuf, sizeof(wbuf), 0) == 0){ printf("对端已下线\n"); break; } printf("收到服务器消息为:%s\n",wbuf); } // 5、关闭套接字 close(cfd); return 0; }

四、基于UDP面向无连接的通信方式

        udp通信是面向无连接的,不可靠的,尽最大努力传输的通信方式,传输过程中,可能会出现数据的丢失、重复、失序、乱序等现象创建通信套接字是,使用的传输层名称为:SOCK_DGRAM

        此处不难发现我们两种模型的函数其实也就只有数据的收发不一样

 📖 recvfrom、sendto数据收发

函数原型ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
头文件

sys/types.h

sys/socket.h

sys/types.h

sys/socket.h

功能从套接字中读取消息放入到buf中,并接受对端的地址信息结构体向套接字中发送消息,并且指定对方的地址信息结构体
参数说明

参数1:套接字文件描述符

参数2:存放数据的容器起始地址

参数3:读取的数据大小

参数4:是否阻塞

        0表示阻塞

        MSG_DONTWAIT表示非阻塞

参数5:接收对端地址信息结构体的容器

参数6:参数5的大小

参数1:套接字文件描述符

参数2:要发送的数据的起始地址

参数3:要发送的数据大小

参数4:是否阻塞

        0表示阻塞

        MSG_DONTWAIT表示非阻塞

参数5:要发送的对端地址信息结构体

参数6:参数5的大小

返回值成功返回读取字节的个数,失败返回-1并置位错误码成功返回发送的字节的个数,失败返回-1并置位错误码

五、UDP的C/S模型

服务端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; #define SER_PORT 8888 #define SER_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于连接的套接字文件描述符 int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1){ perror("socket error"); return -1; } printf("socket success sfd = %d\n", sfd); // 2、绑定ip地址与端口号 struct sockaddr_in sin; sin.sin_family = AF_INET; // 此处绑定数字都为网络字节序 sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){ perror("bind error"); return -1; } printf("bind success\n"); // 3、数据收发 struct sockaddr_in cin; socklen_t socklen = sizeof(cin); char rbuf[128] = ""; while(1){ bzero(rbuf, sizeof(rbuf)); if(recvfrom(sfd, rbuf, sizeof(rbuf), 0, (struct sockaddr*)&cin, &socklen) == -1){ perror("recvfrom error"); break; } printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf); strcat(rbuf, "*_*"); sendto(sfd, rbuf, strlen(rbuf), 0, (struct sockaddr*)&cin, sizeof(cin)); printf("send success\n"); } // 4、关闭套接字 close(sfd); return 0; }

客户端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; #define SER_PORT 8888 #define SER_IP "192.168.160.129" #define CLI_PORT 9999 #define CLI_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于连接的套接字文件描述符 int cfd = socket(AF_INET, SOCK_DGRAM, 0); if(cfd == -1){ perror("socket error"); return -1; } printf("socket success sfd = %d\n", cfd); // 2、绑定ip地址与端口号 struct sockaddr_in cin; cin.sin_family = AF_INET; cin.sin_port = htons(CLI_PORT); cin.sin_addr.s_addr = inet_addr(CLI_IP); if(bind(cfd, (struct sockaddr*)&cin, sizeof(cin)) == -1){ perror("bind error"); return -1; } printf("绑定成功\n"); // 3、数据收发 struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); char wbuf[128] = ""; while(1){ bzero(wbuf, sizeof(wbuf)); fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = 0; // 将换行改成换行 sendto(cfd, wbuf, strlen(wbuf), 0, (struct sockaddr*)&sin, sizeof(sin)); printf("send success\n"); recvfrom(cfd, wbuf, sizeof(wbuf), 0, NULL, NULL); printf("服务器发来的消息为:%s\n", wbuf); } // 4、关闭套接字 close(cfd); return 0; }

结语

网络编程其实并不神秘,核心就是:创建 Socket -> 绑定/连接 -> 收发数据 -> 关闭

  • TCP 像打电话,建立连接后放心聊,适合重要数据。
  • UDP 像寄信,扔进邮筒就不管了,适合实时性要求高的场景。

        掌握了这些基础 API,你就拿到了通往高性能服务器开发的大门钥匙,但我们目前并不能让我们的TCP服务器实现多台客户端的连接,那么下一步我将带着大家进入TCP并发服务器的学习。

我是YYYing,后面还有更精彩的内容,希望各位能多多关注支持一下主包。

无限进步,我们下次再见!


---⭐️封面自取⭐️---

Read more

【MySQL数据库基础】(六)MySQL 表的约束详解:从基础到实战,拿捏数据合法性!

【MySQL数据库基础】(六)MySQL 表的约束详解:从基础到实战,拿捏数据合法性!

前言         在 MySQL 数据库开发中,我们总希望存入表中的数据是合法、规范、符合业务逻辑的。虽然数据类型能对字段做基础限制,但面对复杂的业务需求,仅靠数据类型远远不够。比如要求邮箱唯一、用户名不能为空、学生的班级必须是已存在的班级…… 这些需求都需要靠表的约束来实现。         表的约束是数据库保证数据完整性的核心手段,它能从业务逻辑层面过滤无效数据,避免脏数据进入数据库。今天这篇文章就带大家全面吃透 MySQL 中最常用的表约束,包括null/not null、default、comment、zerofill、primary key、auto_increment、unique key、foreign key,从基础概念到实操案例,手把手教你用约束拿捏数据合法性!下面就让我们正式开始吧! 一、为什么需要表的约束?         先看一个简单的例子:如果我们创建一个班级表,只定义字段和数据类型,不添加任何约束,会发生什么? -- 无约束的班级表 create table myclass( class_

By Ne0inhk
【MySQL数据库基础】(四)MySQL 表的操作通关指南:创建 / 修改 / 删除一网打尽

【MySQL数据库基础】(四)MySQL 表的操作通关指南:创建 / 修改 / 删除一网打尽

前言         上一篇我们讲了 MySQL 库的核心操作,作为 MySQL 数据存储的核心载体,数据表的操作更是开发和运维中的高频操作。从表的创建、结构设计,到日常的字段增删改、表名修改,再到最后的表删除,每一步都有对应的语法和实操细节,稍不注意就可能踩坑(比如误删字段导致数据丢失)。         这篇文章就基于 MySQL 实战场景,把表的全套操作讲透,从创建表的核心语法、存储引擎的差异,到修改表的各种场景,再到删除表的高危操作注意事项,让你一文掌握 MySQL 表操作的所有精髓,新手也能快速上手!下面就让我们正式开始吧! 一、创建表:打好基础,定好结构         创建数据表是表操作的第一步,也是最关键的一步 —— 表的结构设计直接决定了后续数据存储的效率和扩展性。MySQL 中创建表的语法支持自定义字段、字段类型、字符集、校验规则和存储引擎,灵活度拉满。 1. 核心创建语法         MySQL 创建表的官方标准语法如下,关键字和可选项的设计和库操作一脉相承,理解起来非常容易: CREATE TABLE

By Ne0inhk

MySQL 从入门到精通完全教程

目录 1. 前言 2. MySQL 基础认知 3. MySQL 安装与配置 4. MySQL 核心语法 5. 高级查询技巧 6. MySQL 函数 7. 数据约束 8. 事务管理 9. 索引优化 10. 存储过程与函数 11. 用户与权限管理 12. 性能优化实战 13. 常见问题与解决方案 1. 前言 1.1 什么是MySQL? MySQL 是一款开源的关系型数据库管理系统(RDBMS),基于SQL(结构化查询语言)实现数据管理,广泛应用于Web开发(如PHP+MySQL、Python+MySQL),特点是轻量、高效、跨平台、

By Ne0inhk
Flutter 组件 ansi_text 适配鸿蒙 HarmonyOS 实战:终端色彩渲染,构建高性能 ANSI 日志高亮与命令行交互架构

Flutter 组件 ansi_text 适配鸿蒙 HarmonyOS 实战:终端色彩渲染,构建高性能 ANSI 日志高亮与命令行交互架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 ansi_text 适配鸿蒙 HarmonyOS 实战:终端色彩渲染,构建高性能 ANSI 日志高亮与命令行交互架构 前言 在鸿蒙(OpenHarmony)生态迈向工业级运维、涉及大量后台守护进程(Daemon)、系统日志审计及开发者工具链(CLI)开发的背景下,如何为枯燥的纯文本终端注入具备视觉层级的色彩与样式,已成为提升调试效率与故障定位速度的“视觉助推器”。在鸿蒙设备这类强调 AOT 极致性能与低级别 shell 交互的环境下,如果应用依然依赖基础的单色字符串输出日志,由于由于信息流极其庞大且缺乏重点,极易由于由于“视觉疲劳”导致关键系统警告或业务异常被淹没在海量数据中。 我们需要一种能够支持 ANSI 转义序列、具备富文本样式(加粗/背景色)且兼容多种终端模拟器的文本渲染方案。 ansi_text 为 Flutter 开发者引入了基于标准

By Ne0inhk