C/S Linux _chat room


//client.h

#ifndef _CLIENT_H__
#define _CLIENT_H__


#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>


#define MAXLEN    1024


struct message              //信息结构
{
char   flag[8];         //标识:"all","view","trans"...还可能是客户的名字
char   name[12];        
int    size;            //信息 msg 字段的字节长度
char   msg[MAXLEN];     //信息的内容
};


struct msg                  //系统中用于信息交互的 消息队列
{
long   type;            //信息类型,要大于 0,小于 0 的 内核有特殊用途。一般用 进程号 表示,
    //因为 消息队列 多用于不同进程间的通信,用 进程号 标识先应的接口,用于发收信息。


char   text[4];         //本程序中,并不把数据放到 消息队列,而是放到 chitchat(客户聊天记录) 文件中
//用 消息队列,只是为了通过 其 发收 机制及时显示所接受到的信息,因为本程序的 输入
//与 显示 是分开的,即是用不同的进程,所以需要 消息队列。如果在 同一个 窗口下 则不需要
};


void   HandleQuit(int signal);                  //客户退出
void * HandleRecvmsg(int *sockfd);              //客户接受信息线程的处理操作
void * HandleSendfile(char *filename);          //客户发送文件线程的处理操作
void * HandleRecvfile(struct message *msg);     //客户接受文件线程的处理操作


int    qid;                 //整数描述符,用其来指代将要的 消息队列,并初始化
int    fd;                  // chitchat 文件的 文件描述符
int    sockfd;              //链接到服务器的套接口
int    savefilefd;          // 发送方:-1 是初始化,还没打开文件,大于 0,打开了的文件的 文件描述符
    // 接收方:0  是接受方收到询问是否接受时,所设定的
char   filetoname[12];      //用于文件传输,接收方的名字
char   filename[12];        //发送方将要发送的 文件名


// chitchat 文件:客户端运行时会自动生成已保存客户本次的通话记录
#endif






// client_main

#include "client.h"

#define SERVER_PORT      9877
/*
extern int    qid;            //
整数描述符,用其来指代将要的 消息队列,并初始化
extern int    fd;             // chitchat
文件的 文件描述符
extern int    savefilefd;     //
发送方:-1 是初始化,还没打开文件,大于 0,打开了的文件的 文件描述符
*/

int main(int argc,char *argv[])
{
    struct sockaddr_in   serveraddr;       //
服务器地址结构
    struct message       sendmsg;          //
将要发送的信息
    struct message       recvmsg;          //
服务器的回应信息
    int                  choose;           //
操作选择
    char                 buf[MAXLEN];
    pthread_t            pid;              //
接受线程id
    char                 flag[8];          //
发送信息的标识,allview...
    char                 name[12];         //
发送者名字

    qid                  = -1;
    fd                   = -1;
    savefilefd           = -1;
//  memset(&recvmsg,0,sizeof(structmessage));
//  memset(&sendmsg,0,sizeof(structmessage));
    if(argc < 2)
    {
        printf("
请输入服务器的地址 (./out [ip地址])");
        exit(1);
    }

    if((sockfd =socket(AF_INET,SOCK_STREAM,0)) < 0)
    {
        perror("socket error");
        exit(1);
    }
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family      = AF_INET;
    serveraddr.sin_addr.s_addr =inet_addr(argv[1]);         //
把 服务器地址赋值给 服务器地址结构
    serveraddr.sin_port        = htons(SERVER_PORT);

    if(connect(sockfd,(structsockaddr*)&serveraddr,sizeof(struct sockaddr)) < 0)
    {//
与服务器链接
        perror("connect error");
        exit(1);
    }

    signal(SIGINT,HandleQuit);                               //
退出是,启动 HandleQuit函数
//
问题:该 信号 函数应放在那个位置
    printf("----------------------------------\n");
    printf("|                                |\n");
    printf("|   input a number to work       |\n");
    printf("|\t1.login\t\t\t|\n");
    printf("|\t2.register\t\t|\n");
    printf("|\t3.exit\t\t\t|\n");
    printf("|                                |\n");
    printf("----------------------------------\n");
   
    printf("
请输入你的选择:");
    scanf("%d",&choose);
    while(choose != 1 && choose !=2 && choose != 3)
    {//
输入不正确
        printf("
你输入的不是上面的选项,请重新输入:\n");
        scanf("%d",&choose);
    }

    int  n = 3;                  //
3 次登录机会
    char     password[20];      
    char     password_sure[20];      //
密码确认
    int      done = -1;            //
输入密码正确

    switch(choose)
    {
        case 1://
登录操作
                                 
            while(n)
            {
                printf("
请输入你的帐号名字: ");
                scanf("%s",name);
                strcpy(sendmsg.name,name);
                printf("
请输入你的密码:");
                scanf("%s",sendmsg.msg);
                strcpy(sendmsg.flag,"login");
                send(sockfd,(structmessage *)&sendmsg,sizeof(struct message),0);
                printf("
等待服务器链接...\n");
                recv(sockfd,(structmessage *)&recvmsg,sizeof(struct message),0);
                if(strcmp(recvmsg.msg,"
登录成功") != 0)
                {//
登录不成功
                    printf("
登录不成功\n");
                    --n;
                    printf("
你还有 %d 次登录机会\n",n);
                    continue;                              //
结束本次循环,等待下一次登录
                }
               
                //
登录成功后的操作
                printf("
登录成功");
                n = 0;                                     //
客户已登录,退出后要重新打开程序方可登录
                pthread_create(&pid,NULL,(void*)HandleRecvmsg,(void*)&sockfd);    //
创建接受信息线程

                while(1)
                {
                    printf("
开始发送信息...\n");
                    printf("
请输入标识(flag):");            //私聊时,flag为 接受端的名字
                    scanf("%s",flag);
                    strcpy(sendmsg.flag,flag);
                    strcpy(sendmsg.name,name);

                    if(strcmp(sendmsg.flag,"all")== 0)
                    {//
群发信息
                        printf("
请输入你的内容:\n");
                        scanf("%s",buf);
                        strcpy(sendmsg.msg,buf);
                        send(sockfd,(structmessage *)&sendmsg,sizeof(struct message),0);
                        continue;                          //
接受本次循环,可重新发送信息
                    }
                    elseif(strcmp(sendmsg.flag,"view") == 0)
                    {//
查看在线客户
                        send(sockfd,(structmessage *)&sendmsg,sizeof(struct message),0);
                        continue;                          //
接受本次循环,可重新发送信息                      
                    }
                    elseif(strcmp(sendmsg.flag,"trans") == 0)
                    {
                        if(savefilefd <0)
                        {//
发送方
                            printf("
请输入所要发送的文件:");
                            scanf("%s",filename);
                            strcpy(sendmsg.msg,filename);
               
                            printf("
请输入文件的接收方: ");
                            scanf("%s",name);
                            strcpy(sendmsg.name,name);
   
                            send(sockfd,(structmessage*)&sendmsg,sizeof(struct message),0);  //
发送询问文件接收信息,agree |disagree
                        }
                        else
                        {//
接收方 ,与 接受线程的 trans 的 (接受端)相呼应
                            scanf("%s",buf);                //
输入是否同意接受文件,agree|disagree
                            send(sockfd,(structmessage*)&sendmsg,sizeof(struct message),0);   //
发送回复信息给 发送端    
                        }
//
问题:如果几个客户同时传送文件给同一个客户,如何处理
                        continue;
                    }//end trans
                    else
                    {//
私聊,因为 flag 与上面的都不符合,私聊时,flag为 接受端的名字
                        printf("
请输入你的内容:\n");
                        scanf("%s",buf);
                        strcpy(sendmsg.msg,buf);
                        send(sockfd,(structmessage*)&sendmsg,sizeof(struct message),0);
                    }
                }//end while(1)            
            }//end while(n)
            break;
        case 2://
注册操作

            printf("
请输入你的 用户名:");
            scanf("%s",name);
            do
            {//
密码输入
                printf("
请输入你的密码:");
                scanf("%s",password);
                printf("
请重新输入一次密码:");
                scanf("%s",password_sure); 
//
问题:如何隐藏密码
                if(strcmp(password,password_sure)== 0)
                {
                    printf("
密码输入正确");
                    done = 1;
                }      
                else
                {
                    printf("
密码输入不正确,请重新输入");
                    done = -1;
                }
            }while(done < 0);

            strcpy(sendmsg.flag,"reg");
            strcpy(sendmsg.name,name);
            strcpy(sendmsg.msg,password);

            printf("
向服务器发送注册请求...\n");
            send(sockfd,(struct message*)&sendmsg,sizeof(struct message),0);
            printf("
等待服务器应答...\n");
            recv(sockfd,(struct message*)&recvmsg,sizeof(struct message),0);
            printf("%s\n",recvmsg.msg);
            close(sockfd);

            break;
        case 3://
退出
            close(sockfd);
            printf("
程序已退出\n");
            exit(0);                                        //
正确退出
            break;
        default:
            printf("
你的输入有误\n");
    }

    return 0;
}

/*
测试时遇到问题
问题 1fread()的错误处理,即当 if((fread(...)==0))是,可能是 错误 或 EOF,用 if(feof(fd))则是EOF,否则不是
问题 2struct message msg字段 内容中如何处理有空格的语句???
   
预想解决方法:发送端还好,简单;但是 接收端麻烦: msg 内容都要扫描
*/

//client.c 功能函数的定义 


#include "client.h"


/**************************
    HandleQuit
函数:客户端退出处理
   
其参数:intsignal:所接收到的信号
   
返回值:void
**************************/
void  HandleQuit(int signal)
{
    if(fd > 0)
    {// chitchat
文件已打开
        close(fd);
    }

    if(qid > 0)
    {//
创建了 消息队列
        if(msgctl(qid,IPC_RMID,NULL) <0)
        {
            perror("
删除消息队列失败");
           
            close(savefilefd);           //
即使 消息队列 删除失败,也是要关闭 文件间的映射的,既要关闭文件描述符
            exit(1);
        }
    }

    close(savefilefd);                   //
关闭 消息队列 成功,关闭 文件描述符
    close(sockfd);
    printf("
程序正常退出...\n");

}




/**************************
    HandleSendfile
函数:客户传送文件的线程函数
   
其参数:charfilename[12]:发送方将要发送的 文件名
   
返回值:void*:(void*)0,(void*)1...0 表示正常退出,1表示出错,强制退出...
**************************/
void * HandleSendfile(char *filename)
{
    char           file[12];           //
文件名
    strcpy(file,filename);
   
    struct message  filedata;          //
该文件的相关信息
    strcpy(filedata.flag,"transf");    //
表示正在传送文件
    strcpy(filedata.name,filetoname);  //
文件的接收方
   
    ssize_t         nread;             //
从被发送的文件中读到的数据字节数
    int             fd;                //
与被发送的文件相映射的 文件描述符      
    char            buf[MAXLEN];
         
    if((fd = open(file,O_RDONLY)) < 0)
    {
        perror("
文件无法打开");
        strcpy(filedata.msg,"end$");   
        send(sockfd,&filedata,sizeof(structmessage),0);
        exit(1);
    }
//
问题:文件无法打开时,怎么办。直接发送 end$,结束该线程

    do
    {
        nread = read(fd,buf,sizeof(buf));
        if(nread < 0)
        {//
文件读取数据失败
            perror("
文件数据读取失败");
            close(fd);
            exit(1);
        }
        else if(nread == 0)
        {
            printf("
文件读取完成\n");
            strcpy(filedata.msg,"end$");
            send(sockfd,&filedata,sizeof(structmessage),0);
        }
        else
        {//
有数据要传
            filedata.size = nread;        //
数据的字节数
            strcpy(filedata.msg,buf);
            send(sockfd,&filedata,sizeof(structmessage),0);
        }
    }while(nread > 0);                    //
有数据要传

    close(fd);                            //
关闭被传送的文件
    savefilefd = -1;                      //
有可以传文件了

    return NULL;
}




/**************************
    HandleRecvfile
函数:接受端接收发送端传送文件的线程函数
   
其参数:structmessage *recvmsg:发送方发送过来的文件内容
   
返回值:void*:(void*)0,(void*)1...0 表示正常退出,1表示出错,强制退出...
**************************/
void * HandleRecvfile(struct message *msg)
{
    struct message  recvmsg = *msg;    //
把 接收线程 接收到的数据赋值到 接收文件线程 中
    int             nread;             //
将要写入文件的字节书数

    if(strcmp(recvmsg.msg,"end$")== 0)
    {//
接受完发送端发送过来的文件
        printf("
接收文件完成\n");
        close(savefilefd);             //
关闭刚接收完的文件
        savefilefd = -1;               //
有可以接受或发送文件了
    }

    do
    {//
把数据写入文件中
        nread =write(savefilefd,&recvmsg.msg,MAXLEN);
    }while(nread > 0);
   
    return NULL;
}





/**************************
    HandleRecvmsg
函数:客户端接受到服务器发来的消息
   
其参数:int*sockfd:链接套接口
   
返回值:void*:(void*)0,(void*)1...0 表示正常退出,1表示出错,强制退出...
**************************/
void * HandleRecvmsg(int *sockfd)
{
    int          connfd = *sockfd;       //
把 主线程 中的链接套接口 赋值到 接受信息线程的 套接口 中
    int          nread;                  //
接受到的信息的字节数
    char        str[MAXLEN];  

    struct message  recvmsg;             //
记录 接受到的信息
    struct msg      msg;                 //
消息队列
    time_t          curtime;             //
表示当下时间
   
    if((fd =open("chitchat",O_RDWR | O_CREAT | O_APPEND)) < 0)
    {
        perror("
打开 聊天记录文件 失败");
        exit(1);
    }
    if((qid = msgget(2222,IPC_CREAT |0666)) < 0)
    {//
显示的创建一个键值是为方便通讯
        perror("
创建 消息队列 不成功");
        exit(1);
    }
/*
在创建 消息队列 时,对于用户的权限不是很清楚,解决(下列图表)

数字值               符号值                       说明  
(八进制)  消息队列     信号量      共享内存区
 0400    MSG_R       SEM_R       SHM_R       
由用户(属主)读
 0200    MSG_W       SEM_A       SHM_W       
由用户(属主)写
 0040    MSG_R >> 3  SEM_R >>3  SHM_R >> 3  
由(属)组成员读
 0020    MSG_W >> 3  SEM_A >>3  SHM_W >> 3  
由(属)组成员写
 0004    MSG_R >> 6  SEM_R >>6  SHM_R >> 6  
由其他用户读
 0002    MSG_W >> 6  SEM_A >>6  SHM_W >> 6  
由其他用户写

记号 >> 3 的意思是将 值 右移 3
该图表上的 权限 都是用 二进制的 AND|)运算的
*/
    msg.type = getpid();                  //
得到当前 进程号,以使 消息队列 可分辨相关数据的处理对象
    strcpy(msg.text,"ok");                //
该数据作用不大,只是为了起到填充数据的作用

   
    while(1)
    {
        nread = recv(connfd,(structmessage *)&recvmsg,sizeof(struct message),0);
       
        if(nread < 0)
        {//
接受信息时出错
            perror("
接受信息失败");
            close(fd);
            exit(1);
        }
        else if(nread == 0)
        {// IP
数据包内容为空
            printf("
与服务器断开链接...\n");
            close(fd);
            exit(1);
        }
        elseif(strcmp(recvmsg.flag,"all") == 0)
        {//
群聊信息
            time(&curtime);                    //
获取当前时间
//          sprintf(str," %s %s
发给所有客户:%s\n\n",ctime(&curtime),recvmsg.name,recvmsg.msg);      
            printf(" %s %s
发给所有客户:%s\n\n",ctime(&curtime),recvmsg.name,recvmsg.msg);                
        }
        elseif(strcmp(recvmsg.flag,"view") == 0)
        {//
查看在线客户
            time(&curtime);
            printf(" %s
当前在线客户:%s\n\n",ctime(&curtime),recvmsg.msg);
            continue;                           //
结束 本次 循环,继续等待新的信息
        }
        else if(strcmp(recvmsg.flag,"trans")== 0)
        {//
传送 文件 询问信息
            pthread_t  pid;                     //
用于传送文件的线程id

            if(strcmp(recvmsg.msg,"agree")== 0)
            {//
(发送端)发送方收到接收方的肯定接受文件的回复信息
                strcpy(filetoname,recvmsg.name);
                pthread_create(&pid,NULL,(void*)HandleSendfile,(void*)filename);
                continue;
            }
            elseif(strcmp(recvmsg.msg,"disagree") == 0)
            {//
(发送端)接收方拒绝接收文件
                printf("
客户 %s 拒绝接收 %s文件\n",recvmsg.name,filename);
                savefilefd = -1;                //
客户又可以重新选择在线用户传送文件了
                continue;
            }
            else if(strcmp(recvmsg.msg,"noexist")== 0)
            {//
(发送端)客户不存在
                printf("
不存在该用户\n");          //可从服务器查看在线的客户,用 flag = view即可
                savefilefd = -1;                 //
客户又可以重新选择在线用户传送文件了
                continue;
            }
            else
            {//
(接受端)如果发送端发送文件结束询问,~.msg 为 发送端要发送的文件名,故和以上的都不匹配
                printf("
客户 %s 传送 %s文件给你,是否接收?[agree(同意)] | [disagree(不同意)]\n",\
                       recvmsg.name,recvmsg.msg);//
作为接受端,其接受到的的是发送端的传送文件询问信息,
                                                 //
此时,recvmsg.name为发送端的名字,
                                                //recvmsg.msg
为将要发送的问文件名 
                strcpy(filename,recvmsg.msg);    //
接收方接受到的文件名

                if((savefilefd =open(recvmsg.msg,O_CREAT | O_RDWR | O_APPEND,0666)) <0)
                {//
savefiledf 作为与接收到的文件的 文件描述符,但此时打开该文件不成功
                    printf("
无法创建 %s 文件",recvmsg.msg);
                }          
            }
/*
在传送文件的设计中,需要考虑到接收方,发送方都是客户端,都要接受收据
故在这数据的接受函数中,这个文件传送的询问信息关系到这两方
*/
            continue;                           //
继续等待信息
        }//end trans
        elseif(strcmp(recvmsg.flag,"transf") == 0)
        {//
(接收方)接受发送端发送过来的文件
/*
            pthread_t        pid;                 //
接收方接收文件的线程处理函数
            pthread_create(&pid,NULL,(void*)HandleRecvfile,(void*)&recvmsg);
问题:如此每次接受到一条文件信息,就要创建一个接收文件线程,不好。
   
如需真用接收文件线程,就需要与接收线程分开(HandleRecvmsg
*/
            if(strcmp(recvmsg.msg,"end$")== 0)
            {//
接受完发送端发送过来的文件
                printf("
接收文件完成\n");
                close(savefilefd);             //
关闭刚接收完的文件
                savefilefd= -1;               //
有可以接受或发送文件了
            }

            do
            {//
把数据写入文件中
                nread =write(savefilefd,&recvmsg.msg,MAXLEN);
            }while(nread > 0);

            continue;
        }
        else
        {//
私聊,因为私聊是,flag 为 接收端的 名字,与以上不符
            time(&curtime);                    //
获取当前时间
//          sprintf(str,"
私聊: %s %s 发给客户:%s\n\n",ctime(&curtime),recvmsg.name,recvmsg.msg); 
            printf("
私聊: %s %s 发给客户:%s\n\n",ctime(&curtime),recvmsg.name,recvmsg.msg);
        }//
群聊,私聊都没有用 continue 是因为,这些内容要写入 聊天记录 文件中,一起处理,方便而已,
         //
也可用continue,只要分别在 群聊,私聊 中直接写入 聊天记录 文件即可

        write(fd,str,sizeof(str));              //
把消息写入 聊天记录 文件中
        msgsnd(qid,&msg,sizeof(structmsg),0);  //
发送消息给 消息队列,通知其 display进程 到 聊天记录 文件中读取数据

    }//end while

    return NULL;
}
/*
问题:可用 switch,可用 枚举 把 flag 打包起来,用相关数字表示
struct message
{
    enum flag {all,view...}
    ...
}
*/






 




 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值