網路程式設計專案:linux下基於C/S架構的聊天室
阿新 • • 發佈:2019-02-16
一、專案要求:
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 端維護一個所有登陸使用者的聊天會的記錄檔案,以便備查
10.可以進行檔案傳輸
二、附加功能:
1. Server 可以內建一個特殊許可權的賬號 root,即超級管理員,用於管理聊天室
2. Admin 可以將某個 Client X “提出聊天室”
3. Admin 可以將某個 Client X ”設為只能旁聽,不能發言,即禁言功能
三、專案所需相關知識點:網路程式設計(C/S模型的架構),檔案I/O程式設計(將使用者資訊和聊天記錄儲存在資料庫檔案中,檔案傳輸),多程序或多執行緒程式設計(一個伺服器可以連線多個客戶端)
四、專案使用(測試)說明:將client.c, client.c和server.c分別置於不同目錄下,編譯命令:gcc client.c -o client -lpthread -lsqlite3
執行命令:./client (server.c同樣編譯執行)
五、專案程式碼
伺服器:
#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 <sys/stat.h> #include <fcntl.h> #define SIZE 20 #define PORT 9999 #define TRUE 1 #define FALSE -1 struct Usr { char name[SIZE]; int socket; int flag;//用來判斷該使用者是否被禁言,沒有被禁設為0,被禁設為1 }; struct Msg { struct Usr usr[SIZE]; char msg[1024]; char buf[1024]; char name[SIZE]; char fromname[SIZE]; char toname[SIZE]; char password[SIZE]; int cmd; int filesize; int flag; //用來判斷使用者許可權 0代表普通使用者,1代表超級使用者 }; struct Usr usr[SIZE]; int count; pthread_mutex_t mutex; //互斥鎖,用來避免兩個客戶端同時訪問全域性變數 //檢視線上使用者 void see_online(int client_socket, struct Msg *msg) { int i; for(i=0; i<20; i++) { msg->usr[i] = usr[i]; } write(client_socket, msg, sizeof(struct Msg)); } //儲存一條聊天記錄 void insert_record(struct Msg *msg) { sqlite3 * database; int ret = sqlite3_open("allrecord.db", &database); if (ret != SQLITE_OK) { printf ("開啟資料庫失敗\n"); return ; } //獲取系統當前時間 time_t timep; char s[30]; time(&timep); strcpy(s,ctime(&timep)); int count = strlen(s); s[count-1] = '\0'; char *errmsg = NULL; char *sql = "create table if not exists allrecord(time TEXT,fromname TEXT,toname TEXT,word TEXT)"; ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf ("聊天記錄表建立失敗:%s\n", errmsg); return; } char buf[100]; sprintf(buf, "insert into allrecord values('%s','%s','%s','%s')",s,msg->fromname, msg->toname,msg->msg); ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf ("新增聊天記錄失敗:%s\n", errmsg); return ; } sqlite3_close(database); return ; } //群聊 void chat_group(int client_socket, struct Msg *msg) { int i = 0; for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(msg->fromname, usr[i].name) == 0) { break; } } if(usr[i].flag == 0) //判斷該使用者有沒有被禁言 { printf ("%s 發一次群訊息\n", msg->fromname); insert_record(msg); for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(msg->fromname, usr[i].name) != 0) { write (usr[i].socket, msg, sizeof(struct Msg)); } } } else { msg->cmd = 1003; write (client_socket, msg, sizeof(struct Msg)); } } //私聊 void chat_private(int client_socket, struct Msg *msg) { int i; for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(msg->fromname, usr[i].name) == 0) { break; } } if(usr[i].flag == 0) { printf("%s給%s發了一條訊息\n", msg->fromname, msg->toname); insert_record(msg); for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(usr[i].name, msg->toname)==0) { write (usr[i].socket, msg, sizeof(struct Msg)); break; } } } else { msg->cmd = 1003; write (client_socket, msg, sizeof(struct Msg)); } } //獲取檔案大小 int file_size(char* filename) { struct stat statbuf; stat(filename,&statbuf); int size=statbuf.st_size; return size; } //上傳檔案 void send_file(int client_socket, struct Msg *msg) { printf("使用者%s在聊天室內上傳了一個檔案%s\n",msg->fromname,msg->msg); int i; for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(usr[i].name, msg->fromname) != 0) { write (usr[i].socket, msg, sizeof(struct Msg)); break; } } int fd = open(msg->msg, O_RDWR | O_CREAT, 0777); if(fd == -1) { perror("open"); printf("檔案傳輸失敗\n"); } int size = msg->filesize; char buf[65535]; memset(buf, 0, 65535); int ret = read(client_socket, buf, size); if(ret == -1) { perror("read"); return; } write(fd, buf, ret); close(fd); } //從使用者資料庫刪除一個使用者 void del_fromsql(struct Msg *msg) { sqlite3 * database; int ret = sqlite3_open("usr.db", &database); if (ret != SQLITE_OK) { printf ("開啟資料庫失敗\n"); return; } char *errmsg = NULL; char buf[100]; sprintf(buf, "delete from usr where name = '%s'", msg->name); ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf ("刪除使用者失敗:%s\n", errmsg); return; } sqlite3_close(database); return; } //登出使用者 void logout(int client_socket,struct Msg *msg) { int i,j; for(i=0; i<count; i++) { if(strcmp(msg->name, usr[i].name) == 0) break; } for(j=i; j<count; j++) { usr[j] = usr[j+1]; } count--; printf("正在登出使用者%s\n",msg->name); del_fromsql(msg); write(client_socket, msg, sizeof(struct Msg)); return; } //使用者下線 void off_line(int client_socket,struct Msg *msg) { pthread_mutex_lock(&mutex); // 搶鎖 printf("使用者%s下線\n",msg->name); int i,j; for(i=0; i<count; i++) { if(strcmp(msg->name, usr[i].name) == 0) break; } for(j=i; j<count; j++) { usr[j] = usr[j+1]; } count--; pthread_mutex_unlock(&mutex); // 解鎖 write(client_socket, msg, sizeof(struct Msg)); return; } //使用者下載檔案 void download_file(int client_socket,struct Msg *msg) { printf("使用者%s下載了檔案%s\n", msg->name, msg->msg); int size = file_size(msg->msg); msg->filesize = size; write(client_socket, msg, sizeof(struct Msg)); usleep(100000); int fd = open(msg->msg, O_RDONLY, 0777); if(fd == -1) { perror("open"); printf("檔案下載失敗\n"); } char buf[65535]; memset(buf, 0, 65535); int ret = read(fd, buf, size); if(ret == -1) { perror("read"); return; } write(client_socket, buf, ret); close(fd); } //設定禁言 void forbid_speak(int client_socket,struct Msg *msg) { msg->cmd = 1003; printf("使用者%s已被禁言\n",msg->msg); pthread_mutex_lock(&mutex); int i; for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg)==0) { write (usr[i].socket, msg, sizeof(struct Msg)); usr[i].flag = 1; break; } } pthread_mutex_unlock(&mutex); } //解除禁言 void relieve_speak(int client_socket,struct Msg *msg) { msg->cmd = 1004; printf("使用者%s已被解除禁言\n",msg->msg); pthread_mutex_lock(&mutex); int i; for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg)==0) { write (usr[i].socket, msg, sizeof(struct Msg)); usr[i].flag = 0; break; } } pthread_mutex_unlock(&mutex); } //踢出聊天室 void kickout_room(int client_socket,struct Msg *msg) { msg->cmd = 1005; printf("使用者%s已被踢出聊天室\n",msg->msg); pthread_mutex_lock(&mutex); int i; for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg) != 0) { //給線上使用者通知某某某被踢出聊天室 write (usr[i].socket, msg, sizeof(struct Msg)); } } pthread_mutex_unlock(&mutex); for (i = 0; i < SIZE; i++) { if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg) == 0) { break; } } msg->cmd = 1006; //strcpy(msg->name,msg->msg); //off_line(usr[i].socket,msg); write (usr[i].socket, msg, sizeof(struct Msg)); } //超級使用者 void surperusr(int client_socket) { 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: see_online(client_socket, &msg); break; case 2: chat_group(client_socket, &msg); break; case 3: chat_private(client_socket, &msg); break; case 6: forbid_speak(client_socket, &msg); // 設定禁言 break; case 7: relieve_speak(client_socket,&msg); // 解除禁言 break; case 8: off_line(client_socket,&msg); return; case 9: kickout_room(client_socket,&msg); // 踢出聊天室 break; } } } //普通使用者 void commonusr(int client_socket) { 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: see_online(client_socket, &msg); break; case 2: chat_group(client_socket, &msg); break; case 3: chat_private(client_socket, &msg); break; case 6: send_file(client_socket, &msg); break; case 7: logout(client_socket,&msg); return; case 8: off_line(client_socket,&msg); return; case 9: download_file(client_socket,&msg); break; } } } //新增到線上使用者列表 void add_usr(struct Msg *msg, int client_socket) { pthread_mutex_lock(&mutex); // 搶鎖 strcpy(usr[count].name, msg->name); usr[count].socket = client_socket; count++; pthread_mutex_unlock(&mutex); // 解鎖 } // 註冊 void reg(int client_socket, struct Msg *msg) { //查詢使用者是否已經被註冊 printf("正在查詢該使用者是否被註冊...\n"); if(find_name(msg) == TRUE) { printf("使用者%s已經被註冊\n",msg->name); msg->cmd = 0; } else { if(insert_sql(msg) == TRUE) { msg->cmd = 1; printf("使用者%s成功新增到資料庫\n",msg->name); } } write(client_socket, msg, sizeof(struct Msg)); } // 登陸 void login(int client_socket, struct Msg *msg) { int flag1 = 0; //用來判斷該使用者有沒有成功登陸 1代表成功 //檢查該使用者有沒有註冊 printf("正在查詢該使用者有沒有註冊...\n"); if(find_name(msg) == TRUE) { if(find_np(msg) == TRUE) { if(check_ifonline(msg) == TRUE) { msg->cmd = 3; printf("使用者%s已經登陸過了\n",msg->name); } else { msg->cmd = 0; printf("檢查該使用者許可權\n"); if(check_root(msg) == TRUE) { printf("該使用者是超級使用者\n"); msg->flag = 1; } else { printf("該使用者是普通使用者\n"); msg->flag = 0; } printf("使用者%s登陸成功\n",msg->name); flag1 = 1; add_usr(msg, client_socket); //新增到線上使用者列表 } } else { msg->cmd = 1; printf("使用者%s密碼輸入錯誤\n",msg->name); } } else { msg->cmd = 2; printf("使用者%s還沒有註冊\n",msg->name); } write(client_socket, msg, sizeof(struct Msg)); if(flag1 == 1) { if(msg->flag == 1) surperusr(client_socket); if(msg->flag == 0) commonusr(client_socket); } } //檢視使用者許可權 int check_root(struct Msg *msg) { if(strcmp(msg->name, "root") == 0) return TRUE; else return FALSE; } void* hanld_client(void* v) { struct Msg msg; int client_socket = (int)v; while(1) { // 從客戶端讀一個結構體資料 int ret = read(client_socket, &msg, sizeof(msg)); if (ret == -1) { perror ("read"); break; } // 代表客戶端退出 if (ret == 0) { printf ("客戶端退出\n"); break; } printf("從客戶端讀到一個使用者:%s, %s, %d\n", msg.name, msg.password, msg.cmd); switch (msg.cmd) { case 1: reg(client_socket, &msg); break; case 2: login(client_socket, &msg); break; } } close (client_socket); } //檢查該使用者是否線上 int check_ifonline(struct Msg *msg) { int i; for(i=0; i<count; i++) { if(strcmp(msg->name, usr[i].name) == 0) return TRUE; } if(i == count) return FALSE; } //查詢使用者名稱 int find_name(struct Msg *msg) { sqlite3 * database; int ret = sqlite3_open("usr.db", &database); if (ret != SQLITE_OK) { printf ("開啟資料庫失敗\n"); return FALSE; } char *errmsg = NULL; char **resultp = NULL; int nrow, ncolumn; char *sql = "select name from usr"; ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg); if (ret != SQLITE_OK) { printf ("使用者查詢失敗:%s\n", errmsg); return FALSE; } int i; for(i=0; i<nrow+1; i++) { if(strcmp(resultp[i], msg->name) == 0) return TRUE; } return FALSE; } //查詢使用者名稱和密碼 int find_np(struct Msg *msg) { sqlite3 * database; int ret = sqlite3_open("usr.db", &database); if (ret != SQLITE_OK) { printf ("開啟資料庫失敗\n"); return FALSE; } char *errmsg = NULL; char **resultp = NULL; int nrow, ncolumn; char *sql = "select * from usr"; ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg); if (ret != SQLITE_OK) { printf ("使用者查詢失敗:%s\n", errmsg); return FALSE; } int i; for(i=0; i<(nrow+1)*ncolumn; i++) { if(strcmp(resultp[i], msg->name) == 0 && strcmp(resultp[i+1], msg->password) == 0) return TRUE; } return FALSE; } //新增使用者到資料庫 int insert_sql(struct Msg *msg) { sqlite3 * database; int ret = sqlite3_open("usr.db", &database); if (ret != SQLITE_OK) { printf ("開啟資料庫失敗\n"); return FALSE; } char *errmsg = NULL; char buf[100]; sprintf(buf, "insert into usr values('%s','%s')", msg->name, msg->password); ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf ("新增使用者失敗:%s\n", errmsg); return FALSE; } sqlite3_close(database); return TRUE; } //建立所有使用者的聊天記錄資料庫 void setup_record() { sqlite3 * database; int ret = sqlite3_open("allrecord.db", &database); if (ret != SQLITE_OK) { printf ("開啟資料庫失敗\n"); return; } char *errmsg = NULL; char *sql = "create table if not exists allrecord(time TEXT,fromname TEXT,toname TEXT,word TEXT)"; ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf ("聊天記錄表建立失敗:%s\n", errmsg); return; } sqlite3_close(database); return; } //建立使用者資料庫,並在裡面新增超級使用者 int setup_sql() { sqlite3 * database; int ret = sqlite3_open("usr.db", &database); if (ret != SQLITE_OK) { printf ("開啟資料庫失敗\n"); return FALSE; } char *errmsg = NULL; char *sql = "create table if not exists usr(name TEXT,password TEXT)"; ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf ("使用者表建立失敗:%s\n", errmsg); return FALSE; } struct Msg msg; strcpy(msg.name, "root"); strcpy(msg.password, "123"); insert_sql(&msg); sqlite3_close(database); 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) { struct sockaddr_in client_addr; int len = sizeof(client_addr); int client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &len); if (client_socket == -1) { perror ("accept"); return -1; } printf ("成功接收一個客戶端: %s\n", inet_ntoa(client_addr.sin_addr)); return client_socket; } int main() { setup_sql(); setup_record(); pthread_mutex_init(&mutex, NULL); int listen_socket = init_socket(); 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); pthread_mutex_destroy(&mutex); return 0; }
客戶端:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 9999
#define SIZE 20
int flag = 0;
int offline = 0; //用來判斷該使用者有沒有被踢 0代表沒有,1代表被踢
struct Usr
{
char name[SIZE];
int socket;
int flag;//用來判斷該使用者是否被禁言,沒有被禁設為0,被禁設為1
};
struct Msg
{
struct Usr usr[SIZE];
char msg[1024];
char buf[1024];
char name[SIZE];
char fromname[SIZE];
char toname[SIZE];
char password[SIZE];
int cmd;
int filesize;
int flag; //用來判斷使用者許可權 0代表普通使用者,1代表超級使用者
};
struct Msg tmp;
//顯示空行
void display_line()
{
system("clear");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
}
//顯示主介面
void maininterface()
{
display_line();
printf("\t\t1、 註冊\n");
printf("\n");
printf("\t\t2、 登陸\n");
printf("\n");
printf("\t\t3、 退出\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\t\t請輸入指令:\n");
}
//顯示普通使用者介面
void cominterface(struct Msg *msg)
{
display_line();
printf("\t\t-.- %s:\n", msg->name);
printf("\n");
printf("\t\t1、 檢視線上使用者\n");
printf("\n");
printf("\t\t2、 群發訊息\n");
printf("\n");
printf("\t\t3、 傳送悄悄話\n");
printf("\n");
printf("\t\t4、 刪除聊天記錄\n");
printf("\n");
printf("\t\t5、 檢視聊天記錄\n");
printf("\n");
printf("\t\t6、 上傳檔案\n");
printf("\n");
printf("\t\t7、 登出當前使用者\n");
printf("\n");
printf("\t\t8、 退出當前賬號\n");
printf("\n");
printf("\t\t9、 下載檔案\n");
printf("\n");
printf("\t\t請輸入指令:");
}
//顯示超級使用者介面
void supinterface(struct Msg *msg)
{
display_line();
printf("\t\t-.- %s:\n", msg->name);
printf("\n");
printf("\t\t1、 檢視線上使用者\n");
printf("\n");
printf("\t\t2、 群發訊息\n");
printf("\n");
printf("\t\t3、 傳送悄悄話\n");
printf("\n");
printf("\t\t4、 刪除聊天記錄\n");
printf("\n");
printf("\t\t5、 檢視聊天記錄\n");
printf("\n");
printf("\t\t6、 設定禁言\n");
printf("\n");
printf("\t\t7、 解除禁言\n");
printf("\n");
printf("\t\t8、 退出當前賬號\n");
printf("\n");
printf("\t\t9、 踢出聊天室\n");
printf("\n");
printf("\t\t請輸入指令:");
}
//檢視線上使用者
void see_online(int socketfd, struct Msg *msg)
{
msg->cmd = 1;
write(socketfd, msg, sizeof(struct Msg));
//struct Usr usr[SIZE];
//read(socketfd, usr, sizeof(usr));
return;
}
//儲存一條聊天記錄
void insert_mysql(struct Msg *msg)
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("開啟資料庫失敗\n");
return ;
}
//獲取系統當前時間
time_t timep;
char s[30];
time(&timep);
strcpy(s,ctime(&timep));
int count = strlen(s);
s[count-1] = '\0';
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("使用者表建立失敗:%s\n", errmsg);
return;
}
char buf[100];
sprintf(buf, "insert into person values('%s','%s','%s','%s')",s,msg->fromname, msg->toname,msg->msg);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("新增聊天記錄失敗:%s\n", errmsg);
return ;
}
sqlite3_close(database);
return ;
}
//群聊
void chat_group(int socketfd, struct Msg *msg)
{
msg->cmd = 2;
printf ("\t\t請輸入要傳送的內容: ");
//scanf("%s",msg->msg);
//while(getchar() != '\n');
fgets(msg->msg, 1024, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
strcpy(msg->fromname, msg->name);
strcpy(msg->toname, "all");
insert_mysql(msg);
write (socketfd, msg, sizeof(struct Msg));
//printf("aaa\n");
}
//傳送悄悄話
void chat_private(int socketfd, struct Msg *msg)
{
msg->cmd = 3;
printf ("\t\t請輸入要傳送的物件名稱: \n");
fgets(msg->toname, SIZE, stdin);
int len = strlen(msg->toname);
msg->toname[len-1] = '\0';
printf ("\t\t請輸入要傳送的內容: \n");
fgets(msg->msg, 1024, stdin);
len = strlen(msg->msg);
msg->msg[len-1] = '\0';
strcpy(msg->fromname, msg->name);
insert_mysql(msg);
write(socketfd, msg, sizeof(struct Msg));
}
//刪除聊天記錄
void del_personsql()
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("開啟資料庫失敗\n");
return;
}
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("使用者表建立失敗:%s\n", errmsg);
return;
}
char *buf = "drop table person";
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("刪除聊天記錄失敗:%s\n", errmsg);
return;
}
sqlite3_close(database);
display_line();
printf("\t\t刪除成功\n");
printf("\t\t按ENTER鍵返回\n");
while(getchar() != '\n');
return;
}
//檢視聊天記錄
void see_record()
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("開啟資料庫失敗\n");
return ;
}
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("使用者表建立失敗:%s\n", errmsg);
return;
}
char **resultp = NULL;
int nrow, ncolumn;
char *buf = "select * from person";
ret = sqlite3_get_table(database, buf, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
return;
}
//判斷表是否為空
if(nrow == 0)
{
display_line();
printf("\t\t沒有聊天記錄\n");
}
else
{
int i;
system("clear");
printf ("nrow = %d, ncolumn = %d\n", nrow, ncolumn);
printf("\t\t%s",resultp[0]);
printf("\t%12s",resultp[1]);
printf("\t%8s",resultp[2]);
printf("\t%s",resultp[3]);
for (i = 4; i < (nrow+1)*ncolumn; i++)
{
if (i % ncolumn == 0)
printf ("\n");
printf ("%12s", resultp[i]);
}
printf ("\n");
}
printf("\t\t按ENTER鍵返回\n");
while(getchar() != '\n');
sqlite3_free_table(resultp);
sqlite3_close(database);
return;
}
//獲取檔案大小
int file_size(char* filename)
{
struct stat statbuf;
stat(filename,&statbuf);
int size=statbuf.st_size;
return size;
}
//上傳檔案
void send_file(int socketfd, struct Msg *msg)
{
msg->cmd = 6;
strcpy(msg->fromname, msg->name);
printf("\t\t請輸入要傳送的檔名:\n");
fgets(msg->msg, 1024, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
int size = file_size(msg->msg);
msg->filesize = size;
write(socketfd, msg, sizeof(struct Msg));
printf("\t\t正在上傳,請稍後...\n");
sleep(1);
int fd = open(msg->msg, O_RDONLY, 0777);
if(fd == -1)
{
perror("open");
printf("檔案傳輸失敗\n");
}
char buf[65535];
memset(buf, 0, 65535);
int ret = read(fd, buf, size);
if(ret == -1)
{
perror("read");
return;
}
write(socketfd, buf, ret);
close(fd);
printf("\t\t上傳檔案成功\n");
sleep(1);
}
//登出當前使用者
void logout(int socketfd, struct Msg *msg)
{
msg->cmd = 7;
write(socketfd, msg, sizeof(struct Msg));
display_line();
printf("\t\t正在登出...\n");
}
//設定禁言
void forbid_speak(int socketfd, struct Msg *msg)
{
msg->cmd = 6;
printf("\t\t請輸入要禁言的物件:\n");
fgets(msg->msg, SIZE, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
}
//解除禁言
void relieve_speak(int socketfd, struct Msg *msg)
{
msg->cmd = 7;
printf("\t\t請輸入要解除禁言的物件:\n");
fgets(msg->msg, SIZE, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
}
//踢出聊天室
void kickout_room(int socketfd, struct Msg *msg)
{
msg->cmd = 9;
printf("\t\t請輸入要踢除的物件:\n");
fgets(msg->msg, SIZE, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
}
//下線
void off_line(int socketfd, struct Msg *msg)
{
msg->cmd = 8;
write(socketfd, msg, sizeof(struct Msg));
display_line();
printf("\t\t正在退出...\n");
}
//超級使用者
void superusr(int socketfd, struct Msg *msg)
{
char ch[SIZE];
int x;
while (1)
{
supinterface(msg);
scanf("%s",ch);
while(getchar() != '\n');
switch(ch[0])
{
case '1': // 檢視線上使用者
see_online(socketfd, msg);
break;
case '2': // 群發訊息
chat_group(socketfd, msg);
break;
case '3': // 傳送悄悄話
chat_private(socketfd, msg);
break;
case '4': // 刪除聊天記錄
del_personsql();
break;
case '5': // 檢視聊天記錄
see_record();
break;
case '6': // 設定禁言
forbid_speak(socketfd, msg);
break;
case '7': // 解除禁言
relieve_speak(socketfd, msg);
break;
case '8': // 退出當前賬號
off_line(socketfd, msg);
return;
case '9': // 踢出聊天室
kickout_room(socketfd, msg);
break;
default:
display_line();
printf("錯誤指令,請重新輸入\n");
sleep(1);
break;
}
}
}
//下載檔案
void download_file(int socketfd, struct Msg *msg)
{
msg->cmd = 9;
printf("\t\t請輸入要下載的檔名:\n");
fgets(msg->msg, 1024, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
}
void fun(int socketfd, struct Msg *msg)
{
printf("\n\t\t正在下載,請稍後...\n");
int fd = open(msg->msg, O_RDWR | O_CREAT, 0777);
if(fd == -1)
{
perror("open");
printf("檔案下載失敗了\n");
}
int size = msg->filesize;
char buf[65535];
memset(buf, 0, 65535);
int ret = read(socketfd, buf, size);
if(ret == -1)
{
perror("read");
return;
}
write(fd, buf, ret);
close(fd);
sleep(1);
printf("\t\t檔案下載完成\n");
}
//普通使用者
void commonusr(int socketfd, struct Msg *msg)
{
char ch[SIZE];
int x;
while (1)
{
cominterface(msg);
//scanf("%s",ch);
//while(getchar() != '\n');
fgets(ch, SIZE, stdin);
if(offline == 1)
{
display_line();
printf("\n\t\t請稍後...\n");
off_line(socketfd, msg);
return;
}
else
{
switch(ch[0])
{
case '1': // 檢視線上使用者
see_online(socketfd, msg);
break;
case '2': // 群發訊息
chat_group(socketfd, msg);
break;
case '3': // 傳送悄悄話
chat_private(socketfd, msg);
break;
case '4': // 刪除聊天記錄
del_personsql();
break;
case '5': // 檢視聊天記錄
see_record();
break;
case '6': // 上傳檔案
send_file(socketfd, msg);
break;
case '7': // 登出當前使用者
logout(socketfd, msg);
return;
case '8': // 退出當前賬號
off_line(socketfd, msg);
return;
case '9':
download_file(socketfd, msg); // 下載檔案
break;
default:
display_line();
printf("錯誤指令,請重新輸入\n");
sleep(1);
break;
}
}
}
}
//建立聊天記錄資料庫
void set_mysql()
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("開啟資料庫失敗\n");
return;
}
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("聊天記錄表建立失敗:%s\n", errmsg);
return;
}
sqlite3_close(database);
return;
}
//註冊函式
void reg(int socketfd)
{
struct Msg msg;
memset(&msg, 0, sizeof(msg));
msg.cmd = 1;
display_line();
printf("\t\t請輸入姓名:");
scanf("%s",msg.name);
while(getchar() != '\n');
printf("\n");
printf("\t\t請輸入密碼:");
scanf("%s",msg.password);
while(getchar() != '\n');
write(socketfd, &msg, sizeof(msg));
read(socketfd, &msg, sizeof(msg));
if(msg.cmd == 0)
{
display_line();
printf("\t\t註冊失敗,該使用者已被註冊\n");
}
if(msg.cmd == 1)
{
display_line();
set_mysql(); //註冊成功就建立一個使用者自己的資料庫存放在本地,存放聊天記錄
printf("\t\t註冊成功\n");
}
sleep(2);
return;
}
//專門用來讀取伺服器
void * readMsg(void *v)
{
int socketfd = (int)v;
struct Msg msg;
while (1)
{
int i;
int ret = read (socketfd, &msg, sizeof(msg));
switch (msg.cmd)
{
case 1: //顯示線上使用者
printf("\n\t\t當前線上戶:\n");
printf("\t\t");
for(i=0; i<SIZE; i++)
{
printf("%-5s",msg.usr[i].name);
}
printf("\n");
break;
case 2: // 接受一條群聊資訊
printf ("\n%s給大家發了一條訊息: %s\n", msg.name, msg.msg);
insert_mysql(&msg); //儲存聊天資訊
break;
case 3: // 接受一條私聊資訊
printf ("\n%s給你發一條訊息:%s\n", msg.fromname, msg.msg);
insert_mysql(&msg);
break;
case 6: // 收到有人上傳檔案的訊息
printf("\n使用者%s上傳了一份檔案%s\n", msg.fromname, msg.msg);
break;
case 7:
return;
case 8:
return;
case 9:
fun(socketfd, &msg);
break;
case 1003:
//display_line();
printf("\n\t\t您已被管理員禁言\n");
//sleep(1);
break;
case 1004:
printf("\n您已被管理員解除禁言\n");
break;
case 1005:
printf("\n使用者%s已被管理員踢出聊天室\n",msg.msg);
break;
case 1006:
display_line();
printf("\n\t\t您已被管理員踢出聊天室\n");
printf("\t\t按ENTER鍵返回主選單\n");
offline = 1;
//off_line(socketfd, msg);
break;
}
}
}
//登陸函式
void login(int socketfd)
{
struct Msg msg;
memset(&msg, 0, sizeof(msg));
msg.cmd = 2;
display_line();
printf("\t\t請輸入姓名:");
scanf("%s",msg.name);
while(getchar() != '\n');
printf("\n");
printf("\t\t請輸入密碼:");
scanf("%s",msg.password);
while(getchar() != '\n');
write(socketfd, &msg, sizeof(msg));
read(socketfd, &msg, sizeof(msg));
printf("%d\n",msg.cmd);
if(msg.cmd == 0)
{
display_line();
printf("\t\t正在登陸...\n");
sleep(2);
display_line();
printf("\t\t登陸成功\n");
//啟動一個執行緒專門用來接受聊天資訊
pthread_t id;
pthread_create(&id, NULL, readMsg, (void *)socketfd);
pthread_detach(id);
offline = 0;
if(msg.flag == 1) //超級使用者
superusr(socketfd, &msg);
if(msg.flag == 0) //普通使用者
commonusr(socketfd, &msg);
}
if(msg.cmd == 1)
{
display_line();
printf("\t\t正在登陸...\n");
sleep(2);
display_line();
printf("\t\t密碼錯誤\n");
}
if(msg.cmd == 2)
{
display_line();
printf("\t\t正在登陸...\n");
sleep(2);
display_line();
printf("\t\t使用者不存在,請先註冊\n");
}
if(msg.cmd == 3)
{
display_line();
printf("\t\t正在登陸...\n");
sleep(2);
display_line();
printf("\t\t該使用者已經登陸,請勿重複登陸\n");
}
sleep(1);
return;
}
// 客戶端向伺服器傳送資料
void ask_server(int socketfd)
{
int x;
while (1)
{
maininterface();
scanf("%d",&x);
while(getchar() != '\n');
switch(x)
{
case 1: // 註冊
reg(socketfd);
break;
case 2: // 登陸
login(socketfd);
break;
case 3: // 退出
system("clear");
return;
default:
display_line();
printf("\t\t錯誤指令,請重新輸入\n");
sleep(1);
break;
}
}
}
int main()
{
// 建立與伺服器通訊的套接字
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));
// 成功的情況下,可以通過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;
}
六、專案演示效果