值得学习的内容:
1.网络通信TCP编程
2.多进程并发服务器
3.sqlite3轻量级数据库
4.IO多路复用编程,使用epoll族
client.c
#include "MYHEAD.h"
#define BACKLOG 5 //允许同时请求的客户端个数
#define PORT 5001 //端口号
#define TRANS 4 //单词解释
#define HIST 5 //历史记录
#define LINE_INFO printf("ERR:%s,%s,%d\n",__FILE__,__FUNCTION__,__LINE__)
#define LOGIN 1 //登录
#define REGISTER 2 //注册
#define QUIT 3 //退出
#define DB_PATH "./mydictory.db"
#define DICT_PATH "./dict.txt"
typedef struct MSG{
int type; //消息类型
char title[10]; //标题
char data[200]; //内容
}MSG;
/* 单词解释 */
int do_translate(int fd, MSG *msg, sqlite3 *db);
/* 查询历史记录 */
int do_history(int fd, MSG *msg, sqlite3 *db);
/* 登录 */
int do_check_login(int fd,MSG *msg,sqlite3 *db);
/* 注册 */
int do_register(int fd,MSG *msg,sqlite3 *db);
/* 子进程 */
void child_process(int fd,struct sockaddr_in client,sqlite3 *db);
/* 响应函数 */
void handler(int signal);
/* 服务器函数 */
int main(char argc,char **argv)
{
int fd,newfd; //文件描述符
pid_t pid; //进程id
char *errmsg; //sql库函数存放错误信息
sqlite3 *db; //数据库
char sql[250]; //sql语句
/* 打开数据库 */
if(sqlite3_open(DB_PATH,&db)!=SQLITE_OK){
LINE_INFO;
}
/* 创建用户ID密码表 */
sprintf(sql,"create table user(id int primary key,password text)");
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK){
LINE_INFO;
printf("%s\n",errmsg);
}
/* 创建用户查询记录表 */
sprintf(sql,"create table record(id int, word text, datetime text)");
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK){
LINE_INFO;
printf("%s\n",errmsg);
}
/* 绑定:信号--响应函数。子进程退出时,内核会发送SIGCHLD通知父进程 */
signal(SIGCHLD,handler);
/* 创建socket */
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
/* 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port=htons(PORT); //字节序转换
addr.sin_addr.s_addr = htonl(INADDR_ANY); //(INADDR_ANY)表示本机任意网卡地址
/* 绑定服务器的ip,port给刚创建的socket */
if(bind(fd,(struct sockaddr *)&addr,sizeof(addr)) < 0)
{
perror("bind");
exit(-1);
}
/* 监听,socket变成被动的 */
if(listen(fd,BACKLOG) < 0) //BACKLOG=同时请求的客户端个数
{
perror("listen");
exit(-1);
}
socklen_t client_len; //容器
struct sockaddr_in client; //容器,存放客户端的地址信息
printf("lstening...\n");
while(1){
/* 阻塞等待建立链接accept,fd属于监听类socket(被动),newfd属于连接类socket(主动) */
if((newfd = accept(fd,(struct sockaddr *)&client,&client_len))<0)
{
if(errno != EINTR){ //accept属于阻塞函数,要考虑被中断信号打断的情况。
perror("accept");
exit(-1);
}
}
printf("newfd:%d,fd:%d\n",newfd,fd); //每个进程都有一个独立的表,因此每个进程打印出来的fd数值可以相同
if((pid=fork())<0){
perror("fork");
exit(-1);
}
if(0 == pid){ //子进程
close(fd); //关闭监听socket,对子进程来说不需要监听
child_process(newfd,client,db);
}else{ //父进程,只负责accept
close(newfd); //关闭连接socket,对父进程来说不要连接
}
}
close(fd); //父进程关闭监听socket
return 0;
}
/* 子进程函数 */
void child_process(int fd,struct sockaddr_in client,sqlite3 *db){
MSG msg;
int n;
char ip[16];
char port[6];
while(1){
if((n=read(fd,(void *)&msg,sizeof(MSG)))<0){ //阻塞等待
if(n!=EINTR) //阻塞可能会被中断信号打断,并返回-1
{ //任何长等待机制都要考虑被中断的处理方式,用来区分异常错误
perror("read");
exit(-1);
}
}
inet_ntop(AF_INET,&client.sin_addr.s_addr,ip,16);
inet_ntop(AF_INET,&client.sin_port,port,6);
if(0==n){
printf("quit!\n%s:%s:\n",ip,port);
close(fd);
exit(0);
}
if(n>0){
/* 收到客户端的用户登录信息 */
switch(msg.type){
case LOGIN:
do_check_login(fd,&msg,db);
break;
case TRANS:
do_translate(fd,&msg,db);
break;
case HIST:
do_history(fd,&msg,db);
break;
case REGISTER:
do_register(fd,&msg,db);
break;
}
}
}
}
/* 注册 */
int do_register(int fd,MSG *msg,sqlite3 *db){
char sql[250];
char *errmsg,**resultp;
int nrow,ncolumn;
sprintf(sql,"insert into user values(%s,'%s')",msg->title,msg->data);
/* 插入记录 */
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK){
LINE_INFO;
printf("%s\n",errmsg);
strcpy(msg->data,errmsg);
}
else strcpy(msg->data,"ok");
if(send(fd,(void *)msg,sizeof(MSG),0)<0){
LINE_INFO;
perror("send");
return -1;
}
return 0;
}
/* 登录 */
int do_check_login(int fd,MSG *msg,sqlite3 *db){
char sql[250];
char *errmsg,**resultp;
int nrow,ncolumn;
int ret;
sprintf(sql,"select * from user where id=%s",msg->title);
/* 先查询id号是否存在 */
if(sqlite3_get_table(db,sql,&resultp,&nrow,&ncolumn,&errmsg)!=SQLITE_OK){
LINE_INFO;
printf("%s\n",errmsg);
strcpy(msg->data,errmsg);
}
/* 表格内容实际以一维数组存放,包括column名,从左到右,从上到下 */
/* nrow=行数,ncolumn=列数。如果ncolumn=0,说明数组内没有内容 */
else if(0==ncolumn){
/* id不存在 */
ret =0 ;
strcpy(msg->data,"id don't exist!\n");
}
else{
/* 密码正确 */
if(0==strcmp(msg->data,resultp[ncolumn*2-1]))
{
ret = 1;
strcpy(msg->data,"ok");
}
/* 密码错误 */
else{
ret = 0;
strcpy(msg->data,"password error!\n");
}
}
/* 返回信息 */
if(send(fd,(void *)msg,sizeof(MSG),0)<0){
LINE_INFO;
perror("send");
return -1;
}
printf("send!\n");
return ret;
}
/* 单词解释 */
int do_translate(int fd, MSG *msg, sqlite3 *db){
char sql[250]; //存放sql语句
char buf[512]; //存放从字典取出来的字符串
char word[20]; //存放用户查询的单词
char *errmsg;
FILE *dict;
int ret,length;
if((dict = fopen(DICT_PATH, "r+")) == NULL){
LINE_INFO;
perror("fopen");
return -1; //打开字典失败
}
printf("recv:%d:%s:%s\n",msg->type, msg->title, msg->data);
strcpy(word,msg->data);
length = strlen(word); //单词长度
/* 循环fgets,直到文件末尾 */
while(!feof(dict)){
/* 字典在txt文件中以行为单位,并且按照字母a-z排序 */
if(fgets(buf, 512, dict) == NULL){
LINE_INFO;
perror("fgets");
}
/* 用data的字串减去buf的字串 */
ret = strncmp(msg->data, buf, length); //匹配
printf("ret=%d,length=%d,word=%s\n",ret,length,word);
printf("%s\n",buf);
if(ret > 0){
/* 继续跟下一行比较,dog 与 a */
continue;
}
else if(ret < 0){
printf("/* 按照字母表顺序,往下已经没有能够匹配的了 */\n");
strcpy(msg->data, "not found!");
break;
}
else if(buf[length] != ' '){
printf("/* 考虑 a与abc的情况。前面匹配,后面却不是空格 */\n");
strcpy(msg->data, "not found!");
break;
}
/* 最后剩下匹配的情况 */
while(buf[length] == ' ')
length++; //跳过空格
strcpy(word,msg->data);
strcpy(msg->data, &buf[length]);
/* 记录保存到数据库内 */
time_t ti = time(NULL);
struct tm* tm = localtime(&ti);
sprintf(buf, "%d-%d-%d,%d:%d:%d", \
tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, \
tm->tm_hour, tm->tm_min, tm->tm_sec);
sprintf(sql,"insert into record values(%s,'%s','%s')",msg->title,word,buf);
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK){
LINE_INFO;
printf("%s\n",errmsg);
}
break;
}
fclose(dict);
if(send(fd,(void *)msg,sizeof(MSG),0)<0){
LINE_INFO;
perror("send");
return -1;
}
return 0;
}
/* 查询历史记录 */
int do_history(int fd, MSG *msg, sqlite3 *db){
char sql[250]; //存放sql语句
char *errmsg, **resultp;
int nrow, ncolumn, r;
msg->data[0] = '\0'; //写入空字符,代表字串长度=0
sprintf(sql,"select * from record where id=%s",msg->title);
if(sqlite3_get_table(db,sql,&resultp,&nrow,&ncolumn,&errmsg) < 0){
LINE_INFO;
printf("%s",errmsg);
return -1;
}
printf("recv:%d:%s:%s",msg->type, msg->title, msg->data);
/* 如果查不到记录,nrow=0,column=0 */
if(0 == nrow){
strcpy(msg->data, "not found record!\n");
if(send(fd,msg,sizeof(MSG),0)<0){
LINE_INFO;
perror("send");
return -1;
}
/* 查不到记录 */
return 1;
}
printf("/* 如果查到记录,nrow >= 2 column >= 1 */\n");
/* 总共nrow行,第1行是column名,查询历史记录只打印最后2条即可 */
if(nrow<3) r=1; //打印1条记录
else r=2; //打印2条记录
while(r--){
/* 打印倒数1或2条记录 */
sprintf(sql,"id=%s,word='%s',time:%s\n", \
resultp[(nrow-r)*ncolumn],resultp[(nrow-r)*ncolumn+1],resultp[(nrow-r)*ncolumn+2]);
strcat(msg->data,sql);
}
if(send(fd,msg,sizeof(MSG),0)<0){
LINE_INFO;
perror("send");
return -1;
}
return 1;
}
/* 信号响应函数 */
void handler(int signal){
if(SIGCHLD == signal){
waitpid(-1,NULL,WNOHANG); //WNOHANG代表非阻塞。
}
}
server.c
#include "MYHEAD.h"
//使用示例:./client 192.168.1.100 5001
#define TRANS 4 //单词解释
#define HIST 5 //历史记录
#define LINE_INFO printf("%s,%s,%d\n",__FILE__,__FUNCTION__,__LINE__)
#define LOGIN 1 //登录
#define REGISTER 2 //注册
#define QUIT 3 //退出
typedef struct MSG{
int type; //消息类型
char title[10]; //标题
char data[255]; //内容
}MSG;
/* 单词解释 */
int do_translate(int sockfd, MSG *msg);
/* 历史记录 */
int do_history(int sockfd, MSG *msg);
/* 注册模块 */
int do_register(int sockfd, MSG *msg);
/* 打印主菜单 */
int mainmenu(void);
/* 打印用户菜单 */
int usermenu(void);
/* 登录 */
int do_login(int sockfd, MSG *msg);
/******************************************************************/
/* 客户端主程序 */
int main(char argc,char **argv){
/* 判断输入参数个数 */
if(argc<3){
printf("Usage:%s <ip> <port>\n",argv[0]);
exit(-1);
}
MSG msg;
int fd, cmd;
/* 创建socket */
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(-1);
}
uint32_t ip; //容器
/* 192.xxx.xxx.xxx字符串形式,转换,32位网络字节序 */
if(inet_pton(AF_INET,argv[1],(void *)&ip) != 1){
perror("inet_pton");
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = ip;
addr.sin_port = htons(atoi(argv[2])); //网络字节序
/* 链接connect */
if(connect(fd,(struct sockaddr *)&addr,sizeof(addr))<0){
perror("connect");
exit(-1);
}
before_login:
while(1){
mainmenu(); //打印菜单
printf("input:");
/* 阻塞等待输入 */
cmd=0;
scanf("%d",&cmd);
getchar(); //过滤回车键
switch(cmd){
case LOGIN: //登录
if(do_login(fd, &msg)>0)
/* 登录成功,跳转 */
goto after_login;
break;
case REGISTER: //注册
do_register(fd, &msg);
break;
case QUIT:
/* 退出 */
close(fd); //关闭sockfd
printf("quit sucessed!\n");
exit(0);
break;
default:
printf("undefined cmd!\n");
}
}
/* 用户登录成功后 */
after_login:
while(1){
usermenu();
printf("input:");
cmd=0;
scanf("%d",&cmd);
getchar();
switch(cmd){
case TRANS:
/* 单词解释 */
do_translate(fd, &msg);
break;
case HIST:
/* 历史记录 */
do_history(fd, &msg);
break;
case QUIT:
/* 返回上级菜单 */
goto before_login;
break;
default:
printf("undefined cmd!\n");
}
}
return 0;
}
/* 打印主菜单 */
int mainmenu(void){
printf("**************************************************\n");
printf("\t\t主菜单\n");
printf("1:登录\t2:注册\t3:退出\n");
printf("**************************************************\n");
return 0;
}
/* 打印用户菜单 */
int usermenu(void){
printf("**************************************************\n");
printf("\t\t用户菜单\n");
printf("4:单词解释\t5:历史记录\t3:退出\n");
printf("**************************************************\n");
return 0;
}
/* 登录模块 */
int do_login(int sockfd,MSG *msg){
pid_t pid;
char buf[100];
msg->type=LOGIN; //登录类型
printf("input id:");
scanf("%s",msg->title);
getchar();
printf("input password:");
scanf("%s",msg->data);
getchar();
printf("wait for login...\n");
/* IO多路复用,同时监听socket与stdin */
int ret;
int epfd=epoll_create(2);
struct epoll_event event; //注册结构体
struct epoll_event ready[2]; //就绪结构体
event.events=EPOLLIN; //监听可读信号
event.data.fd=sockfd; //socket的fd
/* 注册sockfd */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
event.data.fd=0;
/* 注册stdin */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
/* 发送数据给服务器 */
if(send(sockfd,(void *)msg,sizeof(MSG),0)<0){
LINE_INFO; //输出位置信息
perror("send"); //输出errno信息
close(epfd);
return -1;
}
printf("/* 开始监听 */\n");
int n;
while((ret=epoll_wait(epfd,ready,2,-1))>0){
/* ret存放的是fd个数,需要配合遍历匹配 */
while(ret--){
if((0 == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* 标准输入fd有可读信号 */\n");
if(read(0,buf,100)<0){
LINE_INFO;
perror("read");
}
/* 比较1个字符 */
if(strncmp("#",buf,1)==0){
close(epfd);
return 0; //退出login模块
}
}
if((sockfd == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* sockfd有可读信号 */\n");
if((n=read(sockfd,(void *)msg,sizeof(MSG))>0)){
printf("recv:%s\n",msg->data);
if(0==strcmp("ok",msg->data)){
/* 登录成功 */
printf("login successed!\n");
printf("recv->type:%d,title:%s,data:%s\n",\
msg->type,msg->title,msg->data);
close(epfd);
return 1; //退出login模块
}
close(epfd);
return -1; //退出login模块
}
}
}
}
/* 登录失败 */
LINE_INFO;
perror("epoll_wait");
close(epfd);
return 0;
}
/* 注册模块 */
int do_register(int sockfd, MSG *msg){
pid_t pid;
char buf[100];
msg->type=REGISTER; //注册类型
printf("input id:");
scanf("%s",msg->title);
getchar();
printf("input password:");
scanf("%s",msg->data);
getchar();
printf("wait for register...\n");
/* IO多路复用,同时监听socket与stdin */
int ret;
int epfd=epoll_create(2);
struct epoll_event event; //注册结构体
struct epoll_event ready[2]; //就绪结构体
event.events=EPOLLIN; //监听可读信号
event.data.fd=sockfd; //socket的fd
/* 注册sockfd */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
event.data.fd=0;
/* 注册stdin */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
/* 发送数据给服务器 */
if(send(sockfd,(void *)msg,sizeof(MSG),0)<0){
LINE_INFO; //输出位置信息
perror("send"); //输出errno信息
close(epfd);
return -1;
}
printf("/* 开始监听 */\n");
int n;
while((ret=epoll_wait(epfd,ready,2,-1))>0){
/* ret存放的是fd个数,需要配合遍历匹配 */
while(ret--){
if((0 == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* 标准输入fd有可读信号 */\n");
if((n=read(0,buf,100))<=2){
/* 防止用户可能不止输入了1个字符 */
/* buf[0]='#' buf[1]=回车 */
if('#'==buf[0]){
/* login过程中,用户想要返回主菜单 */
close(epfd); //关闭io句柄
return 0; //中止
}
printf("undefined cmd!\n"); //输如了#以外的命令
}
}
if((sockfd == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* sockfd有可读信号 */\n");
if((n=read(sockfd,(void *)msg,sizeof(MSG))>0)){
printf("recv:%s\n",msg->data);
if(0==strcmp("ok",msg->data)){
/* 注册成功 */
printf("register successed!\n");
close(epfd);
return 1;
}
close(epfd);
return -1;
}
}
}
}
/* 注册失败 */
LINE_INFO;
perror("epoll_wait");
return 0;
}
/* 单词解释*/
int do_translate(int sockfd, MSG *msg){
char buf[100];
int ret; //存放返回值
int epfd=epoll_create(2); //监听集合
struct epoll_event event; //注册结构体
struct epoll_event ready[2]; //就绪结构体
/* 读取用户输入word */
msg->type = TRANS; //单词解释
printf("input word:");
scanf("%s",msg->data);
getchar();
/* IO多路复用,同时监听socket与stdin */
event.events=EPOLLIN; //监听可读信号
event.data.fd=sockfd; //socket
/* 注册sockfd */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
event.data.fd=0;
/* 注册stdin */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
/* 发送数据给服务器 */
if(send(sockfd,(void *)msg,sizeof(MSG),0)<0){
LINE_INFO; //输出位置信息
perror("send"); //输出errno信息
close(epfd);
return -1;
}
printf("send->type:%d,title:%s,data:%s\n",msg->type,msg->title,msg->data);
printf("/* 开始监听 */\n");
while((ret=epoll_wait(epfd,ready,2,-1))>0){
/* ret存放的是fd个数,需要配合遍历匹配 */
while(ret--){
if((0 == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* 标准输入fd有可读信号 */\n");
if(read(0, buf, 100)<0){
LINE_INFO;
perror("read");
}
/* 识别用户的输入 */
if(0 == strncmp("#",buf,1)){
close(epfd); //关闭监听集合
return 0; //退出单词解释模块
}
}
if((sockfd == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* sockfd有可读信号 */\n");
if(read(sockfd,(void *)msg,sizeof(MSG))>0){
printf("%s\n",msg->data);
close(epfd);
return 1; //收到单词解释,退出
}
}
}
}
close(epfd);
return 0;
}
/* 历史记录 */
int do_history(int sockfd, MSG *msg){
char buf[100];
int ret; //存放返回值
int epfd; //监听集合
struct epoll_event event; //注册结构体
struct epoll_event ready[2]; //就绪结构体
msg->type = HIST; //查询历史记录
if((epfd = epoll_create(2))<0){
LINE_INFO;
perror("epoll_create\n");
}
/* IO多路复用,同时监听socket与stdin */
event.events=EPOLLIN; //监听可读信号
event.data.fd=sockfd; //socket
/* 注册sockfd */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
event.data.fd=0;
/* 注册stdin */
if(epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event)<0){
LINE_INFO;
perror("epoll_ctl");
}
/* 发送数据给服务器 */
if(send(sockfd,(void *)msg,sizeof(MSG),0)<0){
LINE_INFO; //输出位置信息
perror("send"); //输出errno信息
close(epfd);
return -1;
}
printf("/* 开始监听 */\n");
while((ret=epoll_wait(epfd,ready,2,-1))>0){
/* ret存放的是fd个数,需要配合遍历匹配 */
while(ret--){
if((0 == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* 标准输入fd有可读信号 */\n");
if(read(0, buf, 100)<0){
LINE_INFO;
perror("read");
}
/* 识别用户的输入 */
if(0 == strncmp("#",buf,1)){
close(epfd); //关闭监听集合
return 0; //退出模块
}
}
if((sockfd == ready[ret].data.fd) && (EPOLLIN & ready[ret].events)){
printf("/* sockfd有可读信号 */\n");
if(read(sockfd,(void *)msg,sizeof(MSG))>0){
printf("%s\n",msg->data);
close(epfd);
return 1; //收到回复,退出
}
}
}
}
close(epfd);
return 0;
}