本项目主要由服务器,消费者,商家,外卖员组成。基本的功能如下。
1、可以注册登录
2、可以修改个人信息
3、可以销户
1、注册时需要填写售卖商品信息
2、可以修改商品信息对自己的商品增删改查
1:可以查看商家列表并进入商家列表选购商品
2:可以查看购物车自己已经选购的商品
3:确认完购物车后将商品放入订单列表等待外卖员接单
4:10分钟还未接单将发消息给消费者让其重新下单
5:选购完商品后将选购信息发送给商家
1:可以查看订单列表
2:可以接单并派送
3:将外卖送给消费者后自动结单
1:支持所有身份的登录注册功能,分类储存当前用户(商家、消费者、外卖员)
2:对外卖员:储存当前订单列表、需要时打印订单至终端
3:对消费者:支持多个用户同时在线选购以及购物车系统
4:对消费者:支持计时功能到达十分钟后无人接单自动取消订单并提示消费者重新下单
5:会记录所有订单的订单编号和结单时间
6:支持商家和消费者之间的订单传输功能
本项目使用sqlite数据库,在服务器端需要有一套比较完备的数据库结构。本次项目主要创建了四个不同的数据库,分别是:商家、其他人(消费者和外卖员)、购物车、订单记录。
对于商家数据库来说,他必须有一个merchant表单用于记录商家用户的用户名和密码。商家的用户名就是商家店铺的名称。将用户名设为主键,不允许重复。每当一个商家注册时,需要填写自己出售的商品,数据库中会自动再创建一个名为该商家的表单用于存储出售的商品信息(商品名称及商品价格)。
对于其他人来说,要有一个username表单用于记录外卖员和消费者的用户名,防止用户名重复。再创建消费者和外卖员两个表单分别存储用户名和密码。
对于购物车,只需要当消费者添加商品进购物车时创建,再消费者确认购物车后删除。是一个比较动态的。
对于记录来说,他会记录订单编号、确认订单时间、结束时间、消费者用户名和外卖员用户名。在确认订单后存入除了结束时间的其他内容,并将订单编号发给外卖员客户端。当外卖员结束订单后将结束时间填入。
在消费者确认订单后,会将订单存放在服务器的链表中,链表中有消费者用户名,商家用户名,下单时间、消费者客户端的信息结构体和套接字接口。在存入链表中会设置一个闹钟alarm。在设定时间到了之后向进程发送SIGALRM信号,然后使用signal函数捕捉这种信号后去遍历订单链表,给订单的消费者发信息并将超时的订单删掉。如果订单被外卖员接走,那么也会删除该订单。这样在遍历的时候也找不到要删除的订单,以实现超时机制。
本项目的信息交互非常多,这对信息的种类分类有着很高的要求。我是用的是类似tftp协议的信息传输方式,将信息的种类封装进一个字符数组中,然后在服务器端进行解码,并实现相应功能。
最后附上本次项目的主函数源码
int main(int argc, const char *argv[])
{
if(signal(SIGALRM,handle)==SIG_ERR)
{
perror("signal error:");
return -1;
}
//打开所有的数据库
sqlite3 *shangjialist=NULL;
if(sqlite3_open("./shangjialist.db",&shangjialist)!=SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(shangjialist));
return -1;
}
sqlite3 *otherlist=NULL;
if(sqlite3_open("./otherlist.db",&otherlist)!=SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(otherlist));
return -1;
}
sqlite3 *gouwuche=NULL;
if(sqlite3_open("./gouwuche.db",&gouwuche)!=SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(otherlist));
return -1;
}
sqlite3 *recorder=NULL;
if(sqlite3_open("./record.db",&recorder)!=SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(otherlist));
return -1;
}
//初始化所有的数据库
init_sqlite(shangjialist,otherlist,recorder);
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1)
{
perror("socket error:");
return -1;
}
int reuse = 1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1)
{
perror("setsockaddr error:");
return -1;
}
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;
}
if(listen(sfd,128)==-1)
{
perror("listen error:");
return -1;
}
struct sockaddr_in cin;
socklen_t socklen=sizeof(cin);
int n=2;
struct pollfd pfd[1024];
pfd[0].fd=0;
pfd[0].events=POLLIN;
pfd[1].fd=sfd;
pfd[1].events=POLLIN;
while(1)
{
int res=poll(pfd,n,-1);
if(res==0)
{
printf("manba out\n");
return -1;
}
else if(res==-1&&errno!=4)
{
perror("poll error:");
return -1;
}
if(pfd[1].revents==POLLIN)
{
int newfd=accept(sfd,(struct sockaddr*)&cin,&socklen);
pfd[n].fd=newfd;
pfd[n].events=POLLIN;
n++;
printf("[%s:%d:%d]已连接\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
}
if(pfd[0].revents==POLLIN)
{
}
for(int i=2;i
int main(int argc, const char *argv[])
{
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1)
{
perror("socket error:");
return -1;
}
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(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("connect error:");
return -1;
}
pid_t pid=fork();
if(pid==0)
{
//子进程,用于接受消息
char buf[111]="";
while(1)
{
sleep(1);
bzero(buf,sizeof(buf));
recv(sfd,buf,sizeof(buf),0);
if(strcmp(buf,"quit")==0)
{
break;
}
printf("%s\n",buf);
}
exit(EXIT_SUCCESS);
}
else if(pid>0)
{
while(1)
{
printf("====================欢迎使用====================\n");
printf("1登录\t2注册\t0退出\n");
printf("请输入命令\n");
int cmd;
scanf("%d",&cmd);
getchar();
switch(cmd)
{
case 1:
{
login(sfd);
break;
}
case 2:
{
registe(sfd);
break;
}
case 0:
{
kill(pid,SIGKILL);
char buf[111]="";
short *type_point=(short *)buf;
*type_point=htons(4002);
send(sfd,buf,sizeof(buf),0);
close(sfd);
wait(NULL);
return 0;
}
default:
{
printf("输入有误\n");
}
}
printf("请输入任意键清屏\n");
while(getchar()!='\n');
//system("clear");
}
}
return 0;
}