服务器代码如下:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>#define USER_LIMIT 5
#define BUFFER_SIZE 1024
#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER 1024
#define PROCESS_LIMIT 65536struct client_data {sockaddr_in address; // 客户端ip地址int connfd;         // socket文件描述符pid_t pid;         //  处理这个连接的子进程pidint pipefd[2];      // 和父进程通信用的管道
};static const char* shm_name = "/my_shm";
int sig_pipefd[2];
int epollfd;
int listenfd;
int shmfd;char* share_mem = 0;client_data* users = 0; // 客户连接数组,进程用客户连接的编号来索引这个数组int* sub_process = 0; // 子进程和客户连接的映射关系表。用进程的pid来索引这个数组,即可取得该进程 所处理的客户连接 的编号int user_count = 0; // 当前客户数量
bool stop_child = false;int setnonblocking(int fd) {int old_option = fcntl(fd, F_GETFL);int new_option = old_option | O_NONBLOCK;fcntl(fd, F_SETFL, new_option);return old_option;
}void addfd(int epollfd, int fd) {epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLET;epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);setnonblocking(fd);
}void sig_handler(int sig) {int save_errno = errno;int msg = sig;send(sig_pipefd[1], (char*)&msg, 1, 0);errno = save_errno;
}void addsig(int sig, void(*handler)(int), bool restart = true) {struct sigaction sa;memset(&sa, '\0', sizeof(sa));sa.sa_handler = handler;if (restart) {sa.sa_flags |= SA_RESTART;}sigfillset(&sa.sa_mask);assert(sigaction(sig, &sa, NULL) != -1);
}void del_resource() {close(sig_pipefd[0]);close(sig_pipefd[1]);close(listenfd);close(epollfd);shm_unlink(shm_name);delete[] users;delete[] sub_process;
}void child_term_handler(int sig) { // 停止一个子进程stop_child = true;
}// 子进程运行的函数。参数idx指出该子进程处理的客户连接的编号,users是保存所有客户连接数据的数组。参数share_mem指出共享内存的起始地址
int run_child(int idx, client_data* users, char* share_mem) {epoll_event events[MAX_EVENT_NUMBER];// 子进程用io复用技术来同时监听两个文件描述符: 客户连接socket和与父进程的管道文件描述符int child_epollfd = epoll_create(5);assert(child_epollfd != -1);int connfd = users[idx].connfd; // 客户端socketaddfd(child_epollfd, connfd);int pipefd = users[idx].pipefd[1]; // 父进程管道文件描述符addfd(child_epollfd, pipefd);int ret;// 子进程设置自己的信号处理函数addsig(SIGTERM, child_term_handler, false);while (!stop_child) {int number = epoll_wait(child_epollfd, events, MAX_EVENT_NUMBER, -1);if ((number < 0) && (errno != EINTR)) {printf("epoll failure\n");break;}for (int i = 0; i < number; i++) {int sockfd = events[i].data.fd;// 本子进程负责的客户连接有数据到达if ((sockfd == connfd) && (events[i].events & EPOLLIN)) {memset(share_mem + idx * BUFFER_SIZE, '\0', BUFFER_SIZE);// 将客户数据读取到对应 读缓存 中。 读缓存是共享内存的一段,开始于 idx*BUFFER_SIZE处,长度为BUFFER_SIZE字节。因此,各个客户连接的// 读缓存是共享的ret = recv(connfd, share_mem + idx * BUFFER_SIZE, BUFFER_SIZE - 1, 0);if (ret < 0) {if (errno != EAGAIN) {stop_child = true;}} else if (ret == 0) {stop_child = true;} else {// 成功读取客户数据后就通知主进程(通过管道)来处理send(pipefd, (char*)&idx, sizeof(idx), 0);}// 主进程通知本进程(通过管道)将第client个客户的数据发送到本进程负责处理的客户端} else if ((sockfd == pipefd) && (events[i].events & EPOLLIN)) {int client = 0;// 接收主进程发送来的数据,即 有客户数据到达的连接 的编号。ret = recv(sockfd, (char*)&client, sizeof(client), 0);if (ret < 0) {if (errno != EAGAIN) {stop_child = true;}} else if (ret == 0) {stop_child = true;} else {send(connfd, share_mem + client * BUFFER_SIZE, BUFFER_SIZE, 0); // 通知这个子进程负责的connfd,有个编号为client的客户端发消息了,把这段消息发给connfd}} else {continue;}}}close(connfd);close(pipefd);close(child_epollfd);return 0;
}int main(int argc, char* argv[])
{if (argc <= 2) {printf("usage: %s ip_address port_number\n", basename(argv[0]));return 1;}const char* ip = argv[1];int port = atoi(argv[2]);int ret = 0;struct sockaddr_in address;bzero(&address, sizeof(address));address.sin_family = AF_INET;inet_pton(AF_INET, ip, &address.sin_addr);address.sin_port = htons(port);listenfd = socket(PF_INET, SOCK_STREAM, 0);assert(listenfd >= 0);ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));assert(ret != -1);ret = listen(listenfd, 5);assert(ret != -1);user_count = 0;users = new client_data[USER_LIMIT + 1];sub_process = new int[PROCESS_LIMIT];for (int i = 0; i < PROCESS_LIMIT; ++i) {sub_process[i] = -1;}epoll_event events[MAX_EVENT_NUMBER];epollfd = epoll_create(5);assert(epollfd != -1);addfd(epollfd, listenfd);ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sig_pipefd);assert(ret != -1);setnonblocking(sig_pipefd[1]);addfd(epollfd, sig_pipefd[0]);addsig(SIGCHLD, sig_handler);addsig(SIGTERM, sig_handler);addsig(SIGINT, sig_handler);addsig(SIGPIPE, SIG_IGN);bool stop_server = false;bool terminate = false;// 创建共享内存,作为所有客户socket连接的读缓存shmfd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);assert(shmfd != -1);ret = ftruncate(shmfd, USER_LIMIT * BUFFER_SIZE);assert(ret != -1);share_mem = (char*)mmap(NULL, USER_LIMIT * BUFFER_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0);assert(share_mem != MAP_FAILED);close(shmfd);while (!stop_server) {int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);if ((number < 0) && (errno != EINTR)) {printf("epoll failure\n");break;}for (int i = 0; i < number; i++) {int sockfd = events[i].data.fd;// 新的客户连接到来if (sockfd == listenfd) {struct sockaddr_in client_address;socklen_t client_addrlength = sizeof(client_address);int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);if (connfd < 0) {printf("errno is: %d\n", errno);continue;}if (user_count >= USER_LIMIT) {const char* info = "too many users\n";printf("%s", info);send(connfd, info, strlen(info), 0);close(connfd);continue;}// 保存第user_counter个客户连接的相关数据users[user_count].address = client_address;users[user_count].connfd = connfd;// 在主进程和子进程间建立管道,以传递必要数据ret = socketpair(PF_UNIX, SOCK_STREAM, 0, users[user_count].pipefd);assert(ret != -1);pid_t pid = fork();if (pid < 0) {close(connfd);continue;} else if (pid == 0) { // 子进程close(epollfd);close(listenfd);close(users[user_count].pipefd[0]);close(sig_pipefd[0]);close(sig_pipefd[1]);run_child(user_count, users, share_mem);munmap((void*)share_mem, USER_LIMIT * BUFFER_SIZE);exit(0);} else { // 父进程close(connfd);close(users[user_count].pipefd[1]);addfd(epollfd, users[user_count].pipefd[0]); // 监听子进程的管道users[user_count].pid = pid;// 记录新的客户连接在数组users中的索引值,建立进程pid和该索引值之间的映射关系sub_process[pid] = user_count;user_count++;}} else if ((sockfd == sig_pipefd[0]) && events[i].events & EPOLLIN) { // 处理信号事件int sig;char signals[1024];ret = recv(sig_pipefd[0], signals, sizeof(signals), 0);if (ret == -1) {continue;} else if (ret == 0) {continue;} else {for (int i = 0; i < ret; ++i) {switch(signals[i]) {case SIGCHLD: { // 子进程推出,表示有某个客户端关闭了连接pid_t pid;int stat;while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {// 用子进程的pid取得被关闭的客户连接的编号int del_user = sub_process[pid];sub_process[pid] = -1;if ((del_user < 0) || (del_user > USER_LIMIT)) {continue;}// 清除第del_user个客户连接使用的相关数据epoll_ctl(epollfd, EPOLL_CTL_DEL, users[del_user].pipefd[0], 0);close(users[del_user].pipefd[0]);users[del_user] = users[--user_count];sub_process[users[del_user].pid] = del_user;}if (terminate && user_count == 0) {stop_server = true;}break;}case SIGTERM:case SIGINT: { // 结束服务器程序printf("kill all the child now\n");if (user_count == 0) {stop_server = true;break;}for (int i = 0; i < user_count; ++i) {int pid = users[i].pid;kill(pid, SIGTERM);}terminate = true;break;}default: {break;}}}}} else if (events[i].events & EPOLLIN) { // 某个子进程向父进程写入了数据int child = 0;// 读取管道数据,child变量记录了是哪个客户连接有数据到达ret = recv(sockfd, (char*)&child, sizeof(child), 0);printf("read data from child accross pipe\n");if (ret == -1) {continue;} else if (ret == 0) {continue;} else {// 向除负责处理第child个客户连接的子进程之外的其他子进程发送消息,通知他们有客户数据要写for (int j = 0; j < user_count; ++j) {if (users[j].pipefd[0] != sockfd) {printf("send data to child accross pipe\n");send(users[j].pipefd[0], (char*)&child, sizeof(child), 0);}}}}}}del_resource();return 0;
}

server:

client 1:

client 2:

先让服务器监听4444端口,之后客户端1连接,客户端2连接。客户端1发送11111,发现客户端1和2都能收到。客户端2发送2222,发现客户端1和2都能收到。

reference: linux高性能服务器编程——游双P255P_{255}P255​

基于共享内存的聊天室服务程序相关推荐

  1. 【毕业设计之PHP系列】基于PHP的网络聊天室系统

    基于PHP的网络聊天室系统 摘要:我们生活在一个通信变得非常重要的世界里,人们需要同他人快速容易的进行交流.E-mail.电话.邮件以及在线聊天是以书写文字的形式让人们进行思想交流的媒体.通信时一个重 ...

  2. 【共享内存】基于共享内存的无锁消息队列设计

    上交所技术服务 2018-09-05 https://mp.weixin.qq.com/s/RqHsX3NIZ4_BS8O30KWYhQ 目录 一.背景 二.消息队列的应用需求 (一)  通信架构的升 ...

  3. 基于TCP的网络聊天室实现(C语言)

    基于TCP的网络聊天室实现(C语言) 一.网络聊天室的功能 二.网络聊天室的结果展示 三.实现思路及流程 四.代码及说明 1.LinkList.h 2.LinkList.c 3.client.c 4. ...

  4. mysql数据库映射到内存_基于共享内存的数据库映射

    基于共享内存的数据库映射 概述 随着各类行业软件对性能追求越来越高,因此对数据库处理的速度提出了新的挑战.然而大部分复杂的业务处理往往依赖体量较大的关系数据(如:Oracle,Mysql,Postgr ...

  5. 基于jquery的ajax聊天室系统,基于jQuery的Ajax聊天室应用毕业设计(含外文翻译)...

    基于jQuery的Ajax聊天室应用毕业设计(含外文翻译) 毕业设计(论文) I 基于基于 jQuery 的的 Ajax 聊天室应用聊天室应用 摘摘 要要 随着网络的逐渐普及,以及网络技术的不断发展, ...

  6. workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)...

    workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...

  7. 基于共享内存、信号、命名管道和Select模型实现聊天窗口

    基于共享内存.信号.命名管道和Select模型实现聊天窗口 问题模型 A.B两个进程通过管道通信,A 进程每次接收到的数据通过共享内存传递给A1进程显示,同理,B进程每次接收到的数据通过共享内存传递给 ...

  8. 基于flask的网页聊天室(四)

    基于flask的网页聊天室(四) 前言 接前天的内容,今天完成了消息的处理 具体内容 上次使用了flask_login做用户登录,但是直接访问login_requare装饰的函数会报401错误,这里可 ...

  9. php聊天室的设计实现,基于PHP的Ajax聊天室系统的设计与实现

    第7卷 第20期 2007年10月167121819(2007)2025396204 科 学 技 术 与 工 程 Science Technol ogy and Engineering Vol .7 ...

最新文章

  1. 生物版AlphaGo发威!DeepMind出手抗疫:预测多种新冠病毒相关蛋白结构
  2. Leetcode剑指 Offer II 024. 反转链表
  3. openshift介绍及centos7安装单节点openshift、Redhat安装openshift集群完全教程
  4. linux中进程优先级,linux下调整进程优先级
  5. js 高级 prototype
  6. Maven学习总结(32)——Maven项目部署到Tomcat8中
  7. 计算机基础在小学的教学论文,计算机基础教育论文计算机教学改革论文.doc
  8. 啊金学习javascript系列一之javascript整体印象
  9. 13 登陆_《星球大战:弹珠台》中文版即将登陆Switch 12月13日正式发售
  10. WCF学习记录【一】
  11. html嵌入word文档,网页中嵌入word文档和导出数据到word文档
  12. an error occurred while contacting the respository
  13. Excel如何快速验证银行卡号和姓名是否一致?
  14. 【腾讯TMQ】我们在外包资源池化管理走过的弯路
  15. 简单的HANGMAN游戏
  16. “酸甜苦辣”说华育(我一个学员的学习心得)
  17. ionic自定义图标
  18. 28 关于 Finalizer
  19. 天翼云linux远程密码不对,天翼云主机远程连接
  20. linux的图形界面的管理

热门文章

  1. Visual Studio 2012 和.NET Framework 4.5 快速开始的5分钟视频
  2. 713C - 如何进入一个研究领域
  3. TypeScript 安装与使用
  4. Hystrix面试 - 深入 Hystrix 执行时内部原理
  5. 如何使用Docker轻松集成OnlyOffice和NextCloud--快速搭建私有云办公系统/私有云盘/私有OfficeOnline
  6. 2018年最佳Linux服务器发行版
  7. 安装Debian-9(Stretch)服务器图文教程
  8. Docker 部署clickhouse-server及添加用户配置密码
  9. 【C语言】用指针作为形参完成数据的升序排列
  10. C#LeetCode刷题之#717-1比特与2比特字符( 1-bit and 2-bit Characters)