聊天室程序中至少要求每个用户的发言能立即呈现给其它用户,为了提高效率,每个用户连接在服务端都对应一个子进程处理该用户连接。所有用户的发言数据记录在一个用户共享内存中,假设A用户发言了那么共享内存中某段数据t对应A的发言数据,用户B对应的子进程是pid_b处理用户B,那么pid_b只要到到共享内存位置t读取A的发言数据并发送给B,则聊天室逻辑就成立了。为了达到该设计需求,服务端主进程监听端口遇见有用户连接请求就fork一个子进程处理该用户连接。子进程收到其对应的用户发言数据了就通过管道告诉主进程我(这个子进程)有话要说,此时主进程将这些话传送给其它子进程要求这些子进程将这些话发送给它们对用的客户。而这些发言数据通过共享内存实现只需要传递些指针即可,且每个子进程只需要向其对应的位置写数据所以不会出现交叉写的竞态。
服务端程序:
#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 65536//最大子进程数
struct client_data//客户数据
{
sockaddr_in address;//客户端地址
int connfd;//客户连接描述符
pid_t pid;//处理该连接的子进程号
int pipefd[2];//管道描述符用户主进程向子进程之间传递数据是socketpair全双工管道,pipefd[0]用于主进程向子进程写入数据,pipefd[1]用于子进程监听主进程是否有数据发送到来
};
static const char* shm_name = "/my_shm";
int sig_pipefd[2];//传递信号值的管道(用于将信号事件与IO事件统一监听)
int epollfd;//事件表描述符
int listenfd;//服务端监听端口
int shmfd;//共享内存标示符
char* share_mem = 0;//共享内存地址
client_data* users = 0;//客户数据数组
int* sub_process = 0;//每个子进程pid对应处理的那个客户编号,采用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 );//通过管道sig_pipefd向主进程发送信号值
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;
}
int run_child( int idx, client_data* users, char* share_mem