用fifo管道实现多人聊天
最近学习了多路IO,多线程,信号注册函数,管道通信等内容。于是通过以上技术,写了一个通过管道通信的多人聊天小程序。程序还存在很多不足,但是勉强能实现需要的功能。先贴上,日后完善。
先介绍程序大致功能,再贴上代码:
1.启动服务器程序,用epoll进行监听客户端事件发生,同时创建一个文件写入服务器端的pid,用于客户端信号发送到服务器.
2.再启动客户端程序,创建读写管道。同时发送信号到服务器,以便让服务器连接管道。
3.连接成功后进行通信
setname xxx 用于创建用户名
to xxx:buf 给指定xxx用户发送buf消息
to all:buf 给所有用户发送buf消息
服务端程序:
#include<stdio.h>
#include<errno.h>
#include<signal.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<map>
#include<iostream>
using namespace std;
typedef struct chat_user_t{//客户信息结构体
string name;
int fd_read;
int fd_write;
}chat_user_t;
map<string,chat_user_t*> users;//保存客户信息
int epollfd;
const char* errmsg[] = {//出错信息
"ok",
"user not exit",
"unknown command"
};
//创建文件存储服务器进程id
void create_pid_file(char* filename){
char buf[1024];
sprintf(buf,"%s.run",filename);
int fd=open(buf,O_CREAT|O_RDWR,0777);
if(fd<0){
perror("create_pid open");
return;
}
sprintf(buf,"%d",(int)getpid());
write(fd,buf,strlen(buf));
close(fd);
}
//设置文件非阻塞
void setNonblock(int fd){
int flags=fcntl(fd,F_GETFL);
flags|=O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
}
//信号处理函数
void signal_handle(int signum,siginfo_t* info,void *p){
char filepath1[64];
char filepath2[64];
sprintf(filepath1,"%d-1",(int)info->si_pid);
sprintf(filepath2,"%d-2",(int)info->si_pid);
int fd_read=open(filepath1,O_RDONLY);//打开管道1读客户端
if(fd_read<0){
return;
}
int fd_write=open(filepath2,O_WRONLY);//打开管道2写客户端
if(fd_write<0){
close(fd_read);
return;
}
setNonblock(fd_read);//设置读管道非阻塞
chat_user_t *user=new chat_user_t;
user->fd_read=fd_read;
user->fd_write=fd_write;
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.ptr=user;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd_read,&ev);//将新加入的客户端放入监听
printf("some is coming\n");
}
//注册信号处理函数
void register_signal(){
struct sigaction act;
act.sa_handler=NULL;
act.sa_sigaction=signal_handle;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_restorer=NULL;
sigaction(SIGRTMIN,&act,NULL);
printf("register_signale\n");
}
//
//setname xxx
//to xxx:
//to all:
void handle_cmd(chat_user_t *user,char *cmd){
if(strncmp(cmd,"setname",7)==0){
strtok(cmd," ");
char *username=strtok(NULL," ");
user->name=username;
users[username]=user;
printf("%s setname\n",username);
}
//to xxx:aaaaa||to all:aaaaa
else if(strncmp(cmd,"to",2)==0){
char* head=strtok(cmd,":");//to xxx
char* content=strtok(NULL,"\0");//aaaaa
strtok(head," ");
char* tousername=strtok(NULL," ");//xxx
//to all:aaaa;
if(strcmp(tousername,"all")==0){//给所有人发消息
char buf[1024];
sprintf(buf,"%s:%s",tousername,content);
map<string,chat_user_t*>::iterator it=users.begin();
for(;it!=users.end();it++){
if(it->first==user->name){
continue;
}
write(it->second->fd_write,buf,strlen(buf)+1);
}
return;
}
map<string,chat_user_t*>::iterator it=users.find(tousername);
if(it==users.end()){//not user
//printf("==========\n");
write(user->fd_write,errmsg[1],strlen(errmsg[1])+1);
}else{//给特定的人发消息
//yyy:buf
char buf[1024];
sprintf(buf,"%s:%s",tousername,content);
write(users[tousername]->fd_write,buf,strlen(buf)+1);
}
}else{//wrong msg
printf("=============cmd:%s\n",cmd);
write(user->fd_write,errmsg[2],strlen(errmsg[2])+1);
}
}
int main(int argc,char *argv[]){
//创建文件,服务器进程id
create_pid_file(argv[0]);
//注册客户端信号
register_signal();
struct epoll_event ev[10];
epollfd=epoll_create(1024);
while(1){
int nready=epoll_wait(epollfd,ev,10,2000);
if(nready>0){//有人发消息
for(int i=0;i<nready;i++){
char buf[1024];
chat_user_t *tempuser=(chat_user_t*)ev[i].data.ptr;
int n=read(tempuser->fd_read,buf,sizeof(buf));
if(n<=0){//读取有异常
if(n==-1&&errno==EINTR){
continue;
}
printf("%s is exit\n",tempuser->name.c_str());
users.erase(tempuser->name);
close(tempuser->fd_read);
close(tempuser->fd_write);
delete(tempuser);
}
else{
//处理格式
//setname xxx
//to yy:hello
//to all:hello all
handle_cmd(tempuser,buf);
}
}
}
else if(nready<0){//监听发生异常
if(errno==EINTR){//被信号打断继续监听
continue;
}
break;//其他异常
}
}
return 0;
}
客户端程序
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
pid_t get_server_pid()
{
int fd = open("myserver.run", O_RDWR);
char buf[1024];
read(fd, buf, sizeof(buf));
close(fd);
return (pid_t)atoi(buf);
}
// 等待服务器发送数据
void* thread_recv(void* ptr)
{
int fd_read = (int)(intptr_t)ptr;
char buf[4096];
while(1)
{
int ret = read(fd_read, buf, sizeof(buf));
if(ret == 0) // 写端已经被关闭了
{
exit(0); // 整个进程退出
}
if(ret < 0)
{
if(errno == EINTR) // 读文件失败
continue;
exit(0); // 读文件错误
}
printf("%s\n", buf); // 要求buf不带
}
}
int main()
{
// 创建两个管道文件
pid_t pid = getpid();
char buf1[4096];
sprintf(buf1, "%d-1", (int)pid);
mkfifo(buf1, 0777);
char buf2[4096];
sprintf(buf2, "%d-2", (int)pid);
mkfifo(buf2, 0777);
// 发送信号给服务器,我来了
pid = get_server_pid();
// printf("%d\n",(int)pid);
// 发送信号告诉服务器,新的客户端加入
union sigval v;
sigqueue(pid, SIGRTMIN, v);
// 打开管道文件,一定在发送信号之后
// 让客户端和服务器一起打开管道,否则会阻塞
int fd_write = open(buf1, O_WRONLY);
int fd_read = open(buf2, O_RDONLY);
// 创建一个线程,负责信息的接收
pthread_t tid;
pthread_create(&tid, NULL, thread_recv, (void*)(intptr_t)fd_read);
// 等待用户输入
while(1)
{
char buf[4096];
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = 0;
if(strlen(buf) == 0) // 空敲回车的处理
continue;
// setname xue
// to yy: hello yy
write(fd_write, buf, strlen(buf)+1); // 带上\0
}
}