Linux系统之网络编程
1 网络基础理论
1.1 OSI模型与TCP/IP协议
OSI体系结构,意为开放式系统互联。
国际标准组织(国际标准化组织)制定了OSI模型。
OSI模型把网络通信分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
1.2 TCP/IP体系模型
1.3 UDP与TCP
1.3.1 UDP的优缺点
1. 数据不可靠,会丢包,不会出现粘包和拆包现象
1.3.2 TCP的优缺点
1. 面向连接,数据可靠,不丢包,但是会出现粘包和拆包问题
2. 粘包:分多次发送小数据,接收方的buf中会存放一次的数据
3. 拆包:接收包接收大文件时,分好几次进行接收大文件或者是大数据
1.4 IP、端口和字节序
1.4.1 IP: 用来标识主机
IP地址 | 常用用途 |
---|---|
A类地址 | 分配给规模特别大的网络使用 |
B类地址 | 分配给一般的中型网络 |
C类地址 | 分配给小型网络,如一般的局域网和校园网 |
D类地址 | 一般多用于广播 |
E类地址 | 保留地址 |
1.4.2 端口: 标识是哪个进程
特殊的端口号
端口号 | 端口说明 |
---|---|
22 | SSH远程连接 |
5900 | VNC远程连接 |
80/443/8080 | 常见的web服务端口 |
53 | DNS域名系统 |
1.4.3 字节序:
1. 小端(little - endian)—— 低序字节存储在低地址
2. 大端(big - endian)—— 高序地址存储在低地址
3. 网络字节序:都是用的大端存储
4. 在使用网络传输数据的时候,需要注意字节序(大小端模式)
2 编程搭建服务器和客户端
2.1 socket接口相关函数
2.1.1 socket()函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/****************************************************
* Description: 创建一个socket套接字
* Input: domain:地址描述
* AF_INET 指的是IPV4
* AF_INET6 指的是IPV6
* type: 套接字的类型
* SOCK_STREAM 指的是TCP连接
* SOCK_DGRAM 指的是UDP连接
* protocol: 套接字所用的协议 一般填0
* Return: 成功:返回新的套接字
* 失败:返回 -1
* Others:
*****************************************************/
2.1.2 bind()函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
/****************************************************
* Description: 绑定指定的IP地址
* Input: sockfd:socket套接字
* my_addr: 绑定套接字的地址,这是一个协议族,使用的时候需要强转,才不会出现警告
* struct sockaddr_in addr;
* addr.sin_family = AF_INET;
* addr.sin_port = htons(PORT);
* addr.sin_addr.s_addr = inet_addr(IP);
* addrlen: 绑定地址的长度,sizeof(addr)
* Return: 成功:返回 0
* 失败:返回 -1
* Others:
*****************************************************/
2.1.3 listen()函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int s, int backlog);
/****************************************************
* Description: 自动接收到来的连接并且为连接队列指定一个长度限制
* Input: s:socket套接字
* backlog: 已经完成连接正等待应用程序接收的套接字队列的长度
* Return: 成功:返回 0
* 失败:返回 -1
* Others:
*****************************************************/
2.1.4 accept()函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
/****************************************************
* Description: 自动接收到来的连接并且为连接队列指定一个长度限制
* Input: s:socket套接字
* addr: 是已经连接的IP地址的结构体,使用的是同一个结构体
* struct sockaddr_in addr;
* 但是这里也是需要强转的
* addrlen: 用于存放结构体的长度
* Return: 成功:返回 连接套接字connetfd
* 失败:返回 -1
* Others:
*****************************************************/
2.1.5 connect()函数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/****************************************************
* Description: 自动接收到来的连接并且为连接队列指定一个长度限制
* Input: sockfd:socket套接字
* addr: 想要连接的IP地址的结构体
* struct sockaddr_in addr;
* addr.sin_family = AF_INET;
* addr.sin_port = htons(PORT);
* addr.sin_addr.s_addr = inet_addr(IP);
* addrlen: 用于存放结构体的长度
* Return: 成功:返回 0
* 失败:返回 -1
* Others:
*****************************************************/
2.1.6 send()函数
#include <sys/types.h>
#include <sys/socket.h>
int send(int s, const void *msg, size_t len, int flags);
/****************************************************
* Description: TCP协议——发送数据包
* Input: s:connet_fd套接字
* msg: 发送的buf包
* len: buf包的字节数
* flags:
* 一般设置为0 (0是阻塞的方式)
* MSG_DONTWAIT 使用非阻塞方式发送
* Return: 成功:返回 0
* 失败:返回 -1
* Others:
*****************************************************/
2.1.7 recv()函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
/****************************************************
* Description: TCP协议——接收数据
* Input: sockfd:connet_fd套接字(已经连接好的sock套接字,用变量connect_fd表示)
* buf: 接收buf缓冲区
* len: 接收缓冲区的字节数
* flags:
* 一般设置为0 (0是阻塞的方式)
* MSG_DONTWAIT 使用非阻塞方式发送
* Return: 成功:返回 接收到的字节数
* 失败:返回 -1
* Others:
*****************************************************/
2.1.8 sendto()函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
/****************************************************
* Description: UDP协议——指定IP地址发送数据
* Input: sockfd:connet_fd套接字(已经连接好的sock套接字,用变量connect_fd表示)
* buf: 接收buf缓冲区
* len: 接收缓冲区的字节数
* flags:
* 一般设置为0 (0是阻塞的方式)
* MSG_DONTWAIT 使用非阻塞方式发送
* dest_addr: 向谁发送数据 传入接收方的IP地址
* struct sockaddr_in server_addr;
* server_addr.sin_family = AF_INET;
* server_addr.sin_port = htons(PORT);
* server_addr.sin_addr.s_addr = inet_addr(IP);
* 传参时,需要加强转
* addrlen:
* 传参的结构体的长度
* sizeof(server_addr);
* Return: 成功:返回 发送数据的总字节数
* 失败:返回 -1
* Others:
*****************************************************/
2.1.9 recvfrom()函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
/****************************************************
* Description: UDP协议——接收数据
* Input: sockfd:connet_fd套接字(已经连接好的sock套接字,用变量connect_fd表示)
* buf: 接收buf缓冲区
* len: 接收缓冲区的字节数
* flags:
* 一般设置为0 (0是阻塞的方式)
* MSG_DONTWAIT 使用非阻塞方式发送
* dest_addr: 来自哪里的数据 传入发送方的IP地址
* struct sockaddr_in server_addr;
* server_addr.sin_family = AF_INET;
* server_addr.sin_port = htons(PORT);
* server_addr.sin_addr.s_addr = inet_addr(IP);
* 传参时,需要加强转
* addrlen:
* 传参的结构体的长度
* sizeof(server_addr);
* Return: 成功:返回 发送数据的总字节数
* 失败:返回 -1
* Others:
*****************************************************/
2.1.10 inet_aton()函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
/****************************************************
* Description: 将一个字符串IP地址转换为一个32位的网络序列IP地址
* Input: cp:要转换的IP地址字符串
* inp: 转换完成后,存放到in_addr结构体中
* Return: 成功:返回 非0
* 失败:返回 0
* Others:
*****************************************************/
2.1.11 inet_ntoa()函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
/****************************************************
* Description: 将一个32位的网络序列IP地址转换为一个字符串IP地址
* Input: in:要转换的IP地址
* Return: 成功:返回 转换成功的字符串IP地址
* 失败:返回 NULL
* Others:
*****************************************************/
2.1.12 inet_addr()函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
/****************************************************
* Description: 将字符串的IP地址转换为一个32位的网络序列IP地址
* Input: cp:要转换的字符串IP地址
* Return: 成功:返回 32位的网络序列IP地址
* 失败:返回 -1
* Others:
*****************************************************/
2.1.13 htons()函数
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
/****************************************************
* Description: 将数字端口号转换为网络字节序端口号
* Input: hostshort:数字端口号
* Return: 成功:网络字节序端口号
* 失败:
* Others:
*****************************************************/
2.1.14 ntohs()函数
#include <arpa/inet.h>
uint16_t ntohs(uint16_t hostshort);
/****************************************************
* Description: 将网络字节序的端口号转换为数字端口号
* Input: hostshort:要转换的网络字节序的端口号
* Return: 成功:数字端口号
* 失败:
* Others:
*****************************************************/
2.2 TCP服务器和TCP客户端
2.2.1 使用socket接口搭建——TCP服务器
基本流程: socket-->bind-->listen-->accept-->recv/send-->close
/******************************
> File Name : TCP_Server.c
> Author : GK
> Description : 搭建TCP服务器
> Created Time : 2020年11月03日 星期二 10时21分49秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
int main(int argc, char *argv[])
{
int connet_fd = 0;
int socket_fd = 0;
int re = 0;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5555);
addr.sin_addr.s_addr = inet_addr("192.168.1.192");
re = bind(socket_fd, (struct sockaddr *)&addr, sizeof(addr)); // 绑定
if (re == -1)
{
perror("bind");
exit(-1);
}
listen(socket_fd, 255); //(开机)一直处于监听状态
while (1)
{
connet_fd = accept(socket_fd, NULL, NULL); // 接听 accept函数只是将第一个客户端取出来,进行发数据,然后进入while(1)死循环中
printf("连接成功 connet_fd:%d\n", connet_fd);
char send_buf[512];
char recv_buf[512];
while (1)
{
memset(send_buf, 0, sizeof(send_buf));
memset(recv_buf, 0, sizeof(recv_buf));
re = recv(connet_fd, recv_buf, sizeof(recv_buf), 0); // 接收客户端发送过来的消息
if (re <= 0)
{
printf("客户端退出, re = %d\n", re);
break;
}
if ((recv_buf[0] == 'G') && (recv_buf[1] == 'e') && (recv_buf[2] == 't'))
{
time_t seconds;
time(&seconds);
strcpy(send_buf, ctime(&seconds));
printf("向客户端发送数据: %s\n", send_buf);
re = send(connet_fd, send_buf, sizeof(send_buf), 0); //发送
if (re == -1)
{
perror("send");
break;
}
}
}
close(connet_fd); // 关闭链接通道
}
close(socket_fd); // 关机
return 0;
}
2.2.2 使用socket接口搭建——TCP客户端
基本流程: socket-->bind(可选)-->connect-->send/recv-->close
/******************************
> File Name : TCP_Client.c
> Author : GK
> Description : 建立TCP客户端
> Created Time : 2020年11月03日 星期二 10时20分42秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int socket_fd = 0;
int re = 0;
socket_fd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字(TCP)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5555);
addr.sin_addr.s_addr = inet_addr("192.168.1.192");//填写服务器的IP地址
re = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)); //连接服务器
if(re == -1)
{
perror("connect");
exit(-1);
}
printf("链接服务器成功\n");
char send_buf[512];
char recv_buf[512];
while(1)
{
memset(send_buf, 0, sizeof(send_buf));
memset(recv_buf, 0, sizeof(recv_buf));
printf("请输入Get: ");
scanf("%s", send_buf);
if(*send_buf == 'q')
break;
else
send(socket_fd, send_buf, sizeof(send_buf), 0);
re = recv(socket_fd, recv_buf, sizeof(recv_buf), 0); // 接收客户端发送过来的消息
if (re <= 0)
break;
else
{
if(recv_buf[0] == '#')
printf("服务器下发的时间:%s\n", recv_buf+1);
}
}
close(socket_fd);
return 0;
}
2.3 UDP服务器和UDP客户端
2.3.1 使用socket接口搭建——UDP服务器
基本流程:socket-->bind-->recvfrom-->sendto-->close
/******************************
> File Name : UDP_Server.c
> Author : GK
> Description : 搭建UDP服务器
> Created Time : 2020年11月03日 星期二 10时21分49秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int socket_fd = 0;
int re = 0;
if(argc != 2)
{
printf("请输入IP地址\n");
exit(-1);
}
socket_fd = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP socket
//服务器自己的IP
struct sockaddr_in server_addr, client_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(7777);
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
re = bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); // 绑定IP
if (re == -1)
{
perror("bind");
exit(-1);
}
else
{
puts("连接成功!\n");
}
int client_addr_len = sizeof(client_addr);
char recv_buf[512];
while(1)
{
memset(recv_buf, 0, sizeof(recv_buf));
re = recvfrom(socket_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&client_addr, &client_addr_len);
printf("re: %d bytes, recv_buf: %s\n", re, recv_buf);
sendto(socket_fd, recv_buf, re, 0, (struct sockaddr *)&client_addr, sizeof(client_addr));
}
close(socket_fd);
return 0;
}
2.3.2 使用socket接口搭建——UDP客户端
基本流程:socket-->bind(可选)-->recvfrom-->sendto-->close
/******************************
> File Name : UDP_Client.c
> Author : GK
> Description : 建立UDP客户端
> Created Time : 2020年11月03日 星期二 10时20分42秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int socket_fd = 0;
int addr_len = 0;
int len = 0;
if(argc != 2)
{
printf("请输入IP\n");
exit(-1);
}
socket_fd = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP socket
//服务器自己的IP
struct sockaddr_in server_addr, client_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(7777);
server_addr.sin_addr.s_addr = inet_addr(argv[1]); //填 "0" 代表本机
addr_len = sizeof(server_addr);
char send_buf[512];
char recv_buf[512];
while(1)
{
memset(send_buf, 0, sizeof(send_buf));
memset(recv_buf, 0, sizeof(recv_buf));
fgets(send_buf, sizeof(send_buf), stdin);
send_buf[strlen(send_buf) - 1] = '\0';
if(*send_buf == 'q')
break;
sendto(socket_fd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
len = recvfrom(socket_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&server_addr, &addr_len);
printf("re: %d bytes, recv_buf: %s\n", len, recv_buf);
printf("IP: %s, Port: %s\n", inet_ntoa(server_addr.sin_addr, ntohs(server_addr.sin_port)));
}
close(socket_fd);
return 0;
}
3 UDP广播与多播
3.1 广播与组播相关函数
3.1.1 setsockopt()函数
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
/****************************************************
* Description: 用于任意类型、任意状态套接口的设置选项值
* Input: sockfd:socket套接字
* level: 选项定义的层次
optname:需设置的选项
optval: 指向存放选项待设置的新值的缓冲区
optlen: optval缓冲区长度
* Return: 成功:返回 32位的网络序列IP地址
* 失败:返回 -1
* Others:
*****************************************************/
3.2 UDP广播
3.2.1 发送广播消息
1. 开广播权限:int on = 1;
//setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
2. 向广播地址发送消息
//搭建udp客户端
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
//广播发送: 1.开广播权限
// 2.向广播地址发送
int main(int argc,char *argv[])
{
int sockfd,connfd;
int ret;
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建套接字(UDP)
/* 创建一张空卡addr */
struct sockaddr_in addr,cliaddr;
addr.sin_family = AF_INET;
addr.sin_port = htons(7777);
addr.sin_addr.s_addr = inet_addr("192.168.1.255");//填写广播地址
//开广播权限
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
char buf[512];
while(1)
{
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = 0;
if(*buf=='q')
{
break;
}
ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,sizeof(addr));
if(ret==-1)
{
perror("sendto");
exit(-1);
}
}
close(sockfd);
return 0;
}
3.2.2 接收广播消息
1.绑定广播地址或0地址
//搭建udp服务器
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
//广播接收: 1.绑定广播地址或0地址
int main(void)
{
int sockfd,connfd;
int ret;
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建套接字(UDP)
/* 创建一张空卡addr */
struct sockaddr_in addr,cliaddr;
addr.sin_family = AF_INET;
addr.sin_port = htons(7777);
addr.sin_addr.s_addr = inet_addr("0"); //填写ip (0:代表本机)
ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)); //绑定地址信息
if(ret==-1)
{
perror("bind");
exit(-1);
}
char buf[512];
int clilen = sizeof(cliaddr);
while(1)
{
memset(buf,0,sizeof(buf));
ret = recvfrom(sockfd, buf, sizeof(buf), 0 ,(struct sockaddr *)&cliaddr,&clilen);
if(ret==-1)
{
break;
}
printf("ip:%s,port:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
printf("recv:%d bytes,buf:%s\n",ret,buf);
//解析客户端请求 根据请求给响应数据
sendto(sockfd,buf, sizeof(buf), 0 , (struct sockaddr *)&cliaddr,sizeof(cliaddr));
}
close(sockfd);
return 0;
}
3.3 UDP组播
3.3.1 发送组播消息
1.向组播地址发送 例如 224.100.100.100
//搭建udp客户端
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
//组播发送: 1.向组播地址发送
int main(int argc,char *argv[])
{
int sockfd,connfd;
int ret;
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建套接字(UDP)
/* 创建一张空卡addr */
struct sockaddr_in addr,cliaddr;
addr.sin_family = AF_INET;
addr.sin_port = htons(7777);
addr.sin_addr.s_addr = inet_addr("224.100.100.100");//填写组播地址
char buf[512];
while(1)
{
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = 0;
if(*buf=='q')
{
break;
}
ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,sizeof(addr));
if(ret==-1)
{
perror("sendto");
exit(-1);
}
}
close(sockfd);
return 0;
}
3.3.2 接收组播消息
1.加入多播组
2.绑定组播地址或0地址
//搭建udp服务器
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
//组播接收: 1.绑定组播地址或0地址
// 2.加入多播组
int main(void)
{
int sockfd,connfd;
int ret;
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建套接字(UDP)
/* 创建一张空卡addr */
struct sockaddr_in addr,cliaddr;
addr.sin_family = AF_INET;
addr.sin_port = htons(7777);
addr.sin_addr.s_addr = inet_addr("0"); //填写ip (0:代表本机)
/* 通过setsockopt 加入多播组 */
//加入多播组
struct ip_mreqn mreq;
memset(&mreq,0,sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.100.100.100"); //组播地址
mreq.imr_address.s_addr = inet_addr("0");
setsockopt(sockfd,IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)); //绑定地址信息
if(ret==-1)
{
perror("bind");
exit(-1);
}
char buf[512];
int clilen = sizeof(cliaddr);
while(1)
{
memset(buf,0,sizeof(buf));
ret = recvfrom(sockfd, buf, sizeof(buf), 0 ,(struct sockaddr *)&cliaddr,&clilen);
if(ret==-1)
{
break;
}
printf("ip:%s,port:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
printf("recv:%d bytes,buf:%s\n",ret,buf);
//解析客户端请求 根据请求给响应数据
sendto(sockfd,buf, sizeof(buf), 0 , (struct sockaddr *)&cliaddr,sizeof(cliaddr));
}
close(sockfd);
return 0;
}
4 并发服务器
4.1 多进程并发服务器
基本思路: 每连接一个客户,创建进程,子进程负责处理客户请求,父进程负责连接请求
/******************************
> File Name : TCP_Server.c
> Author : GK
> Description : 搭建TCP并发服务器
> Created Time : 2020年11月03日 星期二 10时21分49秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
int tcp_Init(const char *ip, int port);
int do_client(int connfd);
void kill_Z_fork(int signum);
int main(int argc, char *argv[])
{
int tcp_sockfd = 0, tcp_connetfd = 0;
int addrlen = 0;
struct sockaddr_in clientaddr;
signal(SIGCHLD, kill_Z_fork);
tcp_sockfd = tcp_Init("192.168.1.192", 8888);
while (1)
{
addrlen = sizeof(clientaddr);
printf("等待连接\n");
tcp_connetfd = accept(tcp_sockfd, (struct sockaddr *)&clientaddr, &addrlen); // 建立连接套接字 这个地方会阻塞
//避免被信号打扰到,加一些判断,如果被打扰到,就continue 继续阻塞
if(tcp_connetfd == -1)
{
if(tcp_connetfd == EINTR) //避免被信号函数打扰
{
continue;
}
else //正常的错误进行退出
{
perror("accept");
exit(-1);
}
}
printf("连接成功 tcp_connetfd:%d IP: %s PORT: %d\n", tcp_connetfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
pid_t pid = fork();
if (pid == 0) //子进程负责处理 客户请求(connetfd)
{
close(tcp_sockfd);
do_client(tcp_connetfd); //执行do_client
}
else if (pid > 0) // 父进程 负责连接请求 (socketfd)
{
close(tcp_connetfd);
continue;
}
else
{
perror("fork");
break;
}
}
close(tcp_sockfd); // 关机
return 0;
}
/**
* TCP服务器的初始化
*/
int tcp_Init(const char *ip, int port)
{
int sockfd = 0;
int re = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
re = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); // 服务器绑定IP
if (re == -1)
{
perror("bind");
exit(-1);
}
re = listen(sockfd, 255); //(开机)一直处于监听状态
if (re == -1)
{
perror("listen");
exit(-1);
}
return sockfd;
}
/**
* while(1)循环处理客户端的消息
*/
int do_client(int connfd) //处理客户请求
{
char recv_buf[512];
char send_buf[512];
int ret;
while (1)
{
memset(recv_buf, 0, sizeof(recv_buf));
ret = recv(connfd, recv_buf, sizeof(recv_buf), 0); //这里会阻塞
if (ret <= 0)
{
printf("客户端退出\n");
perror("recv");
close(connfd);
exit(0); //结束子进程
}
printf("recv:%dbytes,recv_buf:%s\n", ret, recv_buf);
if (0 == strcmp(recv_buf, "get_time"))
{
time_t val = time(NULL);
char *ptr = ctime(&val);
strcpy(send_buf, ptr);
send(connfd, send_buf, sizeof(send_buf), 0);
}
}
}
/**
* 处理僵尸进程
*/
void kill_Z_fork(int signum)
{
printf("捕捉到僵尸信号\n");
while(waitpid(-1, NULL, WNOHANG) > 0); //处理僵尸进程
}
4.2 多线程并发服务器
基本思路: 每连接一个客户,创建线程,子线程负责处理客户请求,主线程负责连接请求
/******************************
> File Name : TCP_Server.c
> Author : GK
> Description : 搭建TCP并发服务器
> Created Time : 2020年11月03日 星期二 10时21分49秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
int tcp_Init(const char *ip, int port);
void *do_client(void *arg);
void *pthread_function1(void *arg);
int main(int argc, char *argv[])
{
int tcp_sockfd = 0, tcp_connetfd = 0;
int addrlen = 0;
struct sockaddr_in clientaddr;
pthread_t thread_id = 0;
tcp_sockfd = tcp_Init("192.168.1.192", 8888);
while (1)
{
addrlen = sizeof(clientaddr);
printf("等待连接\n");
tcp_connetfd = accept(tcp_sockfd, (struct sockaddr *)&clientaddr, &addrlen); // 建立连接套接字 这个地方会阻塞
//避免被信号打扰到,加一些判断,如果被打扰到,就continue 继续阻塞
if (tcp_connetfd == -1)
{
perror("accept");
exit(-1);
}
printf("连接成功 tcp_connetfd:%d IP: %s PORT: %d\n", tcp_connetfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
pthread_create(&thread_id, NULL, do_client, (void *)&tcp_connetfd);
}
close(tcp_sockfd); // 关机
return 0;
}
/**
* TCP服务器的初始化
*/
int tcp_Init(const char *ip, int port)
{
int sockfd = 0;
int re = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
re = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); // 服务器绑定IP
if (re == -1)
{
perror("bind");
exit(-1);
}
re = listen(sockfd, 255); //(开机)一直处于监听状态
if (re == -1)
{
perror("listen");
exit(-1);
}
return sockfd;
}
/**
* while(1)循环处理客户端的消息
*/
void* do_client(void *arg) //处理客户请求
{
pthread_detach(pthread_self());
int connfd = *((int*)arg);
char recv_buf[512];
char send_buf[512];
int ret;
while (1)
{
memset(recv_buf, 0, sizeof(recv_buf));
ret = recv(connfd, recv_buf, sizeof(recv_buf), 0); //这里会阻塞
if (ret <= 0)
{
printf("客户端退出\n");
perror("recv");
close(connfd);
break;
}
printf("recv:%dbytes,recv_buf:%s\n", ret, recv_buf);
if (0 == strcmp(recv_buf, "get_time"))
{
time_t val = time(NULL);
char *ptr = ctime(&val);
strcpy(send_buf, ptr);
send(connfd, send_buf, sizeof(send_buf), 0);
}
}
pthread_exit(NULL);
}
5 I/O模型
阻塞: 最常用、最简单、效率最低
非阻塞: 可防止进程阻塞在I/O操作上,需要轮询
io多路复用:允许同时对多个I/O进行控制
信号驱动: 一种异步通信模型
5.1 IO多路复用模拟并发服务器
5.1.1 select(框架)
//多线程TCP并发服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
#include <sys/time.h>
int tcp_init(const char *ip, int port);
int main(void)
{
int sockfd = 0;
int connfd = 0;
int re = 0;
char buf[512] = {0};
sockfd = tcp_init("0", 8089);
if (sockfd == -1)
{
exit(-1);
}
int maxfd = sockfd; //记录最大文件描述符
fd_set fd_read, tmpfs;
FD_ZERO(&fd_read);
FD_SET(sockfd, &fd_read); //添加文件描述符
tmpfs = fd_read;
while (1)
{
fd_read = tmpfs; //修正
re = select(maxfd + 1, &fd_read, NULL, NULL, NULL);
if (re == -1)
{
perror("select");
exit(-1);
}
int fd = 0;
for (fd = sockfd; fd <= maxfd; fd++)
{
if (FD_ISSET(fd, &fd_read))//printf("有事件产生\n");
{
if (fd == sockfd)
{
//printf("连接请求\n");
int connfd = accept(sockfd, NULL, NULL);
printf("connect a client :%d\n", connfd);
FD_SET(connfd, &tmpfs); //添加connfd
if (maxfd < connfd)
{
maxfd = connfd; //修正maxfd
}
}
else
{
//printf("客户请求\n");
memset(buf, 0, sizeof(buf));
re = recv(fd, buf, sizeof(buf), 0);
if (re <= 0)
{
printf("客户退出 %d\n", fd);
close(fd);
FD_CLR(fd, &tmpfs); //从表中清除
continue;
}
printf("recv:%s\n", buf);
}
}
}
}
return 0;
}
int tcp_init(const char *ip, int port) //tcp服务器初始化
{
int sockfd;
int re;
sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字(TCP)
/* 创建一张空卡addr */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip); //填写自己的ip
re = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); //绑定地址信息
if (re == -1)
{
perror("bind");
return -1;
}
listen(sockfd, 50); //设置监听队列 队列长度32
return sockfd;
}
5.1.2 poll(框架)
以下代码借鉴:Linux网络编程——tcp并发服务器(poll实现)
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#define OPEN_MAX 100
int main(int argc, char *argv[])
{
//1.创建tcp监听套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//2.绑定sockfd
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8000);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
//3.监听listen
listen(sockfd, 10);
//4.poll相应参数准备
struct pollfd client[OPEN_MAX];
int i = 0, maxi = 0;
for(;i<OPEN_MAX; i++)
client[i].fd = -1;//初始化poll结构中的文件描述符fd
client[0].fd = sockfd;//需要监测的描述符
client[0].events = POLLIN;//普通或优先级带数据可读
//5.对已连接的客户端的数据处理
while(1)
{
int ret = poll(client, maxi+1, -1);//对加入poll结构体数组所有元素进行监测
//5.1监测sockfd(监听套接字)是否存在连接
if((client[0].revents & POLLIN) == POLLIN )
{
struct sockaddr_in cli_addr;
int clilen = sizeof(cli_addr);
int connfd = 0;
//5.1.1 从tcp完成连接中提取客户端
connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
//5.1.2 将提取到的connfd放入poll结构体数组中,以便于poll函数监测
for(i=1; i<OPEN_MAX; i++)
{
if(client[i].fd < 0)
{
client[i].fd = connfd;
client[i].events = POLLIN;
break;
}
}
//5.1.3 maxi更新
if(i > maxi)
maxi = i;
//5.1.4 如果没有就绪的描述符,就继续poll监测,否则继续向下看
if(--ret <= 0)
continue;
}
//5.2继续响应就绪的描述符
for(i=1; i<=maxi; i++)
{
if(client[i].fd < 0)
continue;
if(client[i].revents & (POLLIN | POLLERR))
{
int len = 0;
char buf[128] = "";
//5.2.1接受客户端数据
if((len = recv(client[i].fd, buf, sizeof(buf), 0)) < 0)
{
if(errno == ECONNRESET)//tcp连接超时、RST
{
close(client[i].fd);
client[i].fd = -1;
}
else
perror("read error:");
}
else if(len == 0)//客户端关闭连接
{
close(client[i].fd);
client[i].fd = -1;
}
else//正常接收到服务器的数据
send(client[i].fd, buf, len, 0);
//5.2.2所有的就绪描述符处理完了,就退出当前的for循环,继续poll监测
if(--ret <= 0)
break;
}
}
}
return 0;
}
5.1.3 epoll(框架)
以下代码借鉴:Linux网络编程——tcp并发服务器(epoll实现)
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#define OPEN_MAX 100
int main(int argc, char *argv[])
{
struct epoll_event event; // 告诉内核要监听什么事件
struct epoll_event wait_event; //内核监听完的结果
//1.创建tcp监听套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//2.绑定sockfd
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8001);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
//3.监听listen
listen(sockfd, 10);
//4.epoll相应参数准备
int fd[OPEN_MAX];
int i = 0, maxi = 0;
memset(fd,-1, sizeof(fd));
fd[0] = sockfd;
int epfd = epoll_create(10); // 创建一个 epoll 的句柄,参数要大于 0, 没有太大意义
if( -1 == epfd ){
perror ("epoll_create");
return -1;
}
event.data.fd = sockfd; //监听套接字
event.events = EPOLLIN; // 表示对应的文件描述符可以读
//5.事件注册函数,将监听套接字描述符 sockfd 加入监听事件
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
if(-1 == ret){
perror("epoll_ctl");
return -1;
}
//6.对已连接的客户端的数据处理
while(1)
{
// 监视并等待多个文件(标准输入,udp套接字)描述符的属性变化(是否可读)
// 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时
ret = epoll_wait(epfd, &wait_event, maxi+1, -1);
//6.1监测sockfd(监听套接字)是否存在连接
if(( sockfd == wait_event.data.fd )
&& ( EPOLLIN == wait_event.events & EPOLLIN ) )
{
struct sockaddr_in cli_addr;
int clilen = sizeof(cli_addr);
//6.1.1 从tcp完成连接中提取客户端
int connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
//6.1.2 将提取到的connfd放入fd数组中,以便下面轮询客户端套接字
for(i=1; i<OPEN_MAX; i++)
{
if(fd[i] < 0)
{
fd[i] = connfd;
event.data.fd = connfd; //监听套接字
event.events = EPOLLIN; // 表示对应的文件描述符可以读
//6.1.3.事件注册函数,将监听套接字描述符 connfd 加入监听事件
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);
if(-1 == ret){
perror("epoll_ctl");
return -1;
}
break;
}
}
//6.1.4 maxi更新
if(i > maxi)
maxi = i;
//6.1.5 如果没有就绪的描述符,就继续epoll监测,否则继续向下看
if(--ret <= 0)
continue;
}
//6.2继续响应就绪的描述符
for(i=1; i<=maxi; i++)
{
if(fd[i] < 0)
continue;
if(( fd[i] == wait_event.data.fd )
&& ( EPOLLIN == wait_event.events & (EPOLLIN|EPOLLERR) ))
{
int len = 0;
char buf[128] = "";
//6.2.1接受客户端数据
if((len = recv(fd[i], buf, sizeof(buf), 0)) < 0)
{
if(errno == ECONNRESET)//tcp连接超时、RST
{
close(fd[i]);
fd[i] = -1;
}
else
perror("read error:");
}
else if(len == 0)//客户端关闭连接
{
close(fd[i]);
fd[i] = -1;
}
else//正常接收到服务器的数据
send(fd[i], buf, len, 0);
//6.2.2所有的就绪描述符处理完了,就退出当前的for循环,继续poll监测
if(--ret <= 0)
break;
}
}
}
return 0;
}
6 网络超时检测
6.1 *通过setsockopt()函数,设置接收超时
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
/****************************************************
* Description: 用于任意类型、任意状态套接口的设置选项值
* Input: sockfd:socket套接字
* level: 选项定义的层次
optname:需设置的选项
optval: 指向存放选项待设置的新值的缓冲区
optlen: optval缓冲区长度
* Return: 成功:返回 32位的网络序列IP地址
* 失败:返回 -1
* Others:
*****************************************************/
下面的例程实现的功能是:
- 使用fork()函数实现的多进程并发服务器
- 使用setsockopt()函数实现接收超时机制
- struct timeval tv = {3,0};
- setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
int do_client(int confd, struct sockaddr_in addr);
int tcp_init(const char *ip);
void do_signal(int sig) // 处理大量的僵尸进程
{
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
int main(int argc, char const *argv[])
{
int sock_fd, connd_fd;
pid_t pid;
struct sockaddr_in cli_addr;
signal(SIGCHLD, do_signal); // 捕获僵尸信号
if ((sock_fd = tcp_init("0")) == -1) // socket ——> TCP初始化
{
exit(-1);
}
while (1)
{
int cli_len = sizeof(cli_addr);
connd_fd = accept(sock_fd, (struct sockaddr *)&cli_addr, &cli_len); // 获取已连接的客户端的信息结构体
if (connd_fd == -1)
{
if (errno == EINTR) // 避免被信号中断打扰
{
continue;
}
else // 如果是函数返回的错误
{
perror("accept");
exit(-1);
}
}
struct timeval timeout; // 设置超时时间结构体 需要包含头文件#include <sys/time.h>
timeout.tv_sec = 3; //3秒
timeout.tv_usec = 0;
// 设置接收超时机制
setsockopt(connd_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
printf("accept done.\n");
pid = fork();
if (pid == 0) //fork会复制两份数据
{
close(sock_fd); // 子进程不使用sock_fd, 所以要关闭
do_client(connd_fd, cli_addr);
}
else if (pid > 0)
{
close(connd_fd); // 父进程不使用connd_fd, 所以要关闭
continue;
}
else
{
perror("fork");
exit(-1);
}
close(connd_fd);
}
close(sock_fd);
return 0;
}
int do_client(int confd, struct sockaddr_in addr) // 处理客户发来的数据
{
char buf[128] = {0};
ssize_t ret = 0;
while (1)
{
memset(buf, 0, sizeof(buf));
ret = recv(confd, buf, sizeof(buf), 0);
if (ret == 0)
{
perror("recv");
printf("客户端退出 IP: %s, Port: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
break;
}
else if(ret == -1)
{
if((errno == EAGAIN) || (errno == EWOULDBLOCK))
{
printf("时间超时\n");
//continue;
break;
}
else
{
exit(0);
}
}
printf("客户端登入 IP: %s, Port: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
printf("buf is :%s\n", buf);
if (send(confd, buf, sizeof(buf), 0) < 0)
{
perror("send");
break;
}
}
return 0;
}
int tcp_init(const char *ip)
{
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return -1;
}
printf("socket done.\n");
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6665);
addr.sin_addr.s_addr = inet_addr(ip);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
perror("bind");
return -1;
}
printf("bind done.\n");
if (listen(sockfd, 255) == -1)
{
perror("listen");
return -1;
}
printf("listen done.\n");
return sockfd;
}
6.2 在select函数中,设置超时时间
struct timeval tv; // 描述时间的结构体变量
fdset rdfds; // 定义读描述符集合
……
while(1)
{
tv.tv_sec = 8;
tv.tv_usec = 0;
FD_ZERO(&rdfds);
FD_SET(sockfd, &rdfds);
if (select(sockfd+1, &rdfds, NULL, NULL, &tv) == 0)
{
超时处理
}
}
……
6.3 通过alarm函数设置闹钟,捕获闹钟
这种方法的原理是在从套接字接收数据之前先设置8秒钟的定时器。如果8秒钟内没有数据到来,内核产生的SIGALRM信号会中断。
void handler(int signo) // 自定义SIGALRM信号处理函数
{
return;
}
struct sigaction act; // 描述信号行为的变量
……
sigaction(SIGALRM, NULL, &act); // 获取SIGALRM信号的属性
act.sa_handler = handler; // 设置SIGALRM信号的处理函数
act.sa_flags &= ~SA_RESTART; // 关闭重启被中断操作的选项
sigaction(SIGALRM, &act, NULL); // 设置SIGALRM信号的属性
alarm(8); // 设置8秒钟的定时器
……
7 数据库
7.1 在线安装SQlite3数据库
sudo apt-get install sqlite3
7.2 数据库的基本使用
7.2.1 创建数据库 .db
sqlite3 student.db
7.2.2 系统命令:以 ‘.’ 开头
.help 帮助手册
.exit 退出
.quit 退出
.open 打开
.schema 查看表的结构
.databases 查看打开的数据库
.table 查看当前数据库下 已经有的表格
7.2.3 基本命令:以 ‘;’ 结尾
7.2.3.1 创建记录表
create table stu(id int, name char, score int);
//创建一张列表 列表名:stu 列表内id是ine型 ...
7.2.3.2 插入记录表
//完整的插入:
insert into stu values(1001, "zhangsan", 80);
insert into stu values(1002, "lisi", 50);
//部分插入
insert into stu (name, score) values("kusu", 30);
7.2.3.3 查看记录表
//查看表格中的内容 '*'代表的是全部内容
select * from stu;
//查看表格中的 部分 字段
select name from stu;
select name,score from stu;
//查看score等于80的
select * from stu where score=80;
//查看score=80并且id=1001的
select * from stu where score=80 and id=1001;
//查看score=40或者id=1002的
select * from stu where score=40 and id=1002;
7.2.3.4 删除记录表
//删除整个记录表中的内容,但是文件还在
delete from stu;
//删除一条记录
delete from stu where score=40;
7.2.3.5 更新一条记录
update stu set name="zhangsan" where id=1001;
update stu set name="zhangsan" , score=80 where id=1001;
7.2.3.6 插入一列
alter table stu add colimn address char;
7.2.3.7 删除一列
sqlite3 不支持,直接删除一列
所以分三个步骤去做:
1.创建一张新的表
create table stu1 as select id, name, score from stu;
2.删除原有的表
drop table stu;
3.将新的表名字改成原有的旧表的名字
alter table stu1 rename to stu;
7.3 使用C语言API接口
还有其他的函数接口,需要去官网查看,以下是基本的函数:
int sqlite3_open(char *path, sqlite3 **db);
/************************************
功能:打开sqlite3数据库
参数:
path:数据库文件的路径
db:指向sqlite句柄的指针
返回值:
成功:SQLITE_OK
失败:返回错误码
***********************************/
int sqlite3_close(sqlite3 *db);
/************************************
功能:关闭数据库
返回值:
成功:SQLITE_OK
失败:返回错误码
const char *sqlite3_errmg(sqlite3 *db);
返回值:返回错误信息
***********************************/