知识点
作用:
创建一个有名管道,解决无血缘关系的进程间通信。
shell脚本创建一个fifo示例
C函数
#include <sys/tpyes.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
当只写打开fifo管道时,如果fifo读端没有打开,则写打开会阻塞。
fifo内核实现时可以支持双向通信(pipe单向通信,因为父子进程共享同一个file结构体)。
fifo可以一个读端,多个写端,也可以多个读端,一个写端。
通信示意图
程序分析
程序功能示意图
server进程:
mkfifo函数创建一条公共fifo,用open函数以O_RDONLY | O_NONBLOCK模式打开。循环用read函数读取客户端写给服务端的数据并进行处理。
用fcntl函数设置STDIN_FILENO(接收键盘输入)为非阻塞(O_NONBLOCK),因为需要接收输入exit命令进行关闭服务器等等。循环用read函数接收键盘的输入。
当客户端进行登录连接server时,用mkfifo函数为客户端创建一条私有fifo,用write函数写数据给客户端告诉客户端连接成功,每次通信完,需要关闭私有fifo的文件描述符。
client进程:
open函数以O_WRONLY模式打开公共fifo,open函数以O_RDONLY | O_NONBLOCK模式打开私有fifo,循环用read函数读取服务端写给客户端的数据并进行处理。
用fcntl函数设置STDIN_FILENO(接收键盘输入)为非阻塞(O_NONBLOCK),因为需要从私有fifo读服务端写给客户端的数据,同时客户端也接收键盘的输入,进行聊天,同时也需要将屏幕输出等等。循环用read函数接收键盘的输入。
server_fifo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define SERVER_FIFO_FILE "server_fifo_file"
#define BUF_SIZE 1024
#define CHAT_HEAD "send "
#define CHAT_HEAD_LEN strlen(CHAT_HEAD)
#define MSG_BEGIN 0
#define MSG_C2S_LOGIN 1
#define MSG_S2C_CONNECT 2
#define MSG_C2S_CHAT 3
#define MSG_S2C_CHAT 4
#define MSG_C2S_OFFLINE 5
#define MSG_S2C_OFFLINE 6
#define MSG_END 7
enum USER_STATUS
{
ONLINE = 1,
OFFLINE = 2
};
struct MSG
{
int id;
char src[32];
char dst[32];
char data[512];
};
//用户数据链表
struct USER_NODE
{
struct USER_NODE *pre;
struct USER_NODE *next;
unsigned short status; //0:不在线 1:在线
char name[32]; //用户名
};
struct USER_LIST
{
int count;
struct USER_NODE *head;
struct USER_NODE *tail;
} g_user_list;
void insert_user(const char *name);
struct USER_NODE* find_user(const char *name);
void print_user_list();
void update_user_status(const char *name, int status);
void sys_err(const char *str, int exitno)
{
perror(str);
exit(exitno);
}
void exit_chat();
void user_offline(struct USER_NODE *puser);
void c2s_login(struct MSG *pmsg);
void c2s_offline(struct MSG *pmsg);
void c2s_chat(struct MSG *pmsg);
int main(int argc, char *argv[])
{
if (access(SERVER_FIFO_FILE, F_OK) < 0) //文件不存在
{
if(mkfifo(SERVER_FIFO_FILE, 0777) < 0) //创建一条公共命名管道
sys_err("mkfifo", 1);
printf("%s mkfifo success\n", SERVER_FIFO_FILE);
}
//设置标准输入为非阻塞
int flags = fcntl(STDIN_FILENO, F_GETFL);
flags |= O_NONBLOCK;
if (fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
sys_err("fcntl", 1);
int fd_r; //用于打开共享管道,读客户端发给服务端的数据
int len = 0;
struct MSG msg;
char buf[BUF_SIZE];
fd_r = open(SERVER_FIFO_FILE, O_RDONLY | O_NONBLOCK); //只读,用于接收客户端数据
if (fd_r < 0)
sys_err("open", 1);
while(1)
{
//printf("loop begin\n");
memset(&msg, 0, sizeof(struct MSG));
memset(buf, 0, BUF_SIZE);
//读共享管道数据
len = read(fd_r, &msg, sizeof(struct MSG));
if (len > 0)
{
switch (msg.id)
{
case MSG_C2S_LOGIN:
{
c2s_login(&msg);
break;
}
case MSG_C2S_CHAT:
{
c2s_chat(&msg);
break;
}
case MSG_C2S_OFFLINE:
{
c2s_offline(&msg);
break;
}
default:
{
printf("not handle message id = %d\n", msg.id);
break;
}
}
}
else if (len == 0) //写端都关闭了
{
//printf("%s: write port close!\n", SERVER_FIFO_FILE);
//sleep(1);
}
else
{
if (errno != EAGAIN)
sys_err("read", 1);
}
//读标准输入数据
len = read(STDIN_FILENO, buf, BUF_SIZE);
if (len > 0)
{
if (strncmp(buf, "exit", 4) == 0)
break;
}
else if (len == 0)
{
}
else
{
if (errno != EAGAIN)
sys_err("read", 1);
}
usleep(100000);
//printf("loop end!\n");
}
exit_chat();
close(fd_r);
unlink(SERVER_FIFO_FILE);
return 0;
}
void c2s_login(struct MSG *pmsg)
{
printf("c2s_login msg: id = %d, src = %s, dst = %s, data = %s\n", pmsg->id, pmsg->src, pmsg->dst, pmsg->data);
if (access(pmsg->src, F_OK) < 0) //文件不存在
{
if (mkfifo(pmsg->src, 0777)) //为客户端创建一条私有命名管道
sys_err("mkfifo", 1);
}
int fd_w = open(pmsg->src, O_WRONLY);
if (fd_w < 0)
sys_err("open", 1);
struct USER_NODE *p = find_user(pmsg->src);
if (p)
update_user_status(pmsg->src, ONLINE);
else
insert_user(pmsg->src);
struct MSG msg;
msg.id = MSG_S2C_CONNECT;
if (write(fd_w, &msg, sizeof(msg)) < 0)
sys_err("write", 1);
close(fd_w);
}
void insert_user(const char *name)
{
struct USER_NODE *p = find_user(name);
if(p)
{
printf("insert fail, %s alread exist\n", name);
return;
}
struct USER_NODE *user = (struct USER_NODE *)malloc(sizeof(struct USER_NODE));
user->status = ONLINE;
strcpy(user->name, name);
if (g_user_list.count == 0)
{
user->pre = NULL;
user->next = NULL;
g_user_list.head = user;
g_user_list.tail = user;
g_user_list.count++;
}
else
{
user->pre = g_user_list.tail;
user->next = NULL;
g_user_list.tail->next = user;
g_user_list.tail = user;
g_user_list.count++;
}
printf("insert user success:%s\n", user->name);
print_user_list();
}
struct USER_NODE* find_user(const char *name)
{
struct USER_NODE *p = g_user_list.head;
while(p)
{
if (strcmp(name, p->name) == 0)
return p;
p = p->next;
}
return NULL;
}
void print_user_list()
{
struct USER_NODE *p = g_user_list.head;
while(p)
{
printf("%s, status = %d\n", p->name, p->status);
p = p->next;
}
}
void update_user_status(const char *name, int status)
{
struct USER_NODE *p = find_user(name);
if (!p)
{
printf("update fail:%s not exist", name);
return;
}
p->status = status;
}
void c2s_offline(struct MSG *pmsg)
{
printf("c2s_offline msg: id = %d, src = %s, dst = %s, data = %s\n", pmsg->id, pmsg->src, pmsg->dst, pmsg->data);
update_user_status(pmsg->src, OFFLINE);
}
void c2s_chat(struct MSG *pmsg)
{
printf("c2s_chat msg: id = %d, src = %s, dst = %s, data = %s\n", pmsg->id, pmsg->src, pmsg->dst, pmsg->data);
struct MSG msg;
memset(&msg, 0, sizeof(struct MSG));
msg.id = MSG_S2C_CHAT;
struct USER_NODE *p = find_user(pmsg->dst);
if (!p) //通知发送方,dst用户不存在
{
strcpy(msg.src, "not-exist");
strcpy(msg.dst, pmsg->dst);
}
else if (p->status == OFFLINE) //通知发送方,dst用户不在线
{
strcpy(msg.src, "user-offline");
strcpy(msg.dst, pmsg->dst);
}
else //将聊天信息转发给接收方
{
pmsg->id = MSG_S2C_CHAT;
}
if (pmsg->id != MSG_S2C_CHAT)
{
if (access(pmsg->src, F_OK) < 0) //文件不存在
{
if (mkfifo(pmsg->src, 0777) < 0)
sys_err("mkfifo", 1);
}
int fd_w = open(pmsg->src, O_WRONLY);
if (fd_w < 0)
sys_err("open", 1);
if (write(fd_w, &msg, sizeof(msg)) < 0)
sys_err("write", 1);
close(fd_w);
}
else //将聊天信息转发给接收方
{
if (access(pmsg->dst, F_OK) < 0) //文件不存在
{
if (mkfifo(pmsg->dst, 0777) < 0)
sys_err("mkfifo", 1);
}
int fd_w = open(pmsg->dst, O_WRONLY);
if (fd_w < 0)
sys_err("open", 1);
pmsg->id = MSG_S2C_CHAT;
if (write(fd_w, pmsg, sizeof(*pmsg)) < 0)
sys_err("write", 1);
close(fd_w);
}
}
void exit_chat()
{
printf("开始退出聊天服务器,开始释放内存......\n");
struct USER_NODE *p = g_user_list.head;
struct USER_NODE *next = NULL;
while(p)
{
if (p->status == ONLINE)
user_offline(p);
unlink(p->name);
next = p->next;
free(p);
p = next;
}
printf("释放内存成功,退出聊天服务器成功......\n");
}
void user_offline(struct USER_NODE *puser)
{
int fd_w = open(puser->name, O_WRONLY);
if (fd_w < 0)
sys_err("open", 1);
struct MSG msg;
msg.id = MSG_S2C_OFFLINE;
if (write(fd_w, &msg, sizeof(msg)) < 0)
sys_err("write", 1);
close(fd_w);
}
client_fifo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define SERVER_FIFO_FILE "server_fifo_file"
#define BUF_SIZE 1024
#define CHAT_HEAD "send "
#define CHAT_HEAD_LEN strlen(CHAT_HEAD)
#define MSG_BEGIN 0
#define MSG_C2S_LOGIN 1
#define MSG_S2C_CONNECT 2
#define MSG_C2S_CHAT 3
#define MSG_S2C_CHAT 4
#define MSG_C2S_OFFLINE 5
#define MSG_S2C_OFFLINE 6
#define MSG_END 7
struct MSG
{
int id;
char src[32];
char dst[32];
char data[512];
};
void sys_err(const char *str, int exitno)
{
perror(str);
exit(exitno);
}
void s2c_connect(struct MSG *pmsg);
void s2c_chat(struct MSG *pmsg);
void s2c_offline(struct MSG *pmsg);
int main(int argc, char * argv[])
{
if (argc < 2)
{
printf("%s player name\n", argv[0]);
exit(1);
}
if (access(SERVER_FIFO_FILE, F_OK) < 0)
{
printf("server offline\n");
exit(1);
}
//设置标准输入为非阻塞
int flags = fcntl(STDIN_FILENO, F_GETFL);
flags |= O_NONBLOCK;
if (fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
sys_err("fcntl", 1);
int fd_w; //用于打开共享管道,写客户端发给服务端的数据
int fd_r; //用于打开私有管道,读服务端发给客户端的数据
int len = 0;
struct MSG msg;
char buf[BUF_SIZE];
fd_w = open(SERVER_FIFO_FILE, O_WRONLY);
if (fd_w < 0)
sys_err("open", 1);
msg.id = MSG_C2S_LOGIN;
strcpy(msg.src, argv[1]);
len = write(fd_w, &msg, sizeof(msg));
if (len < 0)
sys_err("write", 1);
sleep(1); //保证服务端收到以后建立私有管道
fd_r = open(argv[1], O_RDONLY | O_NONBLOCK); //非阻塞读私有管道
if (fd_r < 0)
sys_err("open", 1);
while(1)
{
//printf("loop begin\n");
memset(&msg, 0, sizeof(struct MSG));
memset(buf, 0, BUF_SIZE);
//读私有管道数据
len = read(fd_r, &msg, sizeof(struct MSG));
if (len > 0)
{
switch (msg.id)
{
case MSG_S2C_CONNECT:
{
s2c_connect(&msg);
break;
}
case MSG_S2C_CHAT:
{
s2c_chat(&msg);
break;
}
case MSG_S2C_OFFLINE:
{
s2c_offline(&msg);
break;
}
default:
{
printf("not handle message id = %d\n", msg.id);
break;
}
}
if (msg.id == MSG_S2C_OFFLINE) //服务器将用户主动踢下线
break;
}
else if (len == 0)
{
//printf("%s: write port close!\n", argv[1]);
}
else
{
if (errno != EAGAIN)
sys_err("read", 1);
}
//读标准输入数据
len = read(STDIN_FILENO, buf, BUF_SIZE);
if (len > 0)
{
memset(&msg, 0, sizeof(struct MSG));
if (strncmp(buf, "exit", 4) == 0)
{
msg.id = MSG_C2S_OFFLINE;
strcpy(msg.src, argv[1]);
len = write(fd_w, &msg, sizeof(msg));
if (len < 0)
sys_err("write", 1);
//sleep(1); //保证服务端收到下线消息
break;
}
else
{
char *p = strchr(buf, ':');
int n = strspn(buf, CHAT_HEAD);
if (p && n == CHAT_HEAD_LEN) //是发送聊天内容格式 send zhangsan: Hello
{
msg.id = MSG_C2S_CHAT;
strcpy(msg.src, argv[1]);
strncpy(msg.dst, buf+CHAT_HEAD_LEN, p-buf-CHAT_HEAD_LEN);
strcpy(msg.data, p+1); //去掉开头的:,所以+1
//printf("dst = %s\n", msg.dst);
//printf("data = %s\n", msg.data);
len = write(fd_w, &msg, sizeof(msg));
if (len < 0)
sys_err("write", 1);
}
}
}
else if (len == 0)
{
printf("STDIN_FILENO\n");
}
else
{
if (errno != EAGAIN)
sys_err("read", 1);
}
//printf("loop end\n");
usleep(100000);
}
close(fd_w);
close(fd_r);
return 0;
}
void s2c_connect(struct MSG *pmsg)
{
printf("connect success\n");
}
void s2c_chat(struct MSG *pmsg)
{
//printf("s2c_chat:src = %s, dst = %s, data = %s\n", pmsg->src, pmsg->dst, pmsg->data);
char buf[BUF_SIZE] = {0};
if (strcmp(pmsg->src, "not-exist") == 0)
{
sprintf(buf, "%s 用户不存在\n", pmsg->dst);
}
else if (strcmp(pmsg->src, "user-offline") == 0)
{
sprintf(buf, "%s 用户不在线\n", pmsg->dst);
}
else
{
sprintf(buf, "recv %s: %s", pmsg->src, pmsg->data);
}
write(STDOUT_FILENO, buf, strlen(buf));
}
void s2c_offline(struct MSG *pmsg)
{
}
运行效果截图
启动服务器时
服务端
启动客户端并以lisi用户登录时
客户端
服务端
再启动多一个客户端并以jim用户登录,lisi跟jim聊天
客户端:lisi
服务端:将lisi发给jim的信息转发给jim
客户端:jim