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

之所以为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  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;          // 消息类型
};

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;
}

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

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页