消息队列、socket(UDP)实现简易聊天系统
前言:
最近在学进程间通信,所以做了一个小项目练习一下。主要用消息队列和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[] = {'\0'};
- char str[] = {'\0'};
- struct sockaddr_in dui,zj;
- int n;
- short x;
- int sockfd = socket(AF_INET,SOCK_DGRAM,);
- if(sockfd < )
- {
- perror("socket");
- exit(-);
- }
- zj.sin_family = AF_INET;
- zj.sin_port = htons();
- zj.sin_addr.s_addr = htonl(INADDR_ANY);
- n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj));
- if(n < )
- {
- close(sockfd);
- perror("bind");
- exit(-);
- }
- //puts("请输入对方号码 端口 IP ");
- //scanf("%hd%s",&x,buf);
- getchar();
- dui.sin_addr.s_addr = inet_addr("10.10.3.129");
- dui.sin_port = htons();
- dui.sin_family = AF_INET;
- puts("请输入想要发送的内容:");
- //gets(str);
- fgets(str,,stdin);
- n = sendto(sockfd,str,sizeof(str),,(struct sockaddr *)&dui,sizeof(dui));
- if(n <= )
- {
- close(sockfd);
- perror("sendto");
- exit(-);
- }
- close(sockfd);
- return;
- }
- #if 1
- void asend(int sockfd,struct sockaddr_in dui)
- {
- char buf[] = {'\0'};
- int n;
- puts("请输入要回复的内容:");
- fgets(buf,,stdin);
- //gets(buf);
- n = sendto(sockfd,buf,sizeof(buf),,(struct sockaddr*)&dui,sizeof(dui));
- if(n <= )
- {
- perror("sendto");
- close(sockfd);
- exit(-);
- }
- 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;
- }
- }
- #endif
- void recv1()
- {
- struct sockaddr_in dui,zj;
- socklen_t len = sizeof(dui);
- int n;
- char buf[] = {'\0'};
- char ch;
- int sockfd = socket(AF_INET,SOCK_DGRAM,);
- if(sockfd < )
- {
- perror("socket");
- exit(-);
- }
- zj.sin_family = AF_INET;
- zj.sin_port = htons();
- zj.sin_addr.s_addr = htonl(INADDR_ANY);
- n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj));
- if(n < )
- {
- close(sockfd);
- perror("bind");
- exit(-);
- }
- n = recvfrom(sockfd,buf,sizeof(buf),,(struct sockaddr *)&dui,&len);
- if(n <= )
- {
- close(sockfd);
- perror("recvfrom");
- exit(-);
- }
- puts(buf);
- puts("是否要回复: 回复-》a,不回复-》n");
- ch = getchar();
- getchar();
- choose1(ch,sockfd,dui);
- }
- void choose(int ch)
- {
- printf("%d",ch);
- switch(ch)
- {
- case :
- m.type = ;
- msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),);
- send1();
- break;
- case :
- recv1();
- break;
- case :
- exit();
- break;
- default:
- puts("input error!");
- break;
- }
- }
- void regis()
- {
- printf("请输入姓名:");
- scanf("%s",m.name);
- printf("请输入密码:");
- scanf("%s",m.passwd);
- m.type=;
- }
- void login(void)
- {
- printf("请输入用户名:");
- scanf("%s",&m.name);
- printf("请输入密码:");
- scanf("%s",&m.passwd);
- m.type=;
- }
- int main(int argc ,char *argv[])
- {
- msgid_ctos=get_ctos_msg();
- msgid_stoc=get_stoc_msg();
- int temptype;
- while()
- {
- show();
- int a = ;
- scanf("%d",&a);
- getchar();
- switch(a)
- {
- case :return ;
- case :regis();temptype=;break; //注册
- case :login();temptype=;break; //登录
- }
- int ret;
- long type=;
- printf("name:%s,passwd:%s\n", m.name,m.passwd);
- msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),);
- sleep();
- msgrcv(msgid_stoc,&msg_stoc,sizeof(msg_stoc),temptype,);
- if( == strcmp(msg_stoc.check,"yes"))
- {
- printf("%s",msg_stoc.info);
- break;
- }
- }
- while()
- {
- 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[];
- static int msgid_ctos;
- static int msgid_stoc;
- void sigint(int signum)
- {
- int i;
- for(i=;i<;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[]=vfork();
- if(==sub_pid[])
- {
- execl("register","register",NULL);
- }
- sub_pid[]=vfork();
- if(==sub_pid[])
- {
- execl("login","login",NULL);
- }
- sub_pid[]=vfork();
- if(==sub_pid[])
- {
- execl("chat","chat",NULL);
- }
- wait(NULL);
- return ;
- }
四、各模块及数据库解析
数据库是通过数据库函数实现的,需要头文件<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", , NULL, )) {
- printf("coneect mysql successful\n");
- char insert_query[];
- //insert
- memset(insert_query, , 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()
- {
- int n;
- long type;
- //per.type = 1;
- msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),,);
- printf("name:%s,passwd:%s\n", per.name,per.passwd);
- sleep();
- open_cli();
- //Msg m;
- msg_stoc.type = ;
- sprintf(msg_stoc.check,"%s","no");
- printf("check:%s", msg_stoc.check);
- msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),);
- }
- }
登录也是通过消息队列接收用户名和密码,并从查询出数据,进行对比,是否可以登录,代码如下:
- #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", , NULL, )) {
- printf("coneect mysql successful\n");
- char select_query[] = {};
- snprintf(select_query,,"select name,password from user where name='%s'",per.name);
- printf("SQL语句: %s\n", select_query);
- if (mysql_query(&conn, select_query) != ) {
- fprintf(stderr, "查询失败\n");
- exit();
- }
- else {
- if ((result = mysql_store_result(&conn)) == NULL) {
- fprintf(stderr, "保存结果集失败\n");
- exit();
- }
- else {
- while ((row = mysql_fetch_row(result)) != NULL) {
- printf("name is %s , ", row[]);
- printf("age is %s\n", row[]);
- if(( == strcmp(row[],per.name)) && ( == strcmp(row[],per.passwd)))
- strcpy(per.info,"login success!");
- }
- }
- }
- }
- }
- int main()
- {
- msgid_ctos = get_ctos_msg();
- msgid_stoc = get_stoc_msg();
- //int sockfd = socket_rcv();
- while()
- {
- int n;
- long type;
- //per.type = 3;
- msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),,);
- //printf("name:%s,passwd:%s\n", per.name,per.passwd);
- sleep();
- login();
- //Msg m;
- msg_stoc.type = ;
- sprintf(msg_stoc.check,"%s","yes");
- printf("check:%s", per.info);
- msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),);
- }
- }
聊天是通过socket(UDP)实现的。
总结:通过做这种小项目学到了很多,也发现许多不足,最重要的就是架构能力,之前都是做一小块,没有大局观,虽然项目小,但五张俱全,很锻炼人,继续找项目做!
消息队列、socket(UDP)实现简易聊天系统的更多相关文章
- 用消息队列和socket实现聊天系统
前言:最近在学进程间通信,所以做了一个小项目练习一下.主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作不熟悉的可以参照我的这篇博客:https://www.cnblo ...
- 7月目标 socket , 一致性哈希算法 ; mongodb分片; 分布式消息队列; 中间件的使用场景
分布式的基础:一致性哈希 路由算法的一致性hash http://www.jiacheo.org/blog/174 http://www.tuicool.com/articles/vQVbmai ...
- 【windows 操作系统】进程间通信(IPC)简述|无名管道和命名管道 消息队列、信号量、共享存储、Socket、Streams等
一.进程间通信简述 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进 ...
- .NetCore利用BlockingCollection实现简易消息队列
前言 消息队列现今的应用场景越来越大,常用的有RabbmitMQ和KafKa. 我们用BlockingCollection来实现简单的消息队列. 实现消息队列 用Vs2017创建一个控制台应用程序.创 ...
- 【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)
在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通 ...
- 8.7 进程间的通讯:管道、消息队列、共享内存、信号量、信号、Socket
进程间的通讯 进程间为什么需要通讯? 共享数据.数据传输.消息通知.进程控制 进程间的通讯有哪些类型? 首先,联系前面讲过的知识,进程之间的用户地址空间是相互独立的,不能进行互相访问,但是,内核空间却 ...
- 持久化消息队列memcacheq的安装配置
MemcacheQ 是一个基于 MemcacheDB 的消息队列服务器. 一.memcacheq介绍 特性: 1.简单易用 2.处理速度快 3.多条队列 4.并发性能好 5.与memcache的协议兼 ...
- 深入浅出 消息队列 ActiveMQ(转)
一. 概述与介绍 ActiveMQ 是Apache出品,最流行的.功能强大的即时通讯和集成模式的开源服务器.ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provide ...
- 007. 服务间通信 RPC & REST over HTTP(s) & 消息队列
服务间通信 服务间通信的几种方式: RPC.REST over HTTP(s).消息队列. https://www.jianshu.com/p/2a01d4383d0b RPC https://bl ...
随机推荐
- Windows linux子系统 使用说明
1.安装 linux 子系统 2.应用商店安装ubuntu 3.为了方便可以配置成默认登陆root账户 Ubuntu config –default-user root 4. 安装完毕 5.安 ...
- phantomjs api文档
phantomjs实现了一个无界面的webkit浏览器.虽然没有界面,但dom渲染.js运行.网络访问.canvas/svg绘制等功能都很完备,在页面抓取.页面输出.自动化测试等方面有广泛的应用. 详 ...
- 如何把if-else代码重构成高质量代码
原文:https://blog.csdn.net/qq_35440678/article/details/77939999 本文提纲: 为什么我们写的代码都是if-else? 这样的代码有什么缺点? ...
- ios日期显示NaN
ios中js通过getMonth()获取到的日期显示NaN,而在其他地方如pc.安卓都是ok的,这是为什么呢,原来这里有个ios的兼容问题,需要将日期中的“-”替换为“/” var time = ne ...
- Hadoop集群搭建-full完全分布式(三)
环境:Hadoop-2.8.5 .centos7.jdk1.8 一.步骤 1).4台centos虚拟机 2). 将hadoop配置修改为完全分布式 3). 启动完全分布式集群 4). 在完全分布式集群 ...
- 将DataRow拷贝到另一个DataRow
DataRow dr = dtPadFluid.Rows[gvPadFluid.FocusedRowHandle]; foreach (DataColumn dc in _dr.Table.Colum ...
- aarch64的架构:unrecognized command line option '-mfpu=neon'
不用添加这个'-mfpu=neon'的编译器选项了,因为这个架构下neon是默认启动的. 参考: https://lists.linaro.org/pipermail/linaro-toolchain ...
- 神经网络_线性神经网络 3 (Nerual Network_Linear Nerual Network 3)
1 LMS 学习规则_解方程组 1.1 LMS学习规则举例 X1=[0 0 1]T,t1=0:X2=[1 0 1]T,t2=0:X3=[0 1 1]T,t3=0:X1=[1 1 1]T,t1=1. 设 ...
- Dora.Interception,为.NET Core度身打造的AOP框架 [3]:多样化拦截器应用方式
在<以约定的方式定义拦截器>中,我们通过对拦截器的介绍了Dora.Interception的两种拦截机制,即针对接口的“实例拦截”针对虚方法的“类型拦截”.我们介绍了拦截器的本质以及基于约 ...
- [Swift]LeetCode60. 第k个排列 | Permutation Sequence
The set [1,2,3,...,n] contains a total of n! unique permutations. By listing and labeling all of the ...