1引言
1.1编写目的
经过一段时间的学习,终于要开始一个实战项目了,这个项目目的就是为了回顾之前的知识,主要用到了网络编程客户端及并发服务器的实现,父子进程,信号,sql数据库等,我们先详细理解需求然后对框架及相关进行梳理,思路清晰后,再开始编程。
1.2 开发环境
开发环境 | 版本 |
linux gcc工具链 |
|
LIB | sqlite3 |
1.3 项目需求
基于网络编程和数据库实现在线词典功能,客户端可以注册,登入,查询历史信息等操作,服务器基于多进程实现多客户端的并发访问,并使用sqlite数据库实现对用户信息的管理。
需求分析,进行需求拆分:
- 使用tcp实现客户端和服务器
- 服务器使用并发服务器(多进程,多线程或者select多路复用)
- 建立一个数据库,数据库中包含两个表,一个表用于存储用户名及密码等信息,一个用于存 储历史信息(包含保存时间)
- 类似我们平时看到的功能,需要有二级菜单,一级菜单中包含用户的注册,用户登录,用户 退出。登录成功后,进入二级菜单,二级菜单包括单词查询,历史查询,退出。
注意:注意进程的回收防止僵尸进程(通过信号回收),用户注册不能出现重复的用户名等,确 保功能完整,性能优化可以之后慢慢进行。
2软件总体设计
1.结构框架(参考)
2.客户端概要框架:
3软件详细设计
3.1服务器端程序流程
3.2客户端程序流程
3.3参数数据结构
4.代码实例(框架)
4.1 net.h (包含需要用到的头文件及结构体等)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sqlite3.h>
#include <signal.h>
#define SERV_PORT 5001
#define BACK_LOG 5
#define BUFSIZE 100
#define N 32
#define R 1 //register
#define L 2 //login
#define Q 3 //query
#define H 4 //history
// 定义通信双方的信息结构体
typedef struct {
int type;
int root_flag;
char name[N];
char data[256];
}MSG;
4.2 client.c(客户端实现)
#include "net.h"
int do_register(int sockfd, MSG *msg)
{
return 0;
}
int do_login(int sockfd, MSG *msg)
{
return 1;
}
int do_query(int sockfd, MSG *msg)
{
return 0;
}
int do_history(int sockfd, MSG *msg)
{
return 0;
}
int main(int argc,char *argv[])
{
struct sockaddr_in sin;
int temp;
int fd;
MSG msg;
/**************************TCP client操作*****************************/
if(argc < 2){
printf("please input %s + ip 192.168.x.xxx\n",argv[0]);
exit(1);
}
//socket
if((fd = socket(AF_INET,SOCK_STREAM, 0) )< 0){
perror ("socket");
exit (1);
}
//connect
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT);
sin.sin_addr.s_addr = inet_addr (argv[1]);
if( connect (fd, (struct sockaddr *)&sin,sizeof(sin)) < 0){
perror ("connect ");
exit (1);
}
printf("connect success\n");
/********************************************************************/
while(1){
//一级菜单,提示用户输入
printf("*****************************************************************\n");
printf("* 1.register 2.login 3.quit *\n");
printf("*****************************************************************\n");
printf("Please choose:");
//等待用户选择
scanf("%d", &temp);
getchar();
switch(temp){
case 1:
do_register(fd,&msg);
break;
case 2:
if(do_login(fd,&msg)){
goto next;
}
break;
case 3:
close(fd);
exit(0);
break;
default:
printf("Please enter the correct command\n");
}
}
next:
//进入二级菜单
while(1){
printf("*****************************************************\n");
printf("* 1.query_word 2.history 3.quit *\n");
printf("*****************************************************\n");
printf("Please choose:");
scanf("%d", &temp);
getchar();
switch(temp){
case 1:
do_query(fd, &msg);
break;
case 2:
do_history(fd, &msg);
break;
case 3:
close(fd);
exit(0);
break;
default:
printf("Please enter the correct command\n");
}
}
return 0;
}
4.3 server.c
#include "net.h"
#define DATABASE "my.db"
void do_register(int fd, MSG *msg, sqlite3 *db)
{
;
}
int do_login(int fd, MSG *msg, sqlite3 *db)
{
return 0;
}
int do_query(int fd, MSG *msg, sqlite3 *db)
{
return 0;
}
int do_history(int fd, MSG *msg, sqlite3 *db)
{
return 0;
}
int do_client(int fd, sqlite3 *db)
{
MSG msg;
while(recv(fd, &msg, sizeof(msg), 0) > 0)
{
switch(msg.type)
{
case R:
do_register(fd, &msg, db);
break;
case L:
do_login(fd, &msg, db);
break;
case Q:
do_query(fd, &msg, db);
break;
case H:
do_history(fd, &msg, db);
break;
default:
printf("Please enter the correct command\n");
}
}
close(fd);
exit(0);
return 0;
}
int main(int argc,char *argv[])
{
struct sockaddr_in sin;
int fd,connfd;
MSG msg;
sqlite3 *db;
char *errmsg;
pid_t pid;
char sql[128] = {0};
/***************************数据库操作***********************************/
//打开数据库
if(sqlite3_open(DATABASE, &db) != SQLITE_OK){
printf("%s",sqlite3_errmsg(db)); //打印出错信息
return -1;
}
//创建表 user
if(sqlite3_exec(db, "create table if not exists user(name text primary key, data text);",NULL,NULL,&errmsg) != SQLITE_OK){
printf("%s\n",errmsg); //打印出错信息
return -1;
}
//在表中加入超级用户
sprintf(sql,"insert into user(name,data) values('%s','%s');","root","1");
if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){
printf("%s\n", errmsg);
}
else
{
printf("Insert success.\n");
}
/**************************TCP server操作*****************************/
//socket
if((fd = socket(AF_INET,SOCK_STREAM, 0) )< 0){
perror ("socket");
exit (1);
}
//bind
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if( bind(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0){
perror ("bind");
exit (1);
}
//更改属性,使ip能够快速重用
int b_reuse =1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
//listen
if( listen(fd,BACK_LOG) < 0){
perror ("listen");
exit (1);
}
printf("listen success\n");
//处理僵尸进程
signal(SIGCHLD, SIG_IGN);
/********************************************************************/
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1){
/**********************************************************************************/
//accept
if ((connfd = accept(fd, (struct sockaddr *) &cin, &addrlen)) < 0){
perror ("accept");
exit (1);
}
char ipv4_addr[16];
if (!inet_ntop(AF_INET, (void *)&cin.sin_addr, ipv4_addr, sizeof(cin))) {
perror("inet_ntop");
exit(1);
}
printf("Clinet(%s:%d) is connected!\n", ipv4_addr, htons(cin.sin_port));
/**********************************************************************************/
if((pid = fork()) < 0)
{
perror("fork");
return -1;
}
else if(pid == 0) // 子进程
{
close(connfd);
do_client(connfd, db);//处理客户端具体的消息
}
else // 父进程,用来接受客户端的请求的
{
close(connfd);
}
}
return 0;
}
4.4 Makefile
all:client server
.PHONY : all
CC = gcc
client : client.c
$(CC) -o client client.c
server : server.c
$(CC) -o server server.c -lsqlite3
.PHONY : clean
clean:
rm client server my.db
因为是搭建的框架,具体的功能后期填充,目前通过编译可以看到数据库的生成及TCP的正常连接。