Linux下C语言项目—聊天室的搭建1.0

来源:互联网 发布:js去掉属性值 编辑:程序博客网 时间:2024/05/16 14:34

之所以为1.0,是因为虽然能运行,但有些地方的边际条件并没有得到补充,很容易产生错误,先上代码吧。

一个在 Linux 下可以使用的聊天软件,要求至少实现如下功能:
1. 采用 Client/Server 架构
2. Client A 登陆聊天服务器前,需要注册自己的 ID 和密码
3. 注册成功后,Client A 就可以通过自己的 ID 和密码登陆聊天服务器
4. 多个 Client X 可以同时登陆聊天服务器之后,与其他用户进行通讯聊天
5. Client A 成功登陆后可以查看当前聊天室内其他在线用户 Client x
6. Client A 可以选择发消息给某个特定的 Client X,即”悄悄话”功能
7. Client A 可以选择发消息全部的在线用户,即”群发消息”功能
8. Client A 在退出时需要保存聊天记录
9. Server 端维护一个所有登陆用户的聊天会的记录文件,以便备查

服务器端

#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>#include <pthread.h>#include <sqlite3.h>#include <stdlib.h>#include <sys/stat.h>#include <fcntl.h>#define FALSE 0#define TRUE  1#define PORT  9999#define SIZE  1024// 协议struct Msg{    char msg[1024];   // 消息内容    char name[20];    // 用户账号    char password[20];// 用户密码    char flag[2];     // 状态(1在线0下线)    char toname[20];    char fromname[20];    int  cmd;         // 消息类型};typedef struct User{    char name[20];   // 用户名    int  socket;     // 和用户通信的socket}UserData;typedef struct _node{    UserData data;    struct _node* next;}Node;Node* h;//创建链表Node * Create_List(){    Node *list = (Node*)malloc(sizeof(Node)/sizeof(char));    if (list == NULL)        return NULL;    list->next = NULL;   // 空表    return list;}//尾插int Insert_Last(Node *h, UserData data){    if (h == NULL)        return FALSE;    Node *node = (Node*)malloc(sizeof(Node)/sizeof(char));    if (node == NULL)    {        return FALSE;    }    node->data = data;    node->next = NULL;    Node* tmp = h;    while (tmp->next)    {        tmp = tmp->next;    }    tmp->next = node;    return TRUE;}// 初始化套接字,返回监听套接字int init_socket(){    //1、创建socket    int listen_socket = socket(AF_INET, SOCK_STREAM, 0);    if (listen_socket == -1)    {        perror ("socket");        return -1;    }    // 2、命名套接字,绑定本地的ip地址和端口    struct sockaddr_in addr;    memset(&addr, 0, sizeof(addr));    addr.sin_family  = AF_INET;     // 设置地址族    addr.sin_port    = htons(PORT); // 设置本地端口    addr.sin_addr.s_addr = htonl(INADDR_ANY);   // 使用本地的任意IP地址    int  ret = bind(listen_socket,  (struct sockaddr *)&addr, sizeof(addr));    if (ret == -1)    {        perror ("bind");        return -1;    }    // 3、监听本地套接字    ret = listen(listen_socket, 5);    if (ret == -1)    {        perror ("listen");        return -1;    }    printf ("等待客户端连接.......\n");    return listen_socket;}// 处理客户端连接,返回与连接上的客户端通信的套接字int  MyAccept(int listen_socket){    // 4、接收连接    // 监听套接字不能用来与客户端进行通信,它的职责是监听客户端的连接    // accpet 处理客户端的连接,如果成功接收,会返回一个新的套接字,用来与客户端进行通信    // accept的第三个参数 是一个传入传出参数    struct sockaddr_in client_addr; // 用来保存客户端的ip和端口信息    int len = sizeof(client_addr);    int client_socket = accept(listen_socket,   (struct sockaddr *)&client_addr,  &len);    if (client_socket == -1)    {        perror ("accept");    }    printf ("成功接收一个客户端: %s\n", inet_ntoa(client_addr.sin_addr));    return client_socket;}//创建数据库来保存用户信息void create_sqlite3(){    sqlite3* database;    //打开数据库    int ret = sqlite3_open("usermsg.db", &database);    if (ret != SQLITE_OK)    {        printf("打开数据库失败\n");        return;    }    char *errmsg = NULL; //创建用户信息表    char *sql = "create table if not exists usermsg(name TEXT,password TEXT,msg TEXT,primary  key(name))";    ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);    if (ret != SQLITE_OK)    {        printf("数据库操作失败\n");        return;    }}//用户注册void reg(int client_socket, struct Msg *msg){    printf ("%s 进行注册\n", msg->name);    sqlite3* database;    //打开数据库    int ret = sqlite3_open("usermsg.db", &database);    if (ret != SQLITE_OK)    {        printf("打开数据库失败\n");        return;    }    // 将用户进行保存    char buf[100];//保存用户账号密码    char *errmsg;    sprintf (buf, "insert into usermsg values('%s','%s')", msg->name, msg->password);    ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);    if (ret != SQLITE_OK)    {        msg->cmd = -1; //数据库中已有该账号    }       else    {        msg->cmd = 1001;//注册成功    }    write (client_socket, msg, sizeof(struct Msg));    sqlite3_close(database);}//登录账号void login(int client_socket, struct Msg *msg){    UserData data;    sqlite3* database;    //打开数据库    int ret = sqlite3_open("usermsg.db", &database);    if (ret != SQLITE_OK)    {        printf("打开数据库失败\n");        return;    }    //检测数据库中是否有该账号    char buf[100];    char *errmsg;    char **resultp = NULL;    int nrow, ncolumn;    sprintf (buf, "select password from usermsg where name = '%s'", msg->name);    ret = sqlite3_get_table(database, buf, &resultp, &nrow, &ncolumn, &errmsg);    if (ret != SQLITE_OK)    {        printf ("数据库操作失败:%s\n", errmsg);        return;    }    printf ("正在检测数据中\n");    if(strcmp(resultp[1],msg->password) == 0)    {        printf("该账号可以登录\n");        msg->cmd = 1002;        strcpy(data.name,msg->name);        data.socket = client_socket;        Insert_Last (h,data);    }    else    {        printf ("该账号不存在\n");        msg->cmd = -1;    }           write (client_socket, msg, sizeof(struct Msg));}//打印在线人员void display(int client_socket, struct Msg* msg){    Node* tmp = h->next;    while(tmp)    {        strcpy(msg->name,tmp->data.name);        write (client_socket, msg, sizeof(struct Msg));        tmp = tmp->next;    }}//群聊void group_chat(int client_socket, struct Msg* msg){    printf ("打开数据库成功\n");    Node* tmp = h->next;    printf("%s 发了一次群消息\n", msg->name);    while(tmp)    {        write (tmp->data.socket, msg, sizeof(struct Msg));        tmp = tmp->next;    }}//悄悄话void private_chat (int client_socket, struct Msg* msg){    printf ("%s 要给 %s 发一条消息\n", msg->fromname, msg->toname);    Node* tmp = h->next;    while(tmp)    {        if (strcmp(tmp->data.name, msg->toname)==0)        {            printf("%s\n",msg->msg);            write (tmp->data.socket, msg, sizeof(struct Msg));            return;        }        tmp = tmp->next;    }    if (tmp == NULL)    {        msg->cmd = -3;        write (client_socket, msg, sizeof(struct Msg));    }}//用户下线void quit_user(int client_socket, struct Msg* msg){    printf ("接收到客户端下线请求\n");    Node* tmp = h;    while(tmp->next)    {        if (strcmp(tmp->next->data.name, msg->name)==0)            break;        tmp = tmp->next;    }    if (tmp->next == NULL)    {        msg->cmd = -4;        write (client_socket, msg, sizeof(struct Msg));    }    Node* p = tmp->next;    tmp->next = p->next;    free(p);    write (client_socket, msg, sizeof(struct Msg));}//查看聊天记录void check_msg(int client_socket, struct Msg* msg){    printf ("正在赋予客户端检查聊天记录的权限\n");    //在客户端自己创建一个数据库    write (client_socket, msg, sizeof(struct Msg));}//修改密码void change_password(int client_socket, struct Msg* msg){    printf ("%s申请修改密码\n",msg->name);    sqlite3 * database;    // 打开数据库    int ret = sqlite3_open("usermsg.db", &database);    if (ret != SQLITE_OK)    {        printf ("打开数据库失败\n");        return;    }    printf ("打开数据库成功\n");    char buf[100];    char *errmsg = NULL;    sprintf (buf, "update usermsg set password = '%s' where name = '%s'", msg->password, msg->name);    ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);    if (ret != SQLITE_OK)    {        printf ("数据库操作失败:%s\n", errmsg);        return;    }    write (client_socket, msg, sizeof(struct Msg));}//发送文件void send_file(int client_socket, struct Msg* msg){    printf("%s收到文件传输请求\n",msg->toname);    int fd2 = open("2.txt", O_WRONLY|O_CREAT, 0777);    if (fd2 == -1)    {        perror ("open fd2");        return;    }    char buf[SIZE] = {0};    strcpy(buf, msg->msg);    write (fd2, buf, strlen(buf));    write (client_socket, msg, sizeof(struct Msg));}// 把负责处理客户端通信的函数改成线程的工作函数void* hanld_client(void* v){    int client_socket = (int)v;    struct Msg msg;    while(1)    {        // 从客户端读一个结构体数据        int ret = read(client_socket, &msg, sizeof(msg));        if (ret == -1)        {            perror ("read");            break;        }        // 代表客户端退出        if (ret == 0)        {            printf ("客户端退出\n");            break;        }        switch (msg.cmd)        {            case 1 :    // 客户端进行注册                reg(client_socket, &msg);                break;            case 2 :    // 客户端进行登录                login(client_socket, &msg);                break;            case 3 :    // 打印在线人员                display(client_socket, &msg);                break;            case 4 :    // 群聊                group_chat(client_socket, &msg);                break;            case 5 :    // 悄悄话                private_chat(client_socket, &msg);                break;            case 6 :    // 下线                quit_user(client_socket, &msg);                break;            case 7 :    // 查看聊天记录                check_msg(client_socket, &msg);                break;            case 8 :    //修改密码                change_password(client_socket, &msg);                break;            case 9 :    //文件传输                send_file(client_socket, &msg);                break;        }    }    close (client_socket);}int main(){    // 初始化套接字    int listen_socket = init_socket();    h = Create_List();    while (1)    {            // 获取与客户端连接的套接字        int client_socket = MyAccept(listen_socket);        // 创建一个线程去处理客户端的请求,主线程依然负责监听        pthread_t id;        pthread_create(&id, NULL, hanld_client,  (void *)client_socket);        pthread_detach(id); // 线程分离    }    close (listen_socket);    return 0;}

客户端

#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>#include <sqlite3.h>#include <stdlib.h>#include <time.h>#include <sys/stat.h>#include <fcntl.h>#define PORT  9999#define SIZE  1024struct Msg{    char msg[1024];    // 消息内容    char name[20];     // 用户账号    char password[20]; // 用户密码    char flag[2];      // 状态(1在线0下线)    char toname[20];    char fromname[20];    int  cmd;          // 消息类型};char Myname[20]; // 界面void interface(){    struct tm *ptr;    time_t lt;     lt =time(NULL);    ptr = gmtime(&lt);    printf(asctime(ptr));     printf ("1. 注册\n");    printf ("2. 登录\n");    printf ("3. 显示在线人员\n");    printf ("4. 群聊\n");    printf ("5. 悄悄话\n");    printf ("6. 用户下线\n");    printf ("7. 查看聊天记录\n");    printf ("8. 修改密码\n");    printf ("9. 文件传输\n");}//创建数据库void create_sqlite3(){    sqlite3* database;    //打开数据库    int ret = sqlite3_open("chatmsg.db", &database);    if (ret != SQLITE_OK)    {        printf("打开数据库失败\n");        return;    }    char *errmsg = NULL; //创建用户信息表    char *sql = "create table if not exists chatmsg(time TEXT, fromname TEXT, toname TEXT, massage TEXT,primary  key(time))";    ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);    if (ret != SQLITE_OK)    {        printf("数据库操作失败: %s\n", errmsg);        return;    }}//插入聊天记录char insert_msg(struct Msg msg,int judge){    struct tm *ptr;    time_t lt;    lt = time(NULL);    ptr = gmtime(&lt);    sqlite3 * database;    // 打开数据库    int ret = sqlite3_open("chatmsg.db", &database);    if (ret != SQLITE_OK)    {        printf ("打开数据库失败\n");        return -1;    }    int i = 0;    char time[30];    strcpy(time,asctime(ptr));    while (time[i] != '\n')    {        i++;    }    time[i] = '\0';    i = 0;    char fromname[20];    strcpy(fromname, msg.name);    while (fromname[i] != '\n')    {        i++;    }    fromname[i] = '\0';    i = 0;    char toname[20];    strcpy(toname, msg.toname);    while (toname[i] != '\n')    {        i++;    }    toname[i] = '\0';    i = 0;    char massage[100];    strcpy(massage, msg.msg);    while (massage[i] != '\n')    {        i++;    }    massage[i] = '\0';    char buf[100];    if (judge == 1)    {        strcpy(toname, "all");        sprintf (buf, "insert into chatmsg values('%s', '%s', '%s', '%s')", time, fromname, toname, massage);    }    else if (judge == 0)    {        sprintf (buf, "insert into chatmsg values('%s', '%s', '%s', '%s')", time, fromname, toname, massage);    }    char *errmsg = NULL;    ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);    if (ret != SQLITE_OK)    {        printf ("数据库操作失败:%s\n", errmsg);        return -1;    }    sqlite3_close(database);}//检查数据库聊天记录int check_msg(){    sqlite3 * database;    // 打开数据库    int ret = sqlite3_open("chatmsg.db", &database);    if (ret != SQLITE_OK)    {        printf ("打开数据库失败\n");        return -1;    }    // 3、char ***resultp: char *resultp[]     // 4、nrow: 多少行数据    // 5、ncolumn: 多少列数据    char *errmsg = NULL;    char **resultp = NULL;    int nrow, ncolumn;    char *sql = "select * from chatmsg";    ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);    if (ret != SQLITE_OK)    {        printf ("数据库操作失败:%s\n", errmsg);        return -1;    }       int i;    for (i = 0; i < (nrow+1)*ncolumn; i++)    {        if (i % ncolumn == 0)            printf ("\n");        printf ("%-25s", resultp[i]);    }    printf ("\n");      // 关闭数据库    sqlite3_close(database);    return 0;}void * readMsg(void *v){    int socketfd = (int)v;    struct Msg msg;    while (1)    {        read (socketfd, &msg, sizeof(msg));        switch (msg.cmd)        {            case 3:   // 打印在线人员                printf("%s\n",msg.name);                break;            case 4:   // 群聊                printf ("收到一条消息:%s\n", msg.msg);                break;            case 5:   // 悄悄话                printf ("%s 给你发一条消息:%s\n", msg.fromname, msg.msg);                break;            case -3 :                printf ("该好友不存在\n");                break;            case 6  :  //用户下线                printf ("服务器正在处理中\n");                sleep(1);                printf ("下线成功\n");                printf ("请按ENTER退出\n");                return;            case -4 :                printf ("不存在该账号在线\n");                printf ("请按ENTER退出\n");                break;            case 7 :   //查看聊天记录                printf ("您的聊天记录: \n");                check_msg();                printf ("请按ENTER退出\n");                break;            case 8 :   //修改密码                sleep(1);                printf ("服务器正在处理中\n");                printf ("处理成功\n");                printf ("请按ENTER退出\n");                break;            case 9 :  //文件传输                printf ("文件极速传输中,请稍后\n");                sleep(1);                printf ("文件传输成功\n");                printf ("请按ENTER退出\n");                break;        }    }}//用户注册void reg(int socketfd){    struct Msg msg;    msg.cmd = 1;    char name[20];    char password[20];    printf("请输入您的用户名:\n");    scanf("%s",name);    while(getchar() != '\n');    strcpy(msg.name, name);    printf("请输入您的密码: \n");    scanf("%s",password);    while(getchar() != '\n');    strcpy(msg.password, password); //输入账号密码    write (socketfd, &msg, sizeof(msg));    read (socketfd, &msg, sizeof(msg));    if (msg.cmd == 1001)    {        printf ("注册成功\n");        sleep(1);    }    else if (msg.cmd == -1)    {        printf ("该账号已被注册,请重新操作\n");        printf ("正在返回界面\n");        sleep(1);    }}//用户登录void login(int socketfd){    struct Msg msg;    msg.cmd = 2;    char name[20];    char password[20];    printf("请输入您的用户名:\n");    scanf ("%s",Myname);    while(getchar() != '\n');    strcpy(msg.name, Myname);    printf("请输入您的密码: \n");    scanf("%s",password);    while(getchar() != '\n');    strcpy(msg.password, password); //输入账号密码    write (socketfd, &msg, sizeof(msg));    read (socketfd, &msg, sizeof(msg));    if (msg.cmd == -1)    {        printf ("登录失败\n");    }    else if (msg.cmd == 1002)    {        printf ("登陆成功\n");    }    sleep(1);    pthread_t id;    pthread_create(&id, NULL, readMsg,  (void *)socketfd);    pthread_detach(id); // 线程分离}// 打印在线人员void display(int socketfd){    struct Msg msg;    msg.cmd = 3;    write (socketfd, &msg, sizeof(msg));}//群聊void group_chat(int socketfd){    struct Msg msg;    msg.cmd = 4;    printf ("请输入要发送的内容: \n");    fgets(msg.msg, 1024, stdin);    strcpy(msg.name,Myname);    insert_msg(msg,1);    write (socketfd, &msg, sizeof(msg));}//悄悄话void private_chat (int socketfd){    struct Msg msg;    msg.cmd = 5;    printf ("请输入要发送的对象名称: \n");    fgets(msg.toname, 20, stdin);    char *tmp = msg.toname;    while(tmp)    {        if(*tmp == '\n')        {            *tmp = '\0';            break;        }        tmp++;    }    printf ("请输入要发送的内容: \n");    fgets(msg.msg, 1024, stdin);    strcpy (msg.fromname, Myname);    strcpy (msg.name, Myname);    insert_msg(msg,0);    write (socketfd, &msg, sizeof(msg));}//用户下线void quit_user (int socketfd){    struct Msg msg;    msg.cmd = 6;    strcpy(msg.name,Myname);    write (socketfd, &msg, sizeof(msg));}//查看聊天记录void check(int socketfd){    struct Msg msg;    msg.cmd = 7;    write (socketfd, &msg, sizeof(msg));}//修改密码void change_password(int socketfd){    struct Msg msg;    msg.cmd = 8;    printf ("请输入您要修改的密码:\n");    char new_password[20];    scanf ("%s",new_password);    strcpy(msg.name, Myname);    strcpy(msg.password,new_password);    write (socketfd, &msg, sizeof(msg));}//发送文件void send_file(int socketfd){    struct Msg msg;    msg.cmd = 9;    printf("请输入要发送的对象名称\n");    fgets(msg.toname, 20, stdin);    char *tmp = msg.toname;    while(tmp)    {        if(*tmp == '\n')        {            *tmp = '\0';            break;        }        tmp++;    }    int fd1 = open("1.txt", O_RDONLY);    if (fd1 == -1)    {        perror ("open fd1");        return;    }    int ret = 0;    char buf[SIZE] = {0};    ret = read(fd1, buf, 1024);    buf[ret] = '\0';    strcpy(msg.msg,buf);    write (socketfd, &msg, sizeof(msg));}// 客户端向服务器发送数据void ask_server(int socketfd){    char ch[2];    while (1)    {        interface();        scanf("%c",ch);        while(getchar()!= '\n');        switch(ch[0])        {            case '1':     // 注册                reg(socketfd);                break;            case '2':     // 登录                login(socketfd);                break;            case '3':     //打印在线人员                display(socketfd);                break;            case '4':     //群聊                group_chat(socketfd);                break;                case '5':     //悄悄话                private_chat(socketfd);                break;            case '6':     //用户下线                quit_user(socketfd);                break;            case '7':     //查看聊天记录                check(socketfd);                break;            case '8':     //修改密码                change_password(socketfd);                break;            case '9':    //文件传输                send_file(socketfd);                break;        }        system("clear");    }}int main(){    create_sqlite3();    // 创建与服务器通信的套接字    int socketfd = socket(AF_INET, SOCK_STREAM, 0);    if (socketfd == -1)    {        perror ("socket");        return -1;    }    // 连接服务器    struct sockaddr_in addr;    memset(&addr, 0, sizeof(addr));    addr.sin_family  = AF_INET;     // 设置地址族    addr.sin_port    = htons(PORT); // 设置本地端口    inet_aton("127.0.0.1",&(addr.sin_addr));    // 连接服务器,如果成功,返回0,如果失败,返回-1    // 成功的情况下,可以通过socketfd与服务器进行通信    int ret = connect(socketfd, (struct sockaddr *)&addr, sizeof(addr));    if (ret == -1)    {        perror ("connect");        return -1;    }    printf ("成功连上服务器\n");    ask_server(socketfd);    // 关闭套接字    close(socketfd);    return 0;}

之后会不断修改添加功能。