1. 程式人生 > >Linux下C語言專案—聊天室的搭建1.0

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

之後會不斷修改新增功能。