struct.h
#ifndef _STRUCT_H_//伺服器端頭檔案
#define _STRUCT_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sqlite3.h>
#define PORT 9999
char myName[20];
sqlite3 * database;
struct Msg
{
int cmd; // 消息類型
char fromname[20];
char password[10];
char tag[50];
char toname[20];
char msg[1024]; // 消息内容
};
struct online
{
int sfd;
int on_flag;
int speek_flag;
char name[20];
struct online *next;
};
struct online *header;
#endif
server.c
#include "struct.h"
struct online *header = NULL;
// 初始化套接字,傳回監聽套接字
int init_socket()
{
int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == -1)
{
perror ("socket");
return -1;
}
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;
}
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; // 用來儲存用戶端的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;
}
//注冊
int reg(int client_socket, struct Msg *msg)
{
printf ("%s進行注冊\n", msg->fromname);
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char buf[100];
sprintf(buf,"select * from user where name = '%s';",msg->fromname);
int ret = sqlite3_get_table(database, buf, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
return -1;
}
//使用者名已存在
if( nrow != 0 )
{
printf("該使用者名已存在!\n");
msg->cmd = 1001;
write (client_socket, msg, sizeof(struct Msg));
return 0;
}
else
{
printf("使用者名不存在,可以注冊!\n");
char tag[20] = "emmmmm.....";
strcpy( msg->tag, tag );
//向資料庫中插入使用者資訊
sprintf (buf, "insert into user values('%s', '%s', '%s')", msg->fromname, msg->password, msg->tag);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
msg->cmd = 1002;
write (client_socket, msg, sizeof(struct Msg));
return -1;
}
msg->cmd = 1003;
printf("注冊成功!\n");
write (client_socket, msg, sizeof(struct Msg));
}
sqlite3_free_table(resultp);
}
//登入
int login(int client_socket, struct Msg *msg)
{
printf ("%s進行登入\n", msg->fromname);
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char buf[100];
sprintf(buf,"select * from user where name = '%s';",msg->fromname);
int ret = sqlite3_get_table(database, buf, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
return -1;
}
if ( nrow == 0 )
{
printf ("該使用者名不存在!\n");
msg->cmd = 2001;
write (client_socket, msg, sizeof(struct Msg));
}
else
{
//密碼正确
if ( strcmp ( msg->password, resultp[4] ) == 0 )
{
//檢查使用者是否線上
struct online *temp = header;
while (temp != NULL)
{
//使用者已線上
if (strcmp (temp->name, msg->fromname) == 0)
{
if (temp->on_flag == 1)
{
msg->cmd = 2002;
printf ("使用者已線上\n");
write (client_socket, msg, sizeof(struct Msg));
return 0;
}
}
temp = temp->next;
}
//使用者不線上,儲存使用者資訊
struct online *node = (struct online *)malloc(sizeof(struct online));
node->sfd = client_socket;
node->on_flag = 1;
node->speek_flag = 1;
strcpy (node->name, msg->fromname);
strcpy (msg->tag, resultp[5]);
//将使用者資訊頭插在線上連結清單中
node->next = header;
header = node;
msg->cmd = 2003;
printf ("登入成功!\n");
write (client_socket, msg, sizeof(struct Msg));
return 0;
}
printf ("密碼輸入錯誤!\n");
msg->cmd = 2004;
write (client_socket, msg, sizeof(struct Msg));
}
sqlite3_free_table (resultp);
return 0;
}
// 私聊
void priva(int client_socket, struct Msg *msg)
{
printf ("%s要給%s發一條消息\n", msg->fromname, msg->toname);
//周遊線上使用者連結清單
struct online *temp = header;
while (temp != NULL)
{
//找到要發送的對象
if (strcmp (temp->name, msg->toname) == 0)
{
msg->cmd = 3001;
write (temp->sfd, msg, sizeof(struct Msg));
printf ("找到要發送的對象,消息發送成功!\n");
msg->cmd = 3002;
write (client_socket, msg, sizeof(struct Msg));
return ;
}
temp = temp->next;
}
printf ("沒有找到要發送的對象!\n");
msg->cmd = 3003;
write (client_socket, msg, sizeof(struct Msg));
}
// 群發
void group(int client_socket, struct Msg *msg)
{
printf ("%s發一次群消息\n", msg->fromname);
//周遊線上使用者連結清單
struct online *temp = header;
while (temp != NULL)
{
msg->cmd = 4001;
write (temp->sfd, msg, sizeof(struct Msg));
temp = temp->next;
}
msg->cmd = 4002;
printf ("群發消息成功!\n");
write (client_socket, msg, sizeof(struct Msg));
}
//檢視線上使用者
void check(int client_socket, struct Msg *msg)
{
printf ("%s檢視線上使用者\n", msg->fromname);
char buf[300] = {0};
int len;
//周遊線上使用者連結清單
struct online *temp = header;
while (temp != NULL)
{
strcat ( buf, temp->name );
len = sizeof(buf);
buf[len] = ' ';
temp = temp->next;
}
strcpy ( msg->msg, buf );
printf ("線上使用者有:%s\n",buf);
msg->cmd = 5001;
printf ("檢視線上使用者成功!\n");
write (client_socket, msg, sizeof(struct Msg));
}
//修改密碼
int changepwd(int client_socket, struct Msg *msg)
{
printf ("%s修改密碼\n", msg->fromname);
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char buf[100];
sprintf(buf,"select * from user where name = '%s';",msg->fromname);
int ret = sqlite3_get_table(database, buf, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
return -1;
}
//密碼正确
if ( strcmp ( msg->password, resultp[4] ) == 0 )
{
char buf[100];
sprintf (buf, "update user set password = '%s' where name = '%s'", msg->msg, msg->fromname);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
msg->cmd = 6001;
write (client_socket, msg, sizeof(struct Msg));
return -1;
}
msg->cmd = 6002;
printf("修改密碼成功!\n");
write (client_socket, msg, sizeof(struct Msg));
}
else
{
msg->cmd = 6003;
printf("密碼錯誤!\n");
write (client_socket, msg, sizeof(struct Msg));
}
}
//修改個性簽名
int changetag(int client_socket, struct Msg *msg)
{
printf ("%s修改個性簽名\n", msg->fromname);
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char buf[100];
sprintf(buf,"select * from user where name = '%s';",msg->fromname);
int ret = sqlite3_get_table(database, buf, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
return -1;
}
sprintf (buf, "update user set tag = '%s' where name = '%s'", msg->tag, msg->fromname);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
msg->cmd = 7001;
write (client_socket, msg, sizeof(struct Msg));
return -1;
}
msg->cmd = 7002;
printf ("修改個性簽名成功!\n");
write (client_socket, msg, sizeof(struct Msg));
}
int clientexit(int client_socket, struct Msg *msg)
{
printf("%s要退出聊天室\n",msg->fromname);
struct online *temp = header;
struct online *prev = NULL;
while (temp != NULL)
{
//找到線上使用者節點
//如果是第一個節點
if (strcmp (temp->name, msg->fromname) == 0)
{
header = header->next;
free(temp);
temp = NULL;
msg->cmd = 1301;
printf ("删除線上使用者節點成功!\n");
write (client_socket, msg, sizeof(struct Msg));
return 0;
}
else
{
prev = temp;
temp = temp->next;
if (strcmp (temp->name, msg->fromname) == 0)
{
prev->next = temp->next;
free(temp);
temp = NULL;
msg->cmd = 1301;
printf ("删除線上使用者節點成功!\n");
write (client_socket, msg, sizeof(struct Msg));
return 0;
}
}
}
msg->cmd = 1302;
printf ("沒有找到該使用者節點!\n");
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 : // 私聊
priva(client_socket, &msg);
break;
case 4 : // 群聊
group(client_socket, &msg);
break;
case 5 : // 檢視線上使用者
check(client_socket, &msg);
break;
case 6: // 修改密碼
changepwd(client_socket, &msg);
break;
case 7: // 修改個性簽名
changetag(client_socket, &msg);
break;
case 13: // 用戶端退出
clientexit(client_socket, &msg);
break;
}
}
close (client_socket);
}
int main()
{
int listen_socket = init_socket();
// 打開資料庫
int ret = sqlite3_open("user.db", &database);
if (ret != SQLITE_OK)
{
printf ("打開資料庫失敗\n");
return -1;
}
printf ("打開資料庫成功\n");
char *errmsg = NULL;
char *sql = "create table if not exists user ( name TEXT, password TEXT, tag TEXT, primary key(name) )";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("資料庫操作失敗:%s\n", errmsg);
return -1;
}
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);
sqlite3_close(database);
return 0;
}