前言:最近在学进程间通信,所以做了一个小项目练习一下。主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作不熟悉的可以参照我的这篇博客:https://www.cnblogs.com/liudw-0215/p/9593414.html,所有代码提交我的Github上,地址:https://github.com/ldw0215/Chat-System.git,可以自行下载,然后make一下就可以了。
一、架构解析
主要有客户端和服务端,客户端发送请求,服务端回应请求,客户端实现的功能主要见下图:
注册、登录使用消息队列进行通信的,聊天是通过socket(UDP)实现的!数据存在数据库中,需要一张数据表,建表数据语句如下:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL DEFAULT '', `password` varchar(64) NOT NULL DEFAULT '', `check` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8;
二、客户端实现
client.c创建不同的消息队列的键,根据不同的消息类型的进行发送,并等待服务端响应,client.c代码如下:
#include "my.h"Msg m;
Msg_stoc msg_stoc;static int msgid_ctos;static int msgid_stoc;void showmenu()
{
puts("-------CHAT----------");
puts("| 1:发送 2:接收 |");
puts("| 3:退出 |");
puts("--------------------");
}void show()
{
puts("-------CHAT----------");
puts("| 1:注册 2:登录 |");
puts("| 0:退出 |");
puts("--------------------");
}void send1()
{
printf("%s","send"); char buf[16] = {'\0'}; char str[200] = {'\0'}; struct sockaddr_in dui,zj; int n; short x; int sockfd = socket(AF_INET,SOCK_DGRAM,0); if(sockfd < 0)
{
perror("socket");
exit(-1);
}
zj.sin_family = AF_INET;
zj.sin_port = htons(5555);
zj.sin_addr.s_addr = htonl(INADDR_ANY);
n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj)); if(n < 0)
{
close(sockfd);
perror("bind");
exit(-1);
} //puts("请输入对方号码 端口 IP "); //scanf("%hd%s",&x,buf); getchar();
dui.sin_addr.s_addr = inet_addr("10.10.3.129");
dui.sin_port = htons(8888);
dui.sin_family = AF_INET;
puts("请输入想要发送的内容:"); //gets(str);
fgets(str,200,stdin);
n = sendto(sockfd,str,sizeof(str),0,(struct sockaddr *)&dui,sizeof(dui)); if(n <= 0)
{
close(sockfd);
perror("sendto");
exit(-1);
}
close(sockfd); return;
}#if 1
void asend(int sockfd,struct sockaddr_in dui)
{ char buf[200] = {'\0'}; int n;
puts("请输入要回复的内容:");
fgets(buf,200,stdin); //gets(buf);
n = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&dui,sizeof(dui)); if(n <= 0)
{
perror("sendto");
close(sockfd);
exit(-1);
}
close(sockfd); return ;
}void choose1(char ch ,int sockfd,struct sockaddr_in dui)
{ switch(ch)
{ case 'a':
asend(sockfd,dui); break; case 'n': break; default:
puts("input error!"); break;
}
}#endifvoid recv1()
{ struct sockaddr_in dui,zj;
socklen_t len = sizeof(dui); int n; char buf[200] = {'\0'}; char ch; int sockfd = socket(AF_INET,SOCK_DGRAM,0); if(sockfd < 0)
{
perror("socket");
exit(-1);
}
zj.sin_family = AF_INET;
zj.sin_port = htons(5555);
zj.sin_addr.s_addr = htonl(INADDR_ANY);
n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj)); if(n < 0)
{
close(sockfd);
perror("bind");
exit(-1);
}
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&dui,&len); if(n <= 0)
{
close(sockfd);
perror("recvfrom");
exit(-1);
}
puts(buf);
puts("是否要回复: 回复-》a,不回复-》n");
ch = getchar();
getchar();
choose1(ch,sockfd,dui);
}void choose(int ch)
{
printf("%d",ch); switch(ch)
{ case 1:
m.type = 5;
msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),0);
send1(); break; case 2:
recv1(); break; case 3:
exit(0); break; default:
puts("input error!"); break;
}
}void regis()
{
printf("请输入姓名:");
scanf("%s",m.name);
printf("请输入密码:");
scanf("%s",m.passwd);
m.type=1;
}void login(void)
{
printf("请输入用户名:");
scanf("%s",&m.name);
printf("请输入密码:");
scanf("%s",&m.passwd);
m.type=3;
}int main(int argc ,char *argv[])
{
msgid_ctos=get_ctos_msg();
msgid_stoc=get_stoc_msg();
int temptype;
while(1)
{
show(); int a = 0;
scanf("%d",&a);
getchar(); switch(a)
{ case 0:return 0; case 1:regis();temptype=2;break; //注册
case 2:login();temptype=4;break; //登录 }
int ret; long type=0;
printf("name:%s,passwd:%s\n", m.name,m.passwd);
msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),0);
sleep(2);
msgrcv(msgid_stoc,&msg_stoc,sizeof(msg_stoc),temptype,0);
if(0 == strcmp(msg_stoc.check,"yes"))
{
printf("%s",msg_stoc.info); break;
}
}
while(1)
{
showmenu(); int ch;
puts("请输入功能");
scanf("%d",&ch);
getchar();
printf("%d",ch);
choose(ch);
} return ;
}注意,不同的通信,要用创建不同消息队列的键,并且消息类型也要不同!
三、服务端实现
服务端主要接送并响应客户端,主要创建不同的子进程,然后调用exec族函数,调用二进制文件,并通过消息队列接收阻塞执行,并建立信号,检测Ctrl+c信号,是进程退出,代码如下:
#include"my.h"static pid_t sub_pid[9];static int msgid_ctos;static int msgid_stoc;void sigint(int signum)
{ int i; for(i=0;i<3;i++)
{
kill(sub_pid[i],SIGKILL);
}
}int main(int argc,char*argv[])
{
signal(SIGINT,sigint);
msgid_ctos=get_ctos_msg();
msgid_stoc=get_stoc_msg();
sub_pid[0]=vfork(); if(0==sub_pid[0])
{
execl("register","register",NULL);
}
sub_pid[1]=vfork(); if(0==sub_pid[1])
{
execl("login","login",NULL);
}
sub_pid[2]=vfork(); if(0==sub_pid[2])
{
execl("chat","chat",NULL);
}
wait(NULL); return 0;
}四、各模块及数据库解析
数据库是通过数据库函数实现的,需要头文件<mysql.h>,并链上动态库-I/usr/include/mysql -L/usr/lib64/mysql -lmysqlclient,数据量比较小,之后还要考虑优化的问题;
注册、登录、聊天都是不同的.c文件生成二进制实现的:
注册通过消息队列接收用户名和密码存入数据库,代码如下:
#include"my.h"Msg per;
Msg_stoc msg_stoc;static int msgid_ctos;static int msgid_stoc;void open_cli()
{
MYSQL conn; int res; //MYSQL_RES * result; //MYSQL_ROW row;
mysql_init(&conn); //第三、四和五个参数,需要自己修改一下
if (mysql_real_connect(&conn, "localhost", "root", "p@s#0fSPV", "pvault", 0, NULL, 0)) {
printf("coneect mysql successful\n"); char insert_query[80];
//insert
memset(insert_query, 0, sizeof(insert_query));
strcat(insert_query, "insert into user(name,password) values('");
strcat(insert_query, per.name);
strcat(insert_query, "','");
strcat(insert_query, per.passwd);
strcat(insert_query, "')");
printf("SQL语句: %s\n", insert_query);
res = mysql_query(&conn, insert_query); if (!res) {
printf("insert %lu rows\n", (unsigned long)mysql_affected_rows(&conn));
sprintf(msg_stoc.check,"%s","no");
} else {
printf("insert error\n");
}
}
}int main()
{
msgid_ctos = get_ctos_msg();
msgid_stoc = get_stoc_msg(); //int sockfd = socket_rcv();
while(1)
{ int n; long type; //per.type = 1;
msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),1,0);
printf("name:%s,passwd:%s\n", per.name,per.passwd);
sleep(1);
open_cli(); //Msg m;
msg_stoc.type = 2;
sprintf(msg_stoc.check,"%s","no");
printf("check:%s", msg_stoc.check);
msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),0);
}
}登录也是通过消息队列接收用户名和密码,并从查询出数据,进行对比,是否可以登录,代码如下:
#include"my.h"Msg per;
Msg_stoc msg_stoc;static int msgid_ctos;static int msgid_stoc;void login(void)
{
MYSQL conn; int res;
MYSQL_RES * result;
MYSQL_ROW row;
mysql_init(&conn); //第三、四和五个参数,需要自己修改一下
if (mysql_real_connect(&conn, "localhost", "root", "p@s#0fSPV", "pvault", 0, NULL, 0)) {
printf("coneect mysql successful\n"); char select_query[64] = {0};
snprintf(select_query,64,"select name,password from user where name='%s'",per.name);
printf("SQL语句: %s\n", select_query); if (mysql_query(&conn, select_query) != 0) {
fprintf(stderr, "查询失败\n");
exit(1);
} else { if ((result = mysql_store_result(&conn)) == NULL) {
fprintf(stderr, "保存结果集失败\n");
exit(1);
} else { while ((row = mysql_fetch_row(result)) != NULL) {
printf("name is %s , ", row[0]);
printf("age is %s\n", row[1]); if((0 == strcmp(row[0],per.name)) && (0 == strcmp(row[1],per.passwd)))
strcpy(per.info,"login success!");
}
}
}
}
}int main()
{
msgid_ctos = get_ctos_msg();
msgid_stoc = get_stoc_msg(); //int sockfd = socket_rcv();
while(1)
{ int n; long type; //per.type = 3;
msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),3,0); //printf("name:%s,passwd:%s\n", per.name,per.passwd);
sleep(1);
login(); //Msg m;
msg_stoc.type = 4;
sprintf(msg_stoc.check,"%s","yes");
printf("check:%s", per.info);
msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),0);
}
}聊天是通过socket(UDP)实现的。
总结:通过做这个小项目学到了很多,也发现许多不足,最重要的就是架构能力,之前都是做一小块,没有大局观,虽然项目小,但五张俱全,很锻炼人,继续找项目做!
作者:柳德维
出处:https://www.cnblogs.com/liudw-0215/p/9600532.html