Linux下C語言專案—聊天室的搭建1.0
阿新 • • 發佈:2019-01-27
之所以為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(<);
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(<);
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;
}
之後會不斷修改新增功能。