聊天室项目

基于c/s架构的大并发多人聊天室

基础功能:注册、登录、修改密码、私聊、群聊,发送文件。
项目模块:1、采用C/S架构,利用TCP/IP协议进行通信,通过多线程实现了服务器与多个客户端之间的通信
2.采用轻量级数据库Sqlite3在服务器端存储所有用户注册时的昵称、账号、密码
3. 采用在线链表在服务器端记录存储所有用户登录时的个人基本信息,方便管理。

服务器端:在这里插入代码片#include <stdio.h> //服务器
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include <sqlite3.h>
#include <string.h>
#include <unistd.h> //read write函数
#define PORT 8888 //1024以下端口号不能使用
#define MYADDR “192.168.12.13”

int flag = 0;

struct user
{
char name[20];
char password[20];
int account;
struct user *next;
};
struct user *head = NULL;
struct online
{
int c_fd;
int account;
char name[20];
struct online *next;
};

struct mess //聊天结构体
{
int toAccount; //发送人账号
char message[1024]; //聊天信息
int flag; // 0为私人聊天,1为群聊,2发送文件
char name[20]; //接收人的昵称
};

struct mess_cfd //cfd和消息包
{
struct mess *msg;
int cfd;
int i;
};

struct online *head1 = NULL;
void createtable()
{
sqlite3 *db = NULL;
int ret = sqlite3_open(“info.db”, &db); //打开数据库
if (SQLITE_OK != ret)
{
perror(“sqlite3_open:”);
exit(1);
}
char *errmsg;
char sql[500] = “\0”; //写入命令
strcpy(sql, “create table user(name text,account integer,password text);”);
ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
if (SQLITE_OK != ret)
{
perror(“sqlite3_exec:”);
printf(“errmsg:%s\n”, errmsg);
exit(2);
}
ret = sqlite3_close(db); //关闭数据库
if (SQLITE_OK != ret)
{
perror(“sqlite3_close:”);
exit(3);
}
return;
}

int searchtable(char *tablename)
{
sqlite3 *db = NULL;
int ret = sqlite3_open(“info.db”, &db); //打开数据库
if (SQLITE_OK != ret)
{
perror(“sqlite3_open:”);
exit(1);
}
char sql[200] = “\0”;
sprintf(sql, “select name from sqlite_master where type=‘table’ AND name=’%s’;”, tablename);
char *errmsg;
char **result;
int row, column;
ret = sqlite3_get_table(db, sql, &result, &row, &column, &errmsg); //分别为文件描述符,数据库命令,查询结果,行,列,错误信息
if (SQLITE_OK != ret)
{
perror(“sqlite3_exec:”);
printf(“errmsg:%s\n”, errmsg);
exit(2);
}
if (row >= 1)
{
return 1;
}
else
{
return 0;
}
sqlite3_free_table(result); //释放内存
ret = sqlite3_close(db); //关闭数据库
if (SQLITE_OK != ret)
{
perror(“sqlite3_close:”);
exit(3);
}
}

void initdb()
{
printf(“正在进行初始化!\n”);
if (searchtable(“user”) == 0)
{
createtable();
}
}

void carryout(char *sql)
{
sqlite3 *db = NULL;
int ret = sqlite3_open(“info.db”, &db); //打开数据库
if (SQLITE_OK != ret)
{
perror(“sqlite3_open:”);
exit(1);
}
char *errmsg;
ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
if (SQLITE_OK != ret)
{
perror(“sqlite3_exec1:”);
printf(“errmsg:%s\n”, errmsg);
exit(2);
}
ret = sqlite3_close(db); //关闭数据库
if (SQLITE_OK != ret)
{
perror(“sqlite3_close:”);
exit(3);
}
}

void updatedb(char *name, char *pw) //插入,修改数据库注册用户信息
{
char sql[500] = “\0”;
sprintf(sql, “update user set password=’%s’ where name=’%s’;”, pw, name); //插入,修改,删除都可以用这条语句
carryout(sql);
}

void insertdb(struct user msg)
{
char sql[500] = “\0”;
sprintf(sql, “insert into user values(’%s’,%d,’%s’);”, msg.name, msg.account, msg.password); //插入,修改,删除都可以用这条语句
carryout(sql);
}
void init()
{
struct user *newnode = (struct user *)malloc(sizeof(struct user));
newnode->next = NULL;
memset(newnode->name, 0, 20);
memset(newnode->password, 0, 20);
newnode->account = 0;
head = newnode;
}

void init1() //在线用户表初始化
{
struct online *newnode = (struct online *)malloc(sizeof(struct online));
newnode->next = NULL;
newnode->c_fd = 0;
strcpy(newnode->name, “\0”);
newnode->account = 0;
head1 = newnode;
}

int insert_tail(struct user msg1) //注册的用户写到链表
{
struct user *ptr = head;
struct user *newnode = (struct user *)malloc(sizeof(struct user));
if (NULL == newnode)
{
return -1;
}
newnode->account = msg1.account;
strcpy(newnode->name, msg1.name);
strcpy(newnode->password, msg1.password);

newnode->next = NULL;
while (ptr->next != NULL)
{
    ptr = ptr->next;
}
ptr->next = newnode;
printf("%s\n%s\n",msg1.name,msg1.password);
return 0;

}

int insert_tail1(struct online msg1) //在线
{
struct online *ptr = head1;
struct online *newnode = (struct online *)malloc(sizeof(struct online));
if (NULL == newnode)
{
return -1;
}
newnode->account = msg1.account;
newnode->c_fd = msg1.c_fd;
strcpy(newnode->name, msg1.name);

newnode->next = NULL;
while (ptr->next != NULL)
{
    ptr = ptr->next;
}
ptr->next = newnode;
return 0;

}

int search_value(int account) //查找账号是否重复
{
struct user *ptr = head;
while (ptr->next != NULL)
{
if (ptr->next->account == account)
{
return 1;
}
ptr = ptr->next;
}
return 0;
}
struct user search_password(struct user msg1) //查找密码
{
struct user *ptr = head;
while (ptr->next != NULL)
{
if (ptr->next->account == msg1.account)
{
if (strcmp(ptr->next->password, msg1.password) == 0)
{
strcpy(msg1.name, ptr->next->name);
return msg1;
}
else
{
strcpy(msg1.password, “\0”);
return msg1;
}
}
ptr = ptr->next;
}
strcpy(msg1.name, “\0”);
return msg1;
}

int search_cfd(int account) //在线用户通过账号查找cfd
{
struct online *ptr = head1;
while (ptr->next != NULL)
{
if (ptr->next->account == account)
{
return ptr->next->c_fd;
}
ptr = ptr->next;
}
return 0;
}
struct online search_cfd1(int c_fd)
{
struct online *ptr = head1;
while (ptr->next != NULL)
{
if (ptr->next->c_fd == c_fd)
{
return *ptr->next;
}
ptr = ptr->next;
}
return *head1;
}
struct user search_root(int account) //用账号查询链表里保存的账号
{
struct user *ptr = head;
while (ptr->next != NULL)
{
if (ptr->next->account == account)
{
return *ptr->next;
}
ptr = ptr->next;
}
return *head;
}

void downdb() //把已经注册的用户载入链表
{
//printf(“进入downdb\n”);
sqlite3 *db = NULL;
int ret = sqlite3_open(“info.db”, &db); //打开数据库
//printf(“打开数据库\n”);
if (SQLITE_OK != ret)
{
perror(“sqlite3_open:”);
exit(1);
}
const char *sql = “select *from user;”; //写入命令
//printf(“写入命令\n”);
char *errmsg;
char **result;
int row = 0, column = 0;
ret = sqlite3_get_table(db, sql, &result, &row, &column, &errmsg); //分别为文件描述符,数据库命令,查询结果,行,列,错误信息
if (SQLITE_OK != ret)
{
perror(“sqlite3_exec:”);
printf(“errmsg:%s\n”, errmsg);
exit(2);
}
printf(“row=%d column=%d\n”, row, column);
if (row == 0)
{
return;
}
int i, j;
int num;
struct user msg;
// char name[20] = “\0”;
// int account;
// char password[20] = “\0”;
printf(“开始载入\n”);
for (i = 1; i <= row; i++)
{
for (j = 0; j < column; j++)
{
num = i * column + j;
//printf("%s\n",result[num]); //查询到的信息按一维数组的方式储存,0到2为id,name,age这些字段,3到5为第一行数据,6到8为第二行数据
if (j == 0)
{
strcpy(msg.name, result[num]);
}
else if (j == 1)
{
msg.account = atoi(result[num]);
}
else
{
strcpy(msg.password, result[num]);
}
}
insert_tail(msg);
}
sqlite3_free_table(result); //释放内存
//printf(“释放内存\n”);
ret = sqlite3_close(db); //关闭数据库
//printf(“关闭数据库\n”);
if (SQLITE_OK != ret)
{
perror(“sqlite3_close:”);
exit(3);
}
return;
}

int register1(int c_fd)
{
struct user msg1;
read(c_fd, &msg1, sizeof(msg1));
while (1)
{
srand(time(NULL));
msg1.account = rand() % 100000; //放个检测函数
if (search_value(msg1.account) == 0)
{
break;
}
}
printf("%d\n", msg1.account);
write(c_fd, &msg1, sizeof(msg1));
insert_tail(msg1);//写到链表里
insertdb(msg1); //写到数据库里
}

int login(int c_fd)//登录
{
struct user msg1;
read(c_fd, &msg1, sizeof(msg1));
msg1 = search_password(msg1);
write(c_fd, &msg1, sizeof(msg1));
struct online msg;
msg.c_fd = c_fd;
msg.account = msg1.account;
strcpy(msg.name, msg1.name);
insert_tail1(msg);
if (strcmp(msg1.name, “\0”) && strcmp(msg1.password, “\0”))
{
return 1;
}
return 0;
}
int i = 0;
sem_t sem[1024];
int ff[1024] = {0};
void *rec(void *arg) //发送
{
struct mess_cfd msg = *(struct mess_cfd *)arg;
int cfd = msg.cfd;
int c = msg.i;
struct mess *msg1 = msg.msg;
while (1)
{
if(ff[c] == 0)
{
read(cfd, msg1, sizeof(struct mess));

        if(strcmp(msg1->message,"\0")!=0)
        {
            printf("发送给%d的消息为%s", msg1->toAccount, msg1->message);
             ff[c] = 1;//在发送完之前,把接收函数阻塞住
            sem_post(&sem[c]);
        }
        else
        {
            close(cfd);
            break;
        }
    }
}

}

int send1(int c_fd, int c)
{
struct mess msg;
struct mess_cfd msg1;
msg1.msg = &msg;
msg1.cfd = c_fd;
msg1.i = c;
ff[c]=0;
//每个客户端登录用户的账号和名字
struct online a = search_cfd1(c_fd);
pthread_t pid;
pthread_create(&pid, NULL, rec, (void *)&msg1);
while (1)
{
sem_wait(&sem[c]); //等待接收函数松开阻塞

    if (msg.flag == 1)        //私聊
    {
        int to_cfd = search_cfd(msg.toAccount);
        if (to_cfd != 0)
        {
            strcpy(msg.name, a.name);
            msg.toAccount = a.account;
            write(to_cfd, &msg, sizeof(struct mess));
        }
    }
   else if (msg.flag == 2)        //群聊
    {
        struct online *ptr = head1;
        strcpy(msg.name, a.name);
        msg.toAccount = a.account;
        while (ptr->next != NULL)
        {
            ptr = ptr->next;
            if (ptr->c_fd != c_fd)
            {
                write(ptr->c_fd, &msg, sizeof(struct mess));
            }
        }
    }
    memset(&msg, 0, sizeof(struct mess));
    ff[c] = 0;//松开接收函数的阻塞
    sem_init(&sem[i], 0, 0);//再把自己阻塞

    if (msg.flag == 3)  //设置管理员用户
    {
        read(c_fd,&msg,sizeof(struct mess));
        msg.flag == 10;          //10-15位管理员功能
        write(c_fd,&msg,sizeof(struct mess));
        
    }
    
}

}

void *r_or_l(void *arg)
{
int c_fd = *(int *)arg;
int k;
while (1)
{
read(c_fd, &k, sizeof(k));
if (k == 1)
{
register1(c_fd);
}
else if (k == 2)
{
int flag = login(c_fd);
if (flag == 1)
{
printf(“登录成功\n”);
sem_init(&sem[i], 0, 0);
i++;
send1(c_fd, i - 1); //发送消息
}
}
// else if(k == 3)
// {
// updatepw(c_fd);
// }
else
{
close(c_fd);
pthread_exit(NULL);
}
}
}

int main()
{
int i = 0;
int s_fd, c_fd;
s_fd = socket(AF_INET, SOCK_STREAM, 0); //IPV4协议,数据流,阻塞型
if (s_fd == -1)
{
perror(“scoket:”);
exit(1);
}
struct sockaddr_in s_addr;
int addrlen = sizeof(struct sockaddr_in);
memset(&s_addr, 0, addrlen); //将结构体情况,防止再次使用时端口号被占用等等问题
s_addr.sin_family = AF_INET; //协议族
s_addr.sin_port = htons(PORT); //将小端本地数据转化为大端本地数据 0x12345678 大端 0x12345678 小端 0x78563412
s_addr.sin_addr.s_addr = inet_addr(MYADDR); //将IP转化成二进制还是十六进制?
int opt = 1;
setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //解决端口被占用的问题
int ret = bind(s_fd, (struct sockaddr *)&s_addr, addrlen); //第二个参数要进行强制类型转换
if (ret == -1)
{
perror(“bind:”);
exit(0);
}
else
{
printf(“绑定成功\n”);
}
ret = listen(s_fd, 10); //监听客户端
if (ret == -1)
{
perror(“listen:”);
exit(-1);
}
else
{
printf(“监听成功\n”);
}
struct sockaddr_in c_addr;
int c_addrlen = sizeof(struct sockaddr_in);
memset(&c_addr, 0, c_addrlen);
init(); //初始化链表所有用户
init1(); //初始化在线用户
initdb(); //初始化数据库
downdb(); //数据库的东西下载到链表里
while (1)
{
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &c_addrlen); //与bind相似,因为accept是阻塞型函数,如果放在循环外,就只能接收一个客户端
if (c_fd == -1)
{
perror(“accept:”);
exit(-2);
}
else
{
printf(“接收成功\n”); //因为使用了阻塞型函数,所以如果没有接收到客户端的信息就强制退出,再次运行会显示端口被占用
}

    pthread_t ptid; //登录注册
    pthread_create(&ptid, NULL, r_or_l, (void *)&c_fd);
}

}



客户端:比较简单,直接上代码

#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 PORT 8888
#define MYADDR “192.168.12.13”

struct user //用户登录结构体
{
char name[20];
char password[20];
int account;
};

struct mess //聊天结构体
{
int toAccount; //发送人账号
char message[1024]; //聊天信息
int flag; // 0为私人聊天,1为群聊,2发送文件
char name[20]; //接收人的昵称
};

int register1(int c_fd)
{
struct user msg;
printf(“请输入你的昵称\n”);
scanf("%s", msg.name);
printf(“请输入你的密码:\n”);
scanf("%s", msg.password);
write(c_fd, &msg, sizeof(msg));
read(c_fd, &msg, sizeof(msg));
printf(“注册成功!\n”);
printf(“这是你的账号:%d,用户名为:%s\n”, msg.account, msg.name);
printf("%s\n",msg.password);
return 0;
}

int login(int c_fd)
{
while (1)
{
struct user msg;
printf(“请输入你的账号\n”);
scanf("%d", &msg.account);
printf(“请输入你的密码:\n”);
scanf("%s", msg.password);
write(c_fd, &msg, sizeof(msg));
memset(&msg,0,sizeof(struct user));
msg.account=0;
while(1)
{
read(c_fd, &msg, sizeof(msg));
if(msg.account!=0)
{
break;
}
}
if (strcmp(msg.name, “\0”) != 0 && strcmp(msg.password, “\0”) != 0)
{
printf(“登录成功!\n”);
break;
}
}
}

void *rec(void *arg)
{
int cfd = *(int *)arg;
while (1)
{
struct mess msg;
read(cfd, &msg, sizeof(msg));
if (msg.message[0] != ‘\0’)
{
printf(“接收到了%s的消息为%s\n”, msg.name, msg.message);
}
}
}

void update_pw(int c_fd,struct user *msg)
{
char pw[20] = {0}; //旧密码以及新密码
char pw1[20] = {0}; //第二次确认密码
printf(“请输入你的旧密码:\n”);
scanf("%s",pw);
printf(“旧密码:%s”,msg->password);
printf(“pw的密码:%s”,pw);
if(strcmp(pw,msg->password) == 0)
{
printf(“密码正确\n”);
memset(pw,0,20);
printf(“请输入新密码:\n”);
scanf("%s",pw);
printf(“请再次输入新密码密码:\n”);
scanf("%s",pw1);
if(strcmp(pw,pw1) == 0 )
{
printf(“修改成功\n”);

    }
    else 
    {
        printf("修改失败!\n");
    }
}

}

int send1(int c_fd)
{
struct mess msg;
pthread_t pid;
pthread_create(&pid, NULL, rec, (void *)&c_fd);
int k = 0;
while (1)
{
printf(“请输入你需要的功能:\n”);
printf("\t1.私密聊天\t2.群聊\n");
printf("\t3.申请管理员\t4.修改密码\n");
printf("\t5.发送文件\t6.退出\n");
scanf("%d", &k);
if (k == 1)
{
msg.flag = 1;
printf(“请输入你聊天对象的账号(bye退出私聊):\n”);
scanf("%d", &msg.toAccount);
while (1)
{
printf(“请输入对话信息:\n”);
scanf("%s", msg.message);
write(c_fd, &msg, sizeof(msg));
if (strcmp(msg.message, “bye”) == 0)
{
break;
}
}
}
else if (k == 2)
{
msg.flag = 2;
while (1)
{
printf(“请输入对话信息:\n”);
scanf("%s", msg.message);
write(c_fd, &msg, sizeof(msg));
if (strcmp(msg.message, “bye”) == 0)
{
break;
}
}
}
else if (k == 3)
{
msg.flag = 3;
write(c_fd,&msg,sizeof(struct mess));
while(1)
{
printf(“请输入你想设置的管理员用户id(退出本次服务输入bye):\n”); //id号永远唯一
scanf("%d",&msg.toAccount);
scanf("%s",msg.message);
read(c_fd,&msg,sizeof(msg));
while(msg.flag == 10)
{
printf(“设置成功,%d用户以成为管理员!\n”,msg.toAccount);
}
}
}
else if (k == 4)
{
msg.flag = 4;
write(c_fd,&msg.flag,sizeof(msg.flag));
//update_pw(c_fd,&msg);
}
else if (k ==0)
{
printf(“欢迎下次使用\n”);
system(“pause”);
return 0;
break;
}
else
{
printf(“输入有误,请重新输入:\n”);
system(“pause”);
system(“clear”);
break;
}
}
return 1;
}

int firstMenu(int c_fd)
{
while (1)
{
int k;
printf(“请输入你需要的功能”);
printf(“欢迎来到冬瓜皮聊天室!\n”);
printf("\t1.注册账号 2.登录账号\t\n");
printf("\t3.修改密码 \t4.退出\t\n");
scanf("%d", &k);
write(c_fd, &k, sizeof(k));
if (k == 1)
{
register1(c_fd);
}
else if (k == 2)
{
login(c_fd);
send1(c_fd);
}
/else if(k == 3)
{
updatepw(c_fd);
}
/
else
{
exit(1);
}
}
}
int main()
{
int c_fd;
c_fd = socket(AF_INET, SOCK_STREAM, 0); //IPV4协议,数据流,阻塞型
if (c_fd == -1)
{
perror(“socket:”);
exit(2);
}
struct sockaddr_in c_addr;
int addrlen = sizeof(struct sockaddr_in);
memset(&c_addr, 0, addrlen); //将结构体情况,防止再次使用时端口号被占用等等问题
c_addr.sin_family = AF_INET; //协议族
c_addr.sin_port = htons(PORT); //将小端本地数据转化为大端本地数据 0x12345678 大端 0x12345678 小端 0x78563412
c_addr.sin_addr.s_addr = inet_addr(MYADDR); //将IP转化成二进制还是十六进制?
int ret = connect(c_fd, (struct sockaddr *)&c_addr, addrlen); //第二个参数要进行强制类型转换
if (ret == -1)
{
perror(“connect:”);
exit(3);
}
else
{
printf(“连接成功\n”);
}
//sem_init(&sem1,0,0);
firstMenu(c_fd);
close(c_fd);
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值