前言:

  最近在学进程间通信,所以做了一个小项目练习一下。主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作数据库不熟悉的可以参照我的这篇博客:https://www.cnblogs.com/liudw-0215/p/9593414.html,所有代码提交我的Github上,地址:https://github.com/ldw0215/Chat-System.git,可以自行下载,然后make一下就可以了。

   一、项目要求

  1. 要求实现用户注册、用户登录功能,密码需加密显示
  2. 要求实现聊天功能,双方能互发消息
  3. 数据要求数据库存储

  二、架构解析

  主要流程图如下:

    

  主要有客户端(用户)和服务端,客户端发送注册、登录请求,服务端回应请求,并且双方可以模拟聊天(互相发送消息),主要客户端请求界面见下图:

  

  注册、登录使用消息队列进行通信的,聊天是通过socket(UDP)实现的!数据存在数据库中,需要一张数据表,建表数据语句如下:

  

  1. CREATE TABLE `user` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(64) NOT NULL DEFAULT '',
  4. `password` varchar(64) NOT NULL DEFAULT '',
  5. `check` varchar(64) NOT NULL DEFAULT '',
  6. PRIMARY KEY (`id`),
  7. UNIQUE KEY `name` (`name`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8;

  三、客户端实现

  client.c创建不同的消息队列的键,根据不同的消息类型的进行发送,并等待服务端响应,client.c代码如下:

  

  1. #include "my.h"
  2.  
  3. Msg m;
  4. Msg_stoc msg_stoc;
  5.  
  6. static int msgid_ctos;
  7. static int msgid_stoc;
  8.  
  9. void showmenu()
  10. {
  11. puts("-------CHAT----------");
  12. puts("| 1:发送 2:接收 |");
  13. puts("| 3:退出 |");
  14. puts("--------------------");
  15. }
  16.  
  17. void show()
  18. {
  19. puts("-------CHAT----------");
  20. puts("| 1:注册 2:登录 |");
  21. puts("| 0:退出 |");
  22. puts("--------------------");
  23. }
  24.  
  25. void send1()
  26. {
  27. printf("%s","send");
  28. char buf[] = {'\0'};
  29. char str[] = {'\0'};
  30. struct sockaddr_in dui,zj;
  31. int n;
  32. short x;
  33. int sockfd = socket(AF_INET,SOCK_DGRAM,);
  34. if(sockfd < )
  35. {
  36. perror("socket");
  37. exit(-);
  38.  
  39. }
  40. zj.sin_family = AF_INET;
  41. zj.sin_port = htons();
  42. zj.sin_addr.s_addr = htonl(INADDR_ANY);
  43. n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj));
  44. if(n < )
  45. {
  46. close(sockfd);
  47. perror("bind");
  48. exit(-);
  49.  
  50. }
  51. //puts("请输入对方号码 端口 IP ");
  52. //scanf("%hd%s",&x,buf);
  53. getchar();
  54. dui.sin_addr.s_addr = inet_addr("10.10.3.129");
  55. dui.sin_port = htons();
  56. dui.sin_family = AF_INET;
  57. puts("请输入想要发送的内容:");
  58. //gets(str);
  59. fgets(str,,stdin);
  60.  
  61. n = sendto(sockfd,str,sizeof(str),,(struct sockaddr *)&dui,sizeof(dui));
  62. if(n <= )
  63. {
  64. close(sockfd);
  65. perror("sendto");
  66. exit(-);
  67.  
  68. }
  69. close(sockfd);
  70. return;
  71.  
  72. }
  73. #if 1
  74. void asend(int sockfd,struct sockaddr_in dui)
  75. {
  76. char buf[] = {'\0'};
  77. int n;
  78. puts("请输入要回复的内容:");
  79. fgets(buf,,stdin);
  80. //gets(buf);
  81. n = sendto(sockfd,buf,sizeof(buf),,(struct sockaddr*)&dui,sizeof(dui));
  82. if(n <= )
  83. {
  84. perror("sendto");
  85. close(sockfd);
  86. exit(-);
  87.  
  88. }
  89. close(sockfd);
  90. return ;
  91. }
  92.  
  93. void choose1(char ch ,int sockfd,struct sockaddr_in dui)
  94. {
  95. switch(ch)
  96. {
  97. case 'a':
  98. asend(sockfd,dui);
  99. break;
  100. case 'n':
  101. break;
  102. default:
  103. puts("input error!");
  104. break;
  105.  
  106. }
  107. }
  108. #endif
  109. void recv1()
  110. {
  111. struct sockaddr_in dui,zj;
  112. socklen_t len = sizeof(dui);
  113. int n;
  114. char buf[] = {'\0'};
  115. char ch;
  116. int sockfd = socket(AF_INET,SOCK_DGRAM,);
  117. if(sockfd < )
  118. {
  119. perror("socket");
  120. exit(-);
  121.  
  122. }
  123. zj.sin_family = AF_INET;
  124. zj.sin_port = htons();
  125. zj.sin_addr.s_addr = htonl(INADDR_ANY);
  126. n = bind(sockfd,(struct sockaddr *)&zj,sizeof(zj));
  127. if(n < )
  128. {
  129. close(sockfd);
  130. perror("bind");
  131. exit(-);
  132.  
  133. }
  134. n = recvfrom(sockfd,buf,sizeof(buf),,(struct sockaddr *)&dui,&len);
  135. if(n <= )
  136. {
  137. close(sockfd);
  138. perror("recvfrom");
  139. exit(-);
  140. }
  141. puts(buf);
  142. puts("是否要回复: 回复-》a,不回复-》n");
  143. ch = getchar();
  144. getchar();
  145. choose1(ch,sockfd,dui);
  146. }
  147.  
  148. void choose(int ch)
  149. {
  150. printf("%d",ch);
  151. switch(ch)
  152. {
  153. case :
  154. m.type = ;
  155. msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),);
  156. send1();
  157. break;
  158. case :
  159. recv1();
  160. break;
  161. case :
  162. exit();
  163. break;
  164. default:
  165. puts("input error!");
  166. break;
  167.  
  168. }
  169. }
  170.  
  171. void regis()
  172. {
  173. printf("请输入姓名:");
  174. scanf("%s",m.name);
  175.  
  176. printf("请输入密码:");
  177. scanf("%s",m.passwd);
  178. m.type=;
  179. }
  180.  
  181. void login(void)
  182. {
  183. printf("请输入用户名:");
  184. scanf("%s",&m.name);
  185.  
  186. printf("请输入密码:");
  187. scanf("%s",&m.passwd);
  188. m.type=;
  189. }
  190.  
  191. int main(int argc ,char *argv[])
  192. {
  193. msgid_ctos=get_ctos_msg();
  194. msgid_stoc=get_stoc_msg();
  195.  
  196. int temptype;
  197.  
  198. while()
  199. {
  200. show();
  201. int a = ;
  202. scanf("%d",&a);
  203. getchar();
  204. switch(a)
  205. {
  206. case :return ;
  207. case :regis();temptype=;break; //注册
  208. case :login();temptype=;break; //登录
  209. }
  210.  
  211. int ret;
  212. long type=;
  213.  
  214. printf("name:%s,passwd:%s\n", m.name,m.passwd);
  215. msgsnd(msgid_ctos,&m,sizeof(m)-sizeof(m.type),);
  216.  
  217. sleep();
  218. msgrcv(msgid_stoc,&msg_stoc,sizeof(msg_stoc),temptype,);
  219.  
  220. if( == strcmp(msg_stoc.check,"yes"))
  221. {
  222. printf("%s",msg_stoc.info);
  223. break;
  224. }
  225. }
  226.  
  227. while()
  228. {
  229. showmenu();
  230. int ch;
  231. puts("请输入功能");
  232. scanf("%d",&ch);
  233. getchar();
  234. printf("%d",ch);
  235. choose(ch);
  236. }
  237. return ;
  238. }

  注意,不同的通信,要用创建不同消息队列的键,并且消息类型也要不同!

  四、服务端实现

  服务端主要接送并响应客户端,主要创建不同的子进程,然后调用exec族函数,调用二进制文件,并通过消息队列接收阻塞执行,并建立信号,检测Ctrl+c信号,是进程退出,服务端响应截图如下:

  

  其实现代码如下:

  

  1. #include"my.h"
  2.  
  3. static pid_t sub_pid[];
  4.  
  5. static int msgid_ctos;
  6. static int msgid_stoc;
  7.  
  8. void sigint(int signum)
  9. {
  10. int i;
  11. for(i=;i<;i++)
  12. {
  13. kill(sub_pid[i],SIGKILL);
  14. }
  15.  
  16. }
  17.  
  18. int main(int argc,char*argv[])
  19. {
  20. signal(SIGINT,sigint);
  21.  
  22. msgid_ctos=get_ctos_msg();
  23. msgid_stoc=get_stoc_msg();
  24.  
  25. sub_pid[]=vfork();
  26. if(==sub_pid[])
  27. {
  28. execl("register","register",NULL);
  29. }
  30.  
  31. sub_pid[]=vfork();
  32. if(==sub_pid[])
  33. {
  34. execl("login","login",NULL);
  35. }
  36.  
  37. sub_pid[]=vfork();
  38. if(==sub_pid[])
  39. {
  40. execl("chat","chat",NULL);
  41. }
  42.  
  43. wait(NULL);
  44. return ;
  45. }

  四、各模块及数据库解析

  数据库是通过数据库函数实现的,需要头文件<mysql.h>,并链上动态库-I/usr/include/mysql -L/usr/lib64/mysql -lmysqlclient,数据量比较小,之后还要考虑优化的问题;

  注册、登录、聊天都是不同的.c文件生成二进制实现的:

  注册通过消息队列接收用户名和密码存入数据库,代码如下:

  

  1. #include"my.h"
  2.  
  3. Msg per;
  4. Msg_stoc msg_stoc;
  5.  
  6. static int msgid_ctos;
  7. static int msgid_stoc;
  8.  
  9. void open_cli()
  10. {
  11. MYSQL conn;
  12. int res;
  13. //MYSQL_RES * result;
  14. //MYSQL_ROW row;
  15. mysql_init(&conn);
  16.  
  17. //第三、四和五个参数,需要自己修改一下
  18. if (mysql_real_connect(&conn, "localhost", "root", "p@s#0fSPV", "pvault", , NULL, )) {
  19. printf("coneect mysql successful\n");
  20. char insert_query[];
  21. //insert
  22. memset(insert_query, , sizeof(insert_query));
  23. strcat(insert_query, "insert into user(name,password) values('");
  24. strcat(insert_query, per.name);
  25. strcat(insert_query, "','");
  26. strcat(insert_query, per.passwd);
  27. strcat(insert_query, "')");
  28. printf("SQL语句: %s\n", insert_query);
  29. res = mysql_query(&conn, insert_query);
  30. if (!res) {
  31. printf("insert %lu rows\n", (unsigned long)mysql_affected_rows(&conn));
  32. sprintf(msg_stoc.check,"%s","no");
  33. }
  34. else {
  35. printf("insert error\n");
  36. }
  37.  
  38. }
  39. }
  40.  
  41. int main()
  42. {
  43. msgid_ctos = get_ctos_msg();
  44. msgid_stoc = get_stoc_msg();
  45.  
  46. //int sockfd = socket_rcv();
  47. while()
  48. {
  49. int n;
  50. long type;
  51. //per.type = 1;
  52. msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),,);
  53. printf("name:%s,passwd:%s\n", per.name,per.passwd);
  54. sleep();
  55. open_cli();
  56. //Msg m;
  57. msg_stoc.type = ;
  58. sprintf(msg_stoc.check,"%s","no");
  59. printf("check:%s", msg_stoc.check);
  60. msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),);
  61. }
  62. }

  登录也是通过消息队列接收用户名和密码,并从查询出数据,进行对比,是否可以登录,代码如下:

  

  1. #include"my.h"
  2.  
  3. Msg per;
  4. Msg_stoc msg_stoc;
  5.  
  6. static int msgid_ctos;
  7. static int msgid_stoc;
  8.  
  9. void login(void)
  10. {
  11. MYSQL conn;
  12. int res;
  13. MYSQL_RES * result;
  14. MYSQL_ROW row;
  15. mysql_init(&conn);
  16.  
  17. //第三、四和五个参数,需要自己修改一下
  18. if (mysql_real_connect(&conn, "localhost", "root", "p@s#0fSPV", "pvault", , NULL, )) {
  19. printf("coneect mysql successful\n");
  20. char select_query[] = {};
  21. snprintf(select_query,,"select name,password from user where name='%s'",per.name);
  22. printf("SQL语句: %s\n", select_query);
  23. if (mysql_query(&conn, select_query) != ) {
  24. fprintf(stderr, "查询失败\n");
  25. exit();
  26. }
  27. else {
  28. if ((result = mysql_store_result(&conn)) == NULL) {
  29. fprintf(stderr, "保存结果集失败\n");
  30. exit();
  31. }
  32. else {
  33. while ((row = mysql_fetch_row(result)) != NULL) {
  34. printf("name is %s , ", row[]);
  35. printf("age is %s\n", row[]);
  36. if(( == strcmp(row[],per.name)) && ( == strcmp(row[],per.passwd)))
  37. strcpy(per.info,"login success!");
  38. }
  39. }
  40. }
  41.  
  42. }
  43. }
  44.  
  45. int main()
  46. {
  47. msgid_ctos = get_ctos_msg();
  48. msgid_stoc = get_stoc_msg();
  49.  
  50. //int sockfd = socket_rcv();
  51. while()
  52. {
  53. int n;
  54. long type;
  55. //per.type = 3;
  56. msgrcv(msgid_ctos,&per,sizeof(per)-sizeof(type),,);
  57. //printf("name:%s,passwd:%s\n", per.name,per.passwd);
  58. sleep();
  59. login();
  60. //Msg m;
  61. msg_stoc.type = ;
  62. sprintf(msg_stoc.check,"%s","yes");
  63. printf("check:%s", per.info);
  64. msgsnd(msgid_stoc,&msg_stoc,sizeof(msg_stoc)-sizeof(type),);
  65. }
  66. }

  

  聊天是通过socket(UDP)实现的。

  总结:通过做这种小项目学到了很多,也发现许多不足,最重要的就是架构能力,之前都是做一小块,没有大局观,虽然项目小,但五张俱全,很锻炼人,继续找项目做!

  

消息队列、socket(UDP)实现简易聊天系统的更多相关文章

  1. 用消息队列和socket实现聊天系统

    前言:最近在学进程间通信,所以做了一个小项目练习一下.主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作不熟悉的可以参照我的这篇博客:https://www.cnblo ...

  2. 7月目标 socket , 一致性哈希算法 ; mongodb分片; 分布式消息队列; 中间件的使用场景

      分布式的基础:一致性哈希  路由算法的一致性hash http://www.jiacheo.org/blog/174 http://www.tuicool.com/articles/vQVbmai ...

  3. 【windows 操作系统】进程间通信(IPC)简述|无名管道和命名管道 消息队列、信号量、共享存储、Socket、Streams等

    一.进程间通信简述 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进 ...

  4. .NetCore利用BlockingCollection实现简易消息队列

    前言 消息队列现今的应用场景越来越大,常用的有RabbmitMQ和KafKa. 我们用BlockingCollection来实现简单的消息队列. 实现消息队列 用Vs2017创建一个控制台应用程序.创 ...

  5. 【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)

    在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通 ...

  6. 8.7 进程间的通讯:管道、消息队列、共享内存、信号量、信号、Socket

    进程间的通讯 进程间为什么需要通讯? 共享数据.数据传输.消息通知.进程控制 进程间的通讯有哪些类型? 首先,联系前面讲过的知识,进程之间的用户地址空间是相互独立的,不能进行互相访问,但是,内核空间却 ...

  7. 持久化消息队列memcacheq的安装配置

    MemcacheQ 是一个基于 MemcacheDB 的消息队列服务器. 一.memcacheq介绍 特性: 1.简单易用 2.处理速度快 3.多条队列 4.并发性能好 5.与memcache的协议兼 ...

  8. 深入浅出 消息队列 ActiveMQ(转)

    一. 概述与介绍 ActiveMQ 是Apache出品,最流行的.功能强大的即时通讯和集成模式的开源服务器.ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provide ...

  9. 007. 服务间通信 RPC & REST over HTTP(s) & 消息队列

    服务间通信 服务间通信的几种方式: RPC.REST over HTTP(s).消息队列.  https://www.jianshu.com/p/2a01d4383d0b RPC https://bl ...

随机推荐

  1. Windows linux子系统 使用说明

    1.安装 linux 子系统 2.应用商店安装ubuntu 3.为了方便可以配置成默认登陆root账户 Ubuntu config –default-user root   4. 安装完毕   5.安 ...

  2. phantomjs api文档

    phantomjs实现了一个无界面的webkit浏览器.虽然没有界面,但dom渲染.js运行.网络访问.canvas/svg绘制等功能都很完备,在页面抓取.页面输出.自动化测试等方面有广泛的应用. 详 ...

  3. 如何把if-else代码重构成高质量代码

    原文:https://blog.csdn.net/qq_35440678/article/details/77939999 本文提纲: 为什么我们写的代码都是if-else? 这样的代码有什么缺点? ...

  4. ios日期显示NaN

    ios中js通过getMonth()获取到的日期显示NaN,而在其他地方如pc.安卓都是ok的,这是为什么呢,原来这里有个ios的兼容问题,需要将日期中的“-”替换为“/” var time = ne ...

  5. Hadoop集群搭建-full完全分布式(三)

    环境:Hadoop-2.8.5 .centos7.jdk1.8 一.步骤 1).4台centos虚拟机 2). 将hadoop配置修改为完全分布式 3). 启动完全分布式集群 4). 在完全分布式集群 ...

  6. 将DataRow拷贝到另一个DataRow

    DataRow dr = dtPadFluid.Rows[gvPadFluid.FocusedRowHandle]; foreach (DataColumn dc in _dr.Table.Colum ...

  7. aarch64的架构:unrecognized command line option '-mfpu=neon'

    不用添加这个'-mfpu=neon'的编译器选项了,因为这个架构下neon是默认启动的. 参考: https://lists.linaro.org/pipermail/linaro-toolchain ...

  8. 神经网络_线性神经网络 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. 设 ...

  9. Dora.Interception,为.NET Core度身打造的AOP框架 [3]:多样化拦截器应用方式

    在<以约定的方式定义拦截器>中,我们通过对拦截器的介绍了Dora.Interception的两种拦截机制,即针对接口的“实例拦截”针对虚方法的“类型拦截”.我们介绍了拦截器的本质以及基于约 ...

  10. [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 ...