最初にそれを分析しましょう:
クライアントは同時に複数のログインを持つことができます. 通信は実際には 2 つのクライアント間の情報交換であり、サーバーは仲介者としてのみ機能します. (QQと通信するときと同じように)
次に、クライアントとサーバーの間でデータがどのように送信されるかを考える必要がありますが、もちろん、ここではネットワーク プログラミングは関係ありません。システムプログラミングを練習するだけです。IPC 通信の方法はたくさんありますが、比較的簡単にインストールできる方法を選びましょう。有名なパイプを使用
通信の前に、データの整理方法やメッセージの整理方法を考えよう【送信者、受信者、データそのもの、プロトコル】
ここでのプロトコルは、ネットワーク内のプロトコルではなく、ログイン、通信、オフライン、終了、およびその他の状態 [クライアントに表示される可能性のある状態] を区別するためのものです。
簡単に送信できるようにパッケージ化します
qq_ipc.h
#ifndef QQ_IPC_H #define QQ_IPC_H /* protocal: 1 登录 2 数据传输 3 不在线 4 退出登录 */ struct DATA_INFO{ int protocal; //协议 char srcname[20]; //发送方 char destname[20]; //接收方 char data[100]; //发送的数据 }; #endif
また、複数のクライアントが同時にオンラインになるという問題も考慮する必要があります。これらのクライアントはすべてサーバーと通信する必要があるため、サーバーはこれらのクライアントをどのように管理するのでしょうか?
クライアントはオンラインと退出の状態になり、比較的頻度の高い操作となるため、リンクリストに従って操作を挿入および削除すると便利です。そのため、リンクリストを使用してオンラインユーザーを管理します。
mylink.h
#ifndef _MYLINK_H_
#define _MYLINK_H_
typedef struct node *mylink;
struct node{
char item[20]; //客户端的名字
int fifo_fd; //该客户端使用的私有管道
mylink next;
};
//初始化链表
void mylink_init(mylink *head);
//创建节点
mylink make_node(char *name,int fd);
//插入
void mylink_insert(mylink *head,mylink p);
//查找
mylink mylink_search(mylink *head,const char *keyname);
//删除
void mylink_delete(mylink *head,mylink p);
void free_node(mylink p);
//遍历(服务器端有时候会向所有的客户端发送更新等消息)
void mylink_travel(mylink *head,void (*vist)(mylink));
//销毁链表
void mylink_destory(mylink *head);
#endif
mylink.c
#include <stdio.h>
#include <string.h>
#include "mylink.h"
//初始化链表
void mylink_init(mylink *head)
{
*head = NULL;
}
//创建节点
mylink make_node(char *name,int fd)
{
mylink p = (mylink)malloc(sizeof(struct node));
strcpy(p->item,name);
p->fifo_fd = fd;
p->next = NULL;
}
//插入
void mylink_insert(mylink *head,mylink p)
{
//头插法
p->next = *head;
*head = p;
}
//查找
mylink mylink_search(mylink *head,const char *keyname)
{
mylink p;
for(p = *head;p != NULL;p = p->next)
{
if(strcmp(p->item,keyname) == 0)
{
return p;
}
}
return NULL;
}
//删除
void mylink_delete(mylink *head,mylink p)
{
mylink tmp;
//如果在头节点
if(*head == p)
{
*head = (*head)->next;
return;
}
//如果不在头节点
for(tmp = *head;tmp != NULL;tmp = tmp->next)
{
if(tmp != NULL && tmp->next == p)
{
tmp->next = p->next;
return;
}
}
}
void free_node(mylink p)
{
free(p);
}
//遍历(服务器端有时候会向所有的客户端发送更新等消息)
void mylink_travel(mylink *head,void (*vist)(mylink))
{
mylink p;
for(p = *head;p != NULL;p = p->next)
{
vist(p);
}
}
//销毁链表
void mylink_destory(mylink *head)
{
mylink p= *head, q;
while (p != NULL) {
q = p->next;
free(p);
p = q;
}
*head = NULL;
}
パイプライン設計: パイプラインは半二重通信であるため、クライアントがサーバーにデータを送信するときにサーバーが統合され、パイプラインが統合されるため、両端で相互に通信する場合は、2 つのパイプラインを設計するのが最適です。クライアントは同じパイプラインにデータを書き込み、サーバーはこのパイプラインからデータを順次読み取ることができます。ただし、サーバーがクライアントにデータを送信するとき、データを A または B に送信する場合があるため、各クライアントには中間パイプラインがあります。
クライアント:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include "qq_ipc.h"
#include "mylink.h"
//服务器端统一的管道
#define SERVER_FIFO "SEV_FIFO"
void sys_error(const char *str)
{
perror(str);
}
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("./client name\n");
}
int server_fd,client_fd,flag,len;
char cmdbuf[256];
//打开文件向服务器中写数据的管道
server_fd = open(SERVER_FIFO,O_NONBLOCK);
if(server_fd == -1)
{
sys_error("open");
}
//创建一个专属自己管道
mkfifo(argv[1],0777);
//通知服务器我现在要登录了
struct DATA_INFO cbuf,tmpbuf,talkbuf;
cbuf.protocal = 1;
//把管道的标识传给服务器端,服务器才能向里面写数据
strcpy(cbuf.srcname,argv[1]);
client_fd = open(argv[1],O_RDONLY|O_NOBLACK);
//将管道设置为非阻塞方式
flag = fcntl(STDIN_FILENO,F_GETFL);
flag |= O_NONBLOCK;
fcntl(STDIN_FILENO,F_SETFL,flag);
//将登录信息传给服务器
write(server_fd,&cbuf,sizeof(cbuf));
//信息交互
while(1)
{
//判断要交互的客户端
len = read(client_fd,&tmpbuf,sizeof(tmpbuf));
if(len > 0)
{
if(tmpbuf.protocal == 3)
{
printf("%s id not online\n",tmpbuf.destname);
}
else if(tmpbuf.protocal == 2)
{
printf("%s : %s\n",tmpbuf.srcname,tmpbuf.destname);
}
}
else if(len < 0)
{
if(errno != EAGAIN)//不是管道中没有数据
{
sys_err("client read\n");
}
}
len = read(STDIN_FILENO,&cmdbuf,sizeof(cmdbuf)); //从键盘输入
if(len >0)
{
char *dname,*databuf;
memset(&talkbuf,0,sizeof(talkbuf));
cmdbuf[len] = '\0';
dname = strtok(cmdbuf, "#\n"); /*按既定格式拆分字符串*/
if (strcmp("exit", dname) == 0) { /*退出登录:指定包号,退出者名字*/
talkbuf.protocal = 4;
strcpy(talkbuf.srcname, argv[1]);
write(server_fd, &talkbuf, sizeof(talkbuf));/*将退出登录包通过公共管道写给服务器*/
break;
} else {
talkbuf.protocal = 2; /*聊天*/
strcpy(talkbuf.destname, dname); /*填充聊天目标客户名*/
strcpy(talkbuf.srcname, argv[1]); /*填充发送聊天内容的用户名*/
databuf = strtok(NULL, "\0");
strcpy(talkbuf.data, databuf);
}
write(server_fd, &talkbuf, sizeof(talkbuf)); /*将聊天包写入公共管道*/
}
}
unlink(argv[1]); /*删除私有管道*/
close(client_fd); /*关闭私有管道的读端(客户端只掌握读端)*/
close(server_fd); /*关闭公共管道的写端(客户端值掌握写端)*/
return 0;
}
server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include "qq_ipc.h"
#include "mylink.h"
#define SERVER_PROT "SEV_FIFO" /*定义众所周知的共有管道*/
mylink head = NULL; /*定义用户描述客户端信息的结构体*/
void sys_err(char *str)
{
perror(str);
exit(-1);
}
/*有新用户登录,将该用户插入链表*/
int login_qq(struct DATA_INFO *buf, mylink *head)
{
int fd;
fd = open(buf->srcname, O_WRONLY); /*获取登录者名字,以只写方式打开以其名字命名的私有管道*/
mylink node = make_node(buf->srcname, fd); /*利用用户名和文件描述符创建一个节点*/
mylink_insert(head, node); /*将新创建的节点插入链表*/
return 0;
}
/*客户端发送聊天,服务器负责转发聊天内容*/
void transfer_qq(struct DATA_INFO *buf, mylink *head)
{
mylink p = mylink_search(head, buf->destname); /*遍历链表查询目标用户是否在线*/
if (p == NULL) {
struct DATA_INFO lineout = {3}; /*目标用户不在, 封装3号数据包*/
strcpy(lineout.destname, buf->destname); /*将目标用户名写入3号包*/
mylink q = mylink_search(head, buf->srcname); /*获取源用户节点,得到对应私有管道文件描述符*/
write(q->fifo_fd, &lineout, sizeof(lineout)); /*通过私有管道写给数据来源客户端*/
} else
write(p->fifo_fd, buf, sizeof(*buf)); /*目标用户在线,将数据包写给目标用户*/
}
/*客户端退出*/
int logout_qq(struct DATA_INFO *buf, mylink *head)
{
mylink p = mylink_search(head, buf->srcname); /*从链表找到该客户节点*/
close(p->fifo_fd); /*关闭其对应的私有管道文件描述符*/
mylink_delete(head, p); /*将对应节点从链表摘下*/
free_node(p); /*释放节点*/
}
void err_qq(struct DATA_INFO *buf)
{
fprintf(stderr, "bad client %s connect \n", buf->srcname);
}
int main(void)
{
int server_fd; /*公共管道文件描述符(读端)*/
struct DATA_INFO dbuf; /*定义数据包结构体对象*/
if (access(SERVER_PROT, F_OK) != 0) { /*判断公有管道是否存在, 不存在则创建*/
mkfifo(SERVER_PROT, 0664);
}
if ((server_fd = open(SERVER_PROT, O_RDONLY)) < 0) /*服务器以只读方式打开公有管道一端*/
sys_err("open");
mylink_init(&head); /*初始化链表*/
while (1) {
read(server_fd, &dbuf, sizeof(dbuf)); /*读取公共管道,分析数据包,处理数据*/
switch (dbuf.protocal) {
case 1: login_qq(&dbuf, &head); break;
case 2: transfer_qq(&dbuf, &head); break;
case 4: logout_qq(&dbuf, &head); break;
default: err_qq(&dbuf);
}
}
close(server_fd);
}