1、服务器端要有一个公共的共享的管道,用来实现用户到服务器之间的通信,并创建一个链表,用于存储用户登录信息。
2、服务器端要跟每一个用户建立一个管道用于服务器与用户之间的通信,并且进行用户数据转发,当用户退出之后,这条管道自动删除。
3、数据包的格式,首先是协议号,源id,目标id,数据内容
服务端思路
1.先创建一个公共管道,接收客户端的消息
2.根据用户登陆后发出的第一个包来创建以用户名命名的管道文件
3.用一个链表来存储用户的登录列表,随时增添
4.根据用户发送数据包的id号来判定将要执行什么操作,1是登录,2是聊天,4.是退出登录
5.转发数据包,是直接发以目标用户名命名的管道文件,但首先要确认登录列表里面有没有目标用户。
6.随着用户发出退出登录的包,把用户的登陆信息,管道文件删掉
客户端思路
1.登陆的时候输入用户名
2.随着用户登录打开公共信道,向服务器发送登录消息,
3.一边接受服务器发来的消息,一遍等待用户输入信息
4.把用户的信息自动封装成数据包的格式发给服务器
服务器源代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "list.h"
int main(void)
{
int fdserver;
int len;
char buff[100] = {0};
struct list *im = creat_list();//用于接收用户的信息
im->next = NULL;
struct list *login = creat_list(); //用于存储登录列表
login->next = NULL;
if((fdserver = open("server", O_RDONLY|O_NONBLOCK)) < 0)
sys_err("open fdserver"); //以不阻塞方式打开服务器共享管道
while(1) {
if((len = read(fdserver, im, sizeof(struct list))) < 0) {//读取用户发给服务器的数据
if(errno != EAGAIN)
sys_err("read fdserver");
}
else
resolve(im->id, im, login); //解析这个数据包,根据协议号工作
sleep(1);
}
close(fdserver);
return 0;
}
客户端源代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include "list.h"
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fdserver;
struct list *im = creat_list();
im->next = NULL;
char buff[100] = {0};
if(argc < 2) {
printf("./cil username\n");
exit(1);
}
if(strlen(argv[1]) > 4) {
printf("username is to long, 4 Bytes\n");
exit(1);
}
//打开管道文件
if((fdserver = open("server", O_WRONLY)) < 0)
sys_err("open fdserver");
change_list(im, 1, argv[1], "0", "0");//填写第一次发给系统的信息:
if((write(fdserver, im, sizeof(struct list))) < 0)
sys_err("write login information");
//把标准输入改为非阻塞属性
be_noblock(STDIN_FILENO);
//把用户自己的管道文件改为非阻塞属性
//be_noblock(fdclient);
while(1) {
//给服务器发信息
if(comm_client(im, fdserver))
printf("Invaild input\n");
sleep(1);
//接收服务器的信息
if(im->id == 4) { //如果是退出包,用户退出,关闭公共管道的文件描述符
close(fdserver);
printf("see you\n");
return 0;
}
read_client(argv[1]);
}
return 0;
}
函数库
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct list { //节点
int id;
char src[5];
char dest[5];
char data[100];
struct list *next;
};
struct list * creat_list() //创建链表
{
return calloc(sizeof(struct list), 1);
}
int add_list(struct list *ls, int id, char *src, char *data) //添加链表元素
{
struct list *p = ls;
while(p->next) {
p = p->next;
}
struct list *tmp = creat_list();
tmp->next = p->next;
p->next = tmp;
tmp->id = id;
strcpy(tmp->src, src);
strcpy(tmp->data, data);
return 0;
}
int change_list(struct list *ls, int id, char *src, char *dest, char *data) //修改链表元素内容
{
struct list *tmp = ls;
tmp->id = id;
strcpy(tmp->src, src);
strcpy(tmp->dest, dest);
strcpy(tmp->data, data);
return 0;
}
void show_list(struct list *ls) //遍历链表,打印出来
{
struct list *p = ls;
while(p) {
printf("id = %d, src = %s, dest = %s, data = %s\n", p->id, p->src, p->dest, p->data);
p = p->next;
}
}
int del_list(struct list *login, char *src) //删除链表的元素
{
struct list *ls = login;
while(strcmp(ls->next->src, src))
ls = ls->next;
struct list *tmp = ls->next;
if(ls->next->next == NULL)
ls->next = NULL;
else {
ls->next = ls->next->next;
}
free(tmp);
return 0;
}
int find_list(struct list *ls, char *name) //查找链表中是否有寻呼的用户
{
show_list(ls);
struct list *login = ls;
while(login) {
if((strcmp(login->src, name)) == 0)
return 0; //存在,返回0
login=login->next;
}
return -1; //不存在返回-1
}
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int resolve(int id, struct list *im, struct list * login) //解析服务器接收到的数据
{
int fdclient;
switch (id) //根据数据包的id处理
{
case 1: //登录信息
add_list(login,im->id, im->src, im->data); //在链表中添加一个元素
if((mkfifo(im->src, 0644)) < 0) //创建服务器与用户之间通信的管道
sys_err("mkfifo");
break;
case 2: //聊天包
if(find_list(login, im->dest) <0) { //聊天包首先是查找用户是否在线
printf("The user is not login\n");
if((fdclient = open(im->src, O_WRONLY)) < 0) //如果存在,就打开对方的管道文件
sys_err("open userfifo");
write(fdclient, "Don't have this user\n", 21);
close(fdclient);
return -1; //如果不在线就返回-1
}
char buff[105] = {0};
if((fdclient = open(im->dest, O_WRONLY)) < 0) //如果存在,就打开对方的管道文件
sys_err("open userfifo");
sprintf(buff, "%s:%s\n", im->src, im->data); //把要发给对方的数据写入buff内
if((write(fdclient, buff, strlen(buff))) < 0) //把buff写入对方的管道文件中,等待对方读取
sys_err("write userfifo");
close(fdclient);
break;
case 4: //退出
unlink(im->src); //退出,把用户的管道文件删除
del_list(login, im->src); //删除链表元素
im->id = 0; //修改协议号,保证下次在没有用户输入的情况下也不会重复删除,导致段错误
break;
}
return 0;
}
int comm_client(struct list *im, int fdserver) //用户输入数据并把它封装到结构体中去
{
char buff[100] = {0};
char dest_tmp[100];
char *dest_name;
int flags;
int len;
if((len = read(STDIN_FILENO, buff, sizeof(buff))) < 0) { //读用户输入的数据
if(errno != EAGAIN)
sys_err("read user input");
}
else { //如果len不小于0,说明有数据,如果有数据就把数据封装到结构体中去
if((strcmp(buff, "quit\n")) == 0||(strcmp(buff,"exit\n") == 0)) { //如果用户输入quit或exit就向服务器发送一个id为4的包,是退出登录信息
im->id = 4; //把id改为4
write(fdserver, im, sizeof(struct list)); //给服务器发送信息
return 0;
}
len = strlen(buff);
if((len < 4) && (len > 0))
return -1;
if(strlen((dest_name = strtok(buff, ":"))) > 4) //判断用户的用户名不超过四个字节
return -1;
//当用户输入的内容不是上述这几种,那就按照正常的用户数据包发出
im->id = 2; //然后把id置为2,说明是正常的数据
strcpy(im->dest, dest_name); //把对方的用户名放到dest这个结构体元素中去
dest_name = strtok(NULL, ":"); //冒号后面是内容,放到data这个元素里面去
strcpy(im->data, dest_name);
printf("im->src = %s, im->dest = %s, im->data = %s\n", im->src, im->dest, im->data);
write(fdserver, im, sizeof(struct list)); //给服务器发送信息
}
return 0;
}
void read_client(char *client_fifo) //读取自己管道文件中服务器发给自己的文件
{
int len;
char buff[100] = {0};
struct list *tmp = creat_list();
int fdclient;
if((fdclient = open(client_fifo,O_RDONLY|O_NONBLOCK)) < 0) //打开以自己名字命名的管道文件
sys_err("open userfifo");
if((len = read(fdclient, buff, sizeof(buff))) < 0) { //读管道中的
if(errno != EAGAIN)
sys_err("read data");
}
else
write(STDIN_FILENO, buff, strlen(buff)); //打印到屏幕上
return;
}
void be_noblock(int fd) //把文件设置为非阻塞
{
int flags;
if((flags = fcntl(fd, F_GETFL)) < 0)
sys_err("fcntl");
flags |= O_NONBLOCK;
if((fcntl(fd, F_SETFL, flags)) < 0)
sys_err("fcntl");
}
头文件
#ifndef __LIST_H_
#define __LIST_H_
struct list { //链表节点
int id;
char src[5];
char dest[5];
char data[100];
struct list *next;
};
struct list * creat_list(); //创建节点
//添加节点
int add_list(struct list *ls, int id, char *src, char *data);
//修改节点
int change_list(struct list *ls, int id, char *src, char *dest, char *data);
//遍历链表,打印出来
void show_list(struct list *ls);
//分析id号来辨别用户的操作
int resolve(int id, struct list *ls, struct list *login);
//接收用户输入,判别用户输入,然后把用户输入转换到im结构体里面
int comm_client(struct list *im, int fdserver);
//查找登录的用户中是否有这个用户存在
int find_list(struct list *ls, char *name);
//从用户自己的fifo文件中读取服务器发过来的数据
void read_client(char *);
void be_noblock(int fd); //把文件设置为非阻塞
#endif
编译过程中遇到的问题
在修改文件阻塞属性的时候,该文件为空时也会返回EAGAIN这个数并且这个数小于0,所以要进行比较,如果返回值是EAGAIN这个数,那就不算出错,继续向下执行,如果不是EAGAIN,而且小于0执行出错行为。
if((len = read(STDIN_FILENO, buff, sizeof(buff))) < 0) { //读用户输入的数据
if(errno != EAGAIN)
sys_err("read user input");
}
每个执行出错会设置errno出错码的函数一定会在下面加上perror,会有效的知道出错的地方,方便排错