基于TCP的网络聊天软件

本文介绍了一款基于Linux的聊天软件项目,包括服务器和客户端的实现。服务器采用select编程实现多客户端通信,使用SQLite3存储用户信息。项目具备注册、登录、修改密码、私聊、群聊等功能,并支持表情发送、文件传输、在线人数查看等特性。管理员还可禁言、强制下线用户。

这个聊天软件运行在linux上。整个项目有两大部分:服务器和客户端
服务器采用select编程实现与多个客户端的通信。 服务器使用了sqlite3 对客户的信息进行存储。
整个项目实现了这些功能:
注册、登录、修改密码、悄悄话、群体喊话、查看在线人数、发送表情、发送文件、成为管理员。管理员还可以禁言,强制下线。
废话不多说,接下来是代码:

chat.h 这个头文件中包函数服务器和客户端之间通行所使用的的数据包的格式。

#ifndef CHAT_H
#define CHAT_H

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>


////////数据库名/////////////////////
#define DATABASE   "chat.db"
/////////////////////////////////////

/////最大在线客户端数
#define SIZE		1024


//定义函数返回值
#define SUCCESS  10001
#define FAILURE  10002
#define TRUE     10003
#define FALSE    10004


//数据包中command(命令)参数的值
#define REGISTER  	0		//注册
#define LOGIN     	1		//登录
#define FORGET	  	2		//忘记
#define PRIVATE   	3		//私聊
#define GROUP	  	4		//群聊
#define ONLINENUM 	5		//在线人数
#define EXIT        6       //退出
#define	LOGOUT      7       //下线
#define VIP         8       //vip
#define FORBID      9       //禁言
#define RELIEVE     10      //解除禁言
#define KICK        11      //踢人
#define EMAIL       12      //匹配邮箱
#define FORCE_EXIT  13      //强制退出
#define FILE_NAME   14      //发送文件名
#define FILE_BUF    15      //发送文件




//数据包中result(结果)参数的值
#define RET_SUCCESS		2000
#define RET_EXIST		2001	//用户已经存在(用于注册)
#define RET_NOT_EXIST   2002	//用户不存在(用于登录)
#define RET_PASS_ERROR  2003 	//密码错误(用于登录)
#define RET_OFFLINE	    2004    //下线
#define RET_HAVE_ONLINE   2005    //用户已经上线
#define RET_EMAIL_SUCC  2006    //邮箱匹配
#define RET_EMAIL_FAIL  2007    //邮箱不匹配
#define RET_FAILURE     2008   




//数据包中retcommand(返回命令)参数的值(用于客户端接收)
#define RETURN_CHAT		   3000     //返回聊天
#define RETURN_ONLINENUM   3001     //返回在线人数 
#define RETURN_GROUP       3002     //返回群聊
#define RETURN_FORBID      3003     //返回禁言
#define RETURN_RELIEVE     3004     //返回解除禁言
#define RETURN_KICK        3005     //返回踢人
#define RETURN_FILE_NAME   2009     //返回文件名
#define RETURN_FILE_BUF    2010     //返回文件


#define PORT   8888//端口号
#define SERVER_IP    "127.0.0.1"//服务器绑定地址


//发送(接收)数据包的格式
struct  Info			
{
	int command;		        //命令
	char nick[128];             //昵称
	char id[32];		        //帐号
	char password[32];	        //密码
	char email[32];			//邮箱
	char text[128];		        //要发送的内容
	char toid[32];              //对方的id
	int onlinenum;		        //在线人数
	int result;                 //服务器返回的结果
	int retcommand;             //返回命令
	int vipflag;			    //vip标志位
	int forbid;			        //禁言标志
	char file[32];              //文件名
	char file_buf[32];          //文件内容
	
};
typedef struct Info info;

#endif

当时为了测试能更加方便,IP使用了回环地址。如果需要移植到开发板上去只要把IP改掉就可以了。之前在友善之臂的开发板上移植过,成功了。

接下来是服务器的代码,当时为了代码更加清晰方便查错,我把代码分成了几部分编写
数据库的代码:
database.h

#ifndef DATABASE_H
#define DATABASE_H

#include "../chat.h"

int InitDataBase();//初始化数据库
int select_name_callback(void* para,int columnCount,char** columnValue,char** columnName);//回调函数
int data_user_exist(info *p);//判断用户是否存在
int data_insert_user(info *p);//把用户名和密码写进数据库
int data_user_password(info *p);//判断用户密码是否正确
int data_update_vip(info *p);//给指定用户修改vip标志位
int data_vip(info *p);//判断用户在数据库中的vip标志
int data_set_forbid(info *p, int a);//给指定用户禁言或者取消禁言
int data_forbid(info *p);//判断用户是否被禁言
int data_update_password(info *p);//给指定用户修改密码
int data_email(info *p);//判断账户邮箱是否匹配
void select_nick(info *p);//查看昵称

#endif

database.c

#include "../chat.h"
#include <sqlite3.h>
#include <stdio.h>
#include <string.h>


char id[1024] = {0};
char nick[128] = {0};
/*
函数名:InitDataBase
功能:打开数据库,创建要用的表,最后关闭数据库
形参:
返回值:成功(SUCCESS) 失败(FAILURE)
*/
int InitDataBase()
{
	sqlite3 *ppdb;
	char sql[128] = {0};//用于存放操作数据库的命令
	//创建(打开)数据库
	int ret = sqlite3_open(DATABASE, &ppdb);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_open : %s\n", sqlite3_errmsg(ppdb));
		return FAILURE;
	}
	
	//数据库中成员包含 昵称  id  密码  e-mail vip标志  禁言标志
	sprintf(sql,"create table if not exists account (nick text, id text, password text, email text, vip integer, forbid integer);");
	ret = sqlite3_exec(ppdb, sql, NULL, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec1 : %s\n",sqlite3_errmsg(ppdb));
		return FAILURE;
	}
	sqlite3_close(ppdb);
	return SUCCESS;
}

/*如果帐户已经存在,则调用回调函数*/
/*如果帐密码匹配,则调用回调函数*/
int select_name_callback(void *para, int columnCount, char **columnValue, char **columnName)
{
	strcpy(id, columnValue[0]);
	return 0;
}

/*查看昵称*/
int select_nick_callback(void *para, int columnCount, char **columnValue, char **columnName)
{
	strcpy(nick, columnValue[0]);
	return 0;
}



/*
日期:
作者:
功能描述:判断用户密码是否正确
形参:客户端发送的数据包(结构体,包含用户名和密码)
返回值:如果用户密码正确则返回TRUE,否则返回FALSE
*/
int data_user_password(info *p)
{
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	ret = sqlite3_open(DATABASE, &ppdb);//打开数据库
	if(ret != SQLITE_OK)
	{
		return FAILURE;
	}
	sprintf(sql, "select * from account where id = '%s' and password = '%s'", p->id, p->password);//通过id,密码查找
	ret = sqlite3_exec(ppdb, sql, select_name_callback, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec2 : %s\n", sqlite3_errmsg(ppdb));
	}	

	if(strlen(id) == 0)//没有调用回调函数,说明密码错误
	{
		memset(id, 0, sizeof(id));
		return FALSE;
	}
	else			//调用回调函数,说明密码正确
	{
		memset(id, 0, sizeof(id));
		return TRUE;
	}
	sqlite3_close(ppdb);
}

/*
日期:
作者:
功能描述:判断用户是否存在
形参:客户端发送的数据包(结构体,包含用户名和密码)
返回值:如果用户存在,则返回TRUE,否则,返回FALSE
*/
int data_user_exist(info *p)
{
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	ret = sqlite3_open(DATABASE, &ppdb);//打开数据库
	if(ret != SQLITE_OK)
	{
		return FAILURE;
	}
	sprintf(sql, "select id from account where id = '%s';", p->id);//只查询名字
	ret = sqlite3_exec(ppdb, sql, select_name_callback, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec2 : %s\n", sqlite3_errmsg(ppdb));
	}	

	if(strlen(id) == 0)//没有调用回调函数,即用户不存在
	{
		memset(id, 0, sizeof(id));
		return FALSE;
	}
	else			//调用回调函数说明用户存在
	{
		memset(id, 0, sizeof(id));
		return TRUE;
	}
	sqlite3_close(ppdb);
}

/*
日期:
作者:
功能描述:把用户名写进数据库
形参:客户端发送的数据包(结构体,包含用户名和密码)
返回值:成功或者失败
*/
int data_insert_user(info *p)
{
	if(NULL == p)
	{
		return FAILURE;
	}
	
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	p->vipflag = 0;
	p->forbid = 0;
	ret = sqlite3_open(DATABASE, &ppdb);
	if(ret != SQLITE_OK)
	{
		return FAILURE;
	}
	sprintf(sql, "insert into account values('%s', '%s', '%s', '%s', '%d', '%d');", p->nick, p->id, p->password, p->email, p->vipflag, p->forbid);
	ret = sqlite3_exec(ppdb, sql, NULL, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec4 : %s\n", sqlite3_errmsg(ppdb));
		return FAILURE;
	}
	sqlite3_close(ppdb);
	return SUCCESS;
}

/*
日期:
作者:
函数名:
功能:给指定用户修改vip标志位
形参:客户端发送的数据包(结构体,包含用户名和密码)
返回值:成功或者失败
*/
int data_update_vip(info *p)
{
	if(NULL == p)
	{
		return FAILURE;
	}
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	
	//打开数据库
	ret = sqlite3_open(DATABASE, &ppdb);
	if(ret != SQLITE_OK)
	{
		return FAILURE;
	}
	
	sprintf(sql, "update account set vip = '%d' where id = '%s';", p->vipflag, p->id);
	
	ret = sqlite3_exec(ppdb, sql, NULL, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec4 : %s\n", sqlite3_errmsg(ppdb));
		return FAILURE;
	}
	//关闭数据库
	sqlite3_close(ppdb);
	return SUCCESS;
	
}

/*
日期:
作者:
函数名:data_vip
功能:判断用户是否是vip
形参:客户端发送的数据包(结构体,包含用户名和密码,标志位)
返回值:TRUE 或者 FALSE
*/
int data_vip(info *p)
{
	
	if(NULL == p)
	{
		return FALSE;
	}
	
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	ret = sqlite3_open(DATABASE, &ppdb);//打开数据库
	
	if(ret != SQLITE_OK)
	{
		return FALSE;
	}
	memset(id, 0, sizeof(id));
	sprintf(sql, "select id from account where id = '%s' and password = '%s' and vip = 1;", p->id, p->password);
	//printf("%s\n",sql);
	ret = sqlite3_exec(ppdb, sql, select_name_callback, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec2 : %s\n", sqlite3_errmsg(ppdb));
	}	
	//printf("id = %d\n", strlen(id));
	if(strlen(id) == 0)//没有调用回调函数,即用户不是vip
	{
		memset(id, 0, sizeof(id));
		return FALSE;
	}
	else			//调用回调函数说明用户是vip
	{
		p->vipflag = 1;
		memset(id, 0, sizeof(id));
		return TRUE;
	}
	sqlite3_close(ppdb);
	return TRUE;
}


/*
日期:
作者:
函数名:data_set_forbid
功能:给指定用户禁言或者取消禁言
形参:客户端发送的数据包(结构体,包含用户名和密码,标志位) 置位(1或0)
返回值:成功(SUCCESS)或者失败(FAILURE)
*/
int data_set_forbid(info *p, int a)
{
	if(NULL == p)
	{
		return FAILURE;
	}

	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	//打开数据库
	ret = sqlite3_open(DATABASE, &ppdb);
	
	sprintf(sql, "update account set forbid = '%d' where id ='%s'", a, p->toid);
	if(ret != SQLITE_OK)
	{
		return FALSE;
	}
	ret = sqlite3_exec(ppdb, sql, NULL, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec4 : %s\n", sqlite3_errmsg(ppdb));
		return FAILURE;
	}
	//关闭数据库
	sqlite3_close(ppdb);
	return SUCCESS;
	

}



/*
日期:
作者:
函数名:data_forbid
功能:判断用户是否被禁言
形参:客户端发送的数据包(结构体,包含用户名和密码,标志位)
返回值:成功(SUCCESS)或者失败(FAILURE)
*/
int data_forbid(info *p)
{
	if(NULL == p)
	{
		return FALSE;
	}
	
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	
	//打开数据库
	ret = sqlite3_open(DATABASE, &ppdb);
	if(ret != SQLITE_OK)
	{
		return FALSE;
	}
	memset(id, 0, sizeof(id));
	
	sprintf(sql, "select id from account where id = '%s' and password = '%s' and forbid = 1;", p->id, p->password);
	ret = sqlite3_exec(ppdb, sql, select_name_callback, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec2 : %s\n", sqlite3_errmsg(ppdb));
	}	
	printf("id = %d\n", strlen(id));
	
	//没有调用回调函数,即用户没被禁言
	if(strlen(id) == 0)
	{
		memset(id, 0, sizeof(id));
		return FALSE;
	}
	
	//调用回调函数说明用户被禁言
	else			
	{
		p->forbid = 1;
		memset(id, 0, sizeof(id));
		return TRUE;
	}
	sqlite3_close(ppdb);
	return TRUE;
}


/*
日期:
作者:
函数名:data_update_password
功能:给指定用户修改密码
形参:客户端发送的数据包(结构体,包含用户名和密码)
返回值:成功或者失败
*/
int data_update_password(info *p)
{
	if(NULL == p)
	{
		return FAILURE;
	}
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	
	//打开数据库
	ret = sqlite3_open(DATABASE, &ppdb);
	if(ret != SQLITE_OK)
	{
		return FAILURE;
	}
	
	sprintf(sql, "update account set password = '%s' where id = '%s';", p->password, p->id);
	
	ret = sqlite3_exec(ppdb, sql, NULL, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("data_update_password sqlite3_exec : %s\n", sqlite3_errmsg(ppdb));
		return FAILURE;
	}
	//关闭数据库
	sqlite3_close(ppdb);
	return SUCCESS;
	
}


/*
日期:
作者:
函数名:data_email
功能:判断用户账号邮箱是否匹配
形参:客户端发送的数据包(结构体,包含用户名和密码,标志位)
返回值:TRUE 或者 FALSE
*/
int data_email(info *p)
{
	if(NULL == p)
	{
		return FALSE;
	}
	
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	
	//打开数据库
	ret = sqlite3_open(DATABASE, &ppdb);
	if(ret != SQLITE_OK)
	{
		return FALSE;
	}
	memset(id, 0, sizeof(id));
	
	sprintf(sql, "select id from account where id = '%s' and email = '%s';", p->id, p->email);
	ret = sqlite3_exec(ppdb, sql, select_name_callback, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec2 : %s\n", sqlite3_errmsg(ppdb));
	}	
	printf("id = %d\n", strlen(id));
	if(strlen(id) == 0)//没有调用回调函数,即用户账号邮箱不匹配
	{
		memset(id, 0, sizeof(id));
		return FALSE;
	}
	else			//调用回调函数说明用户账号邮箱匹配
	{
		p->vipflag = 1;
		memset(id, 0, sizeof(id));
		return TRUE;
	}
	sqlite3_close(ppdb);
	return TRUE;
}


/*
日期:
作者:
函数名:select_nick
功能:查看昵称
形参:客户端发送的数据包(结构体,包含用户名和密码,标志位)
返回值:无
*/
void select_nick(info *p)
{
	if(NULL == p)
	{
		return;
	}
	
	sqlite3 *ppdb;
	int ret;
	char sql[128] = {0};
	
	//打开数据库
	ret = sqlite3_open(DATABASE, &ppdb);
	if(ret != SQLITE_OK)
	{
		return;
	}
	memset(nick, 0, sizeof(nick));
	
	sprintf(sql, "select nick from account where id ='%s';", p->id);
	ret = sqlite3_exec(ppdb, sql, select_nick_callback, NULL, NULL);
	if(ret != SQLITE_OK)
	{
		printf("sqlite3_exec2 : %s\n", sqlite3_errmsg(ppdb));
	}	
	strcpy(p->nick, nick);
	memset(nick, 0, sizeof(nick));
	
}

链表部分:
link.h

#ifndef  LINK_H
#define  LINK_H

#include "../chat.h"

//在线用户信息链表结点
struct Node
{
	char id[32];
	char nick[128];
	int fd; 
	int vipflag;
	int forbid;
	struct Node *next;
};
typedef struct Node node;


int InitLink(node **l);//初始化链表
int InsertLink(node *l, int p, info *pChat, int fd);//插入结点
int ListLength(node *l);//链表长度(在线人数)
int LocateNode(node *l, char *buf);//找结点位置
int NodeFd(node *l, char *toid);//通过id查找在线用户的fd
int DeleteList(node *l, int p, char *buf);//删除节点
int FindNode(node *first, info *chat_buf);//查找在线用户
#endif

link.c

#include "link.h"
 #include "../chat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
日期:
作者:
功能:初始化链表
形参:指向结点指针的指针
返回值:成功返回SUCCESS,失败返回FAILURE。
*/
int InitLink(node **l)
{
	if(NULL == l)
	{
		return FAILURE;
	}
	*l = (node *)malloc(sizeof(node) * 1);
	if(NULL == (*l))
	{
		return FAILURE;
	}
	(*l)->next = NULL;
	return SUCCESS;
}


/*
日期:
作者:
函数名:FindNode
功能:查找用户是否在线
形参:指向头结点的指针, 指向数据包的指针
返回值:存在返回TRUE,不存在返回FALSE。
*/
int FindNode(node *first, info *chat_buf)
{
	if(NULL == first || NULL == chat_buf)
	{
		return FALSE;
	}
	node *tmp = first->next;
	while(tmp)
	{
		if(!strcmp(tmp->id, chat_buf->id))
		{
			return TRUE;
		}
		tmp = tmp->next;
	}
	return FALSE;
}


/*
日期:
作者:
功能:插入链表
形参:链表头结点指针,插入的位置,指向数据包的指针, 客户端的fd(文件描述符)  
返回值:成功返回SUCCESS,失败返回FAILURE。
*/
int InsertLink(node *l, int p, info *pChat, int fd)
{
	if(NULL == l)
	{
		return FAILURE;
	}
	
	int k = 1;
	node *q = l;
	while(q && k < p)
	{
		q = q->next;
		k++;
	}
	if(k > p || !q)
	{
		return FAILURE;
	}
	node *tmp = (node *)malloc(sizeof(node) * 1);//产生结点
	strcpy(tmp->id, pChat->id);
	strcpy(tmp->nick, pChat->nick);
	tmp->fd = fd;
	tmp->vipflag = pChat->vipflag;
	tmp->forbid = pChat->forbid;
	tmp->next = q->next;
	q->next = tmp;
	return SUCCESS;
}
////////////////////////////////////////////////////////////////////////////////

/*
日期:
作者:
功能:链表长度(在线人数)
形参:链表头结点指针
返回值:成功返回在线人数,失败返回FAILURE。
*/
int ListLength(node *l)
{
	if(NULL == l)
	{
		return FAILURE;
	}
	int k = 0;
	node *v = l->next;
	while(v)
	{
		k++;
		v = v->next;
	}
	return k;
}
/////////////////////////////////////////////////////////////////////////////////////////


/*
日期:
作者:
函数名:LocateNode(结点位置)  
功能:输入信息,找到该结点的位置,用于用户下线(删除结点)
形参:链表头结点指针 信息(id字符串)
返回值:成功返回结点位置,失败返回FAILURE。
*/
int LocateNode(node *l, char *buf)
{
	if(NULL == l || NULL == buf)
	{
		return FAILURE;
	}
	node *v = l->next;
	int k = 1;
	while(v)
	{
		if(!strcmp(v->id, buf))
		{
			return k;
		}
		v = v->next;
		k++;
	}
	return SUCCESS;
}
/////////////////////////////////////////////////////////////////////


/*
日期:
作者:
函数名:NodeFd(通过id查找在线用户的fd)  
功能:通过输入的id查找到用户的fd
形参:链表头结点指针  指向字符串的指针
返回值:成功返回用户fd,失败返回FAILURE。
*/
int NodeFd(node *l, char *id)
{
	if(NULL == l || NULL == id)
	{
		return FAILURE;
	}
	node *n = l->next;
	//int k = 1;
	while(n)
	{
		if(!strcmp(n->id, id))
		{
			return n->fd;
		}
		n = n->next;
		//k++;
	}
	return FAILURE;
}
////////////////////////////////////////////////////////////


/////删除结点//////////////////////////////////
/*
日期:
作者:
函数名:DeleteList(删除结点)
功能:输入位置,找到该结点并从链表中删除
形参:链表头结点指针  位置   指针:用于存放删除的信息
返回值:成功返回SUCCESS,失败返回FAILURE。
*/
int DeleteList(node *l, int p, char *buf)
{
	if(NULL == l)
	{
		return FAILURE;
	}
	////12.16修改///////
	if(FAILURE == p)
	{
		return FAILURE;
	}
	///////////////////
	
	node *v = l->next;
	int k = 1;
	while(v && k < p)
	{
		v = v->next;
		k++;
	}
		
	if(k > p || !v)
	{
		return FAILURE;
	}
	//要删除的结点是最后一个
	if(v->next == NULL)
	{
			
		strcpy(buf, v->id);
		node *tmp ;
		tmp = l;
		while(tmp->next != v)
		{
			tmp = tmp->next;
		}
		tmp->next = NULL;
		free(v);
	}
		
	else  //结点不是最后一个
	{
		node *n = l;
		while(n->next != v)//v为要删除的借点
		{
			n = n->next;
		}
		
		n->next = v->next;
		strcpy(buf, v->id);
		free(v);
	}
	return SUCCESS;
}
///////////////////////////////////////////////

select.h

#ifndef SOCKET_H
#define SOCKET_H

#include "../chat.h"
#include "link.h"

int InitSocket(int *sockfd);//初始化套接字
void register_handler(info *pChat, int fd);//注册操作
void login_handler(info *pChat, int fd, node *first);//登录操作
void handler(int sockfd, node *first);//服务器处理函数
void PrivateChat(node *first, info *chat_buf);//私聊操作
void LogOut(node *first,info *chat_buf);//下线
void OnlineNum(node *first,info *chat_buf, int fd);//查看在线人数
void GroupChat(node *first, info *chat_buf, int fd);//群聊
void Vip(node *l,info *chat_buf);//VIP
void Forbid(node *first,info *chat_buf);//禁言
void Relieve(node *first,info *chat_buf);//解除禁言



#endif

select.c

#include "../chat.h"
#include "database.h"
#include "socket.h"

/*
日期:
作者:
函数名:InitSocket
功能:初始化套接字,完成创建套接字、绑定服务器信息、监听
形参:套接字
返回值:成功(SUCCESS) 、失败(FAILURE)
*/
int InitSocket(int *sockfd)
{
	struct sockaddr_in server_addr;//记录服务器信息
	int ret;
	if(NULL == sockfd)
	{
		return FAILURE;
	}
	*sockfd = socket(PF_INET, SOCK_STREAM, 0);// 创建套接字
	if(-1 == *sockfd)
	{
		perror("socket");
		return FAILURE;
	}


	int opt = 1;
	setsockopt(*sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//允许地址复用
	memset(&server_addr, 0, sizeof(server_addr));//初始化
	server_addr.sin_family = PF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
	

	//绑定
	ret = bind(*sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if(-1 == ret)
	{
		perror("bind");
		return FAILURE;
	}
	//监听
	ret = listen(*sockfd, 10);
	if(-1 == ret)
	{
		perror("listen");
		return FAILURE;
	}
	return SUCCESS;
}

/*
日期:
作者:
函数名:register_handler
功能:注册操作,需要打开数据库查询、插入、返回结果
形参:指向数据包的指针,fd标识符
返回值:无
*/
void register_handler(info *pChat, int fd)
{
	int ret;
	if(NULL == pChat)
	{
		return;
	}

	//判断用户是否存在
	ret = data_user_exist(pChat);//判断用户是否存在
	if(TRUE == ret)//用户已经存在,回复客户端
	{
		pChat->result = RET_EXIST;
		ret = send(fd, pChat, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("register_handler send 1");
		}
	}
	else //用户不存在,写进数据库,同时回复客户端注册成功
	{
		ret = data_insert_user(pChat);
		if(FAILURE == ret)
		{
			printf("Insert user Failure!\n");
		}

		pChat->result = RET_SUCCESS;
		ret = send(fd, pChat, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("register_handler send 2");
		}
	}
}


/*
日期:
作者:
函数名:login_handler(登录操作)
功能:(1)判断帐号密码是否合法,将合法的帐号插入在线链表
形参:指向数据包的指针,fd标识符,链表头结点指针
返回值:无
*/
void login_handler(info *pChat, int fd, node *first)
{

	if(NULL == pChat || NULL == first)
	{
		return;
	}
	int ret;
	//(1)判断用户是否存在
	ret = data_user_exist(pChat);//判断用户是否存在数据库中
	
	//如果用户不存在,回复客户端该用户不存在
	if(ret == FALSE)
	{
		pChat->result = RET_NOT_EXIST;//结果位填不存在
		ret = send(fd, pChat, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("login_handler send 1");
		}
	}
	else //(2)用户存在,则检查密码是否正确
	{
		ret = data_user_password(pChat);
	
		if(TRUE == ret)//密码正确
		{
			// 判断在线链表有无该用户,如果有则不让登录
			int res = FindNode(first, pChat);
			
			//用户已经在线,则不让登录
			if(res == TRUE)
			{
				//用户已经在线
				pChat->result = RET_HAVE_ONLINE;
				
				//把数据包发回客户端
				ret = send(fd, pChat, sizeof(info), 0);
				if(-1 == ret)
				{
					perror("login_handler send 2 :");
				}
			}
			
			//用户没有在线,则登录
			else
			{
				pChat->result = RET_SUCCESS;
				//判断用户在数据库中的vip标志位
				ret = data_vip(pChat);
				if(TRUE == ret)
				{
					pChat->vipflag = 1;
					printf("vip\n");
				}
				else if(FALSE == ret)
				{
					pChat->vipflag = 0;
					printf("novip\n");
				}
			
				//判断用户在数据库中的禁言标志位
				ret = data_forbid(pChat);
				if(TRUE == ret)
				{
					pChat->forbid = 1;
					printf("forbid\n");
				}
				else
				{
					pChat->forbid = 0;
					printf("noforbid\n");
				}
				//printf("181\n");
				//查看昵称
				select_nick(pChat);
				printf("nick = %s\n", pChat->nick);
				//把数据包发回客户端
				ret = send(fd, pChat, sizeof(info), 0);
				if(-1 == ret)
				{
					perror("login_handler send 3");
				}
				
				//把客户端信息插入链表中
				ret = InsertLink(first, 1, pChat, fd);
				if(FAILURE == ret)
				{
					printf("插入链表失败");
				}
				printf("用户 %s 上线!\n",pChat->id);
			}
		}
		else//密码错误,回复客户端密码错误
		{
			pChat->result = RET_PASS_ERROR;
			ret = send(fd, pChat, sizeof(info), 0);//把数据包发回客户端
			if(-1 == ret)
			{
				perror("login_handler send 4 :");
			}
		}
	}
}


/*
日期:
作者:
函数名:PrivateChat(服务处理私聊函数)
功能:根据客户端发来的数据包,将私聊信息发给指定客户端
返回值:无
*/
void PrivateChat(node *first, info *chat_buf)
{
	//入参判断
	if(NULL == chat_buf || NULL == first)
	{
		return;
	}
	int ret;
	
	//找到toid对应的fd
	int fd = NodeFd(first, chat_buf->toid);
	
	//返回聊天命令
	chat_buf->retcommand = RETURN_CHAT;
	
	//把数据包发回toid客户端
	ret = send(fd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("PrivateChat send");
	}
}


/*
日期:
作者:
函数名:GroupChat(群聊)
功能:将需要群发的数据包发给所有在线用户
形参:链表头结点指针,指向数据包的指针
返回值:无
*/
void GroupChat(node *first, info *chat_buf, int fd)
{
	if(NULL == first || NULL == chat_buf)
	{
		return;
	}
	node *tmp = first->next;
	int ret;	
	
	//返回群聊命令
	chat_buf->retcommand = RETURN_GROUP;
	while(tmp)
	{
		if(tmp->fd == fd)
		{
			tmp = tmp->next;
		}
		else
		{
			printf("203\n");
			ret = send(tmp->fd, chat_buf, sizeof(info), 0);
			printf("205\n");
			if(-1 == ret)
			{
				perror("GroupChat send");
			}
			tmp = tmp->next;
		}
	}
	
}


/*
日期:
作者:
函数名:OnlineNum(查找在线人数)
功能:查在线人数,并返回给客户端
形参:链表头结点指针, 指向数据包的指针
返回值:无
*/
void OnlineNum(node *first,info *chat_buf, int fd)
{
	if(NULL == first || NULL == chat_buf)
	{
		return;
	}
	chat_buf->onlinenum = ListLength(first);
	
	//返回在线人数命令
	chat_buf-> retcommand = RETURN_ONLINENUM;
	
	//把数据包发回客户端
	int ret = send(fd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("PrivateChat send");
	}
	

}


/*
日期:
作者:
函数名:LogOut(下线操作)
功能:让在线用户下线,在线用户的节点从链表中删除
形参:链表头结点指针, 指向数据包的指针
返回值:无
*/
void LogOut(node *first,info *chat_buf)
{
	
	if(NULL == first || NULL == chat_buf)
	{
		return;
	}
	
	int locate = LocateNode(first, chat_buf->id);
	
	char tmp[128] = {0};
	DeleteList(first, locate, tmp);
	
	printf("用户%s 下线\n", tmp);
	sleep(1);
}


/*
日期:
作者:
函数名:Vip
功能:注册会员,使客户端在数据库和链表上的vip标志位置为1
形参:链表头结点指针, 指向数据包的指针
返回值:无
*/
void Vip(node *l,info *chat_buf)
{
	if(NULL == l || NULL == chat_buf)
	{
		return;
	}	
	int ret;
	node *tmp = l->next;
	
	//给在线链表修改vip标志
	while(tmp)
	{
		if(!strcmp(tmp->id, chat_buf->id))
		{
			tmp->vipflag = chat_buf->vipflag; 
		}
		tmp = tmp->next;
	}
	
	//在数据库中修改vip标志
	ret = data_update_vip(chat_buf);
	if(FAILURE == ret)
	{
		printf("成为会员失败\n");
	}
	
}


/*
日期:
作者:
函数名:Forbid
功能:禁言,使客户端在数据库和链表上的禁言标志位置为1
形参:链表头结点指针, 指向数据包的指针
返回值:无
*/
void Forbid(node *first,info *chat_buf)
{
	//入参判断/////////////////////////////
	if(NULL == first || NULL == chat_buf)
	{
		return;
	}
	///////////////////////////////////////
	
	////在线用户链表加入禁言标志////////////
	node *n = first->next;
	//printf("351\n");
	while(n)
	{
		if(!strcmp(n->id, chat_buf->toid))
		{
			n->forbid = 1;
			break;
		}
		n = n->next;
	}
	///////////////////////////////////////
	
	//数据库中加入禁言
	data_set_forbid(chat_buf, 1);
	
	//找到toid对应的fd
	int fd = NodeFd(first, chat_buf->toid);
	printf("fd = %d\n", fd);
	
	//返回禁言
	chat_buf->retcommand = RETURN_FORBID;
	//printf("365\n");
	//把数据包发回对应的客户端
	int ret = send(fd, chat_buf, sizeof(info), 0);
	//printf("368\n");
	if(-1 == ret)
	{
		perror("PrivateChat send");
	}
	
}


/*
日期:
作者:
函数名:Relieve
功能:解除禁言,使客户端在数据库和链表上的禁言标志位置为1
形参:链表头结点指针, 指向数据包的指针
返回值:无
*/
void Relieve(node *first,info *chat_buf)
{
	//入参判断
	if(NULL == chat_buf)
	{
		return;
	}
	////在线用户链表解除禁言标志////////////
	node *n = first->next;
	printf("397\n");
	while(n)
	{
		if(!strcmp(n->id, chat_buf->toid))
		{
			n->forbid = 0;
			break;
		}
		n = n->next;
	}
	///////////////////////////////////////
	
	//数据库中加入解除禁言
	data_set_forbid(chat_buf, 0);
	//找到toid对应的fd
	int fd = NodeFd(first, chat_buf->toid);
	printf("fd = %d\n", fd);
	
	//返回解除禁言
	chat_buf->retcommand = RETURN_RELIEVE;
	printf("415\n");
	//把数据包发回对应的客户端
	int ret = send(fd, chat_buf, sizeof(info), 0);
	printf("418\n");
	if(-1 == ret)
	{
		perror("PrivateChat send");
	}
}


/*
日期:
作者:
函数名:Kick
功能:会员用户踢掉指定用户
形参:链表头结点指针, 指向数据包的指针
返回值:无
*/
void Kick(node *first,info *chat_buf)
{
	if(NULL == first || NULL == chat_buf)
	{
		return;
	}
	
	//找到toid对应的fd
	int fd = NodeFd(first, chat_buf->toid);
	
	int locate = LocateNode(first, chat_buf->toid);
	char tmp[128] = {0};
	//删除结点
	DeleteList(first, locate, tmp);
	
	chat_buf->retcommand = RETURN_KICK;
	int ret = send(fd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("Kick send");
	}
	
	printf("用户%s 下线\n", tmp);
	sleep(1);

}

/*
日期:
作者:
函数名:Email
功能:判断账号邮箱是否匹配
形参:指向数据包的指针   文件标识符
返回值:无
*/
void Email(info *chat_buf, int fd)
{
	if(NULL == chat_buf)
	{
		return;
	}
	int ret = data_email(chat_buf);
	
	//账号邮箱不匹配
	if(FALSE == ret)
	{
		chat_buf->result = RET_EMAIL_FAIL;
		ret = send(fd, chat_buf, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("Email send 1");
		}
	}
	
	//账号邮箱匹配
	else
	{
		chat_buf->result = RET_EMAIL_SUCC;
		ret = send(fd, chat_buf, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("Email send 2");
		}
	}
}


/*
日期:
作者:
函数名:Forget
功能:修改密码
形参:指向数据包的指针  文件描述符
返回值:无
*/
void Forget(info *pChat, int fd)
{
	if(NULL == pChat)
	{
		return;
	}
	printf("561\n");
	int ret = data_update_password(pChat);
	if(FAILURE == ret)
	{
		printf("密码修改失败!\n");
		pChat->result = RET_FAILURE;
		ret = send(fd, pChat, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("Forget send 1");
		}
	}
	
	else
	{
		printf("密码修改成功!\n");
		pChat->result = RET_SUCCESS;
		
		ret = send(fd, pChat, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("Forget send 2");
		}
	}
}



/*
日期:
作者:
函数名:FileName
功能:根据客户端发来的数据包,将文件名发给指定客户端
形参:链表头结点指针,指向数据包的指针
返回值:无
*/
void FileName(node *l, info *chat_buf)
{
	if(NULL == l || NULL == chat_buf)
	{
		return;
	}
	int ret;
	
	//找到toid对应的fd
	int fd = NodeFd(l, chat_buf->toid);
	
	//返回文件名
	chat_buf->retcommand = RETURN_FILE_NAME;
	
	//把数据包发回toid客户端
	ret = send(fd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("FileName send");
	}
}


/*
日期:
作者:
函数名:FileBuf
功能:根据客户端发来的数据包,将文件名发给指定客户端
形参:链表头结点指针,指向数据包的指针
返回值:无
*/
void FileBuf(node *l, info *chat_buf)
{
	if(NULL == l || NULL == chat_buf)
	{
		return;
	}
	int ret;
	
	//找到toid对应的fd
	int fd = NodeFd(l, chat_buf->toid);
	//返回文件
	chat_buf->retcommand = RETURN_FILE_BUF;
	
	//发送给对应的客户端
	ret = send(fd, chat_buf, sizeof(info), 0); 
	if(-1 == ret)
	{
		perror("FileBuf send");
	}
}


/*
日期:
作者:
函数名:handler(服务器处理函数)
功能:处理客户端上传的信息
返回值:无
*/
void handler(int sockfd, node *first)
{
	if(NULL == first)
	{
		return;
	}
	int fd[SIZE] = {0}, i = 0, j = 0, ret;
	//传输文件用
	//int filefd;
	fd_set readfd, tmpfd;

	FD_ZERO(&readfd);//初始化集合
	FD_SET(sockfd,&readfd);//将socket添加进集合
	
	int maxfd = sockfd;

	struct sockaddr_in client_addr;//保存客户端信息
	int length = sizeof(client_addr);
	
	info chat_buf;//数据包(传输信息)

	while(1)
	{
		tmpfd = readfd;
		ret = select(maxfd + 1, &tmpfd, NULL, NULL, NULL);
		if(-1 == ret)
		{
			perror("select");
		}
		
		if(FD_ISSET(sockfd, &tmpfd)) //有客户端发起连接
		{
			for(i = 0; i < SIZE; i++)
			{
				if(fd[i] == 0)
				{
					break;
				}
			}
			
			if(i == SIZE)
			{
				printf("客户端连接已满,请稍后\n");
				sleep(1);
				continue;
			}
			fd[i] = accept(sockfd, (struct sockaddr *)&client_addr, (socklen_t *)&length);
			if(-1 == fd[i])
			{
				perror("accept");
			}
			FD_SET(fd[i],&readfd);
			if(fd[i] > maxfd)
			{
				maxfd = fd[i];
			}
		}
		
		//有客户端发消息
		else 
		{
			for(j = 0; j < SIZE; j++)
			{
				if(FD_ISSET(fd[j], &tmpfd))
				{
					ret = recv(fd[j], &chat_buf, sizeof(chat_buf),0);
					if(-1 == ret)
					{
						perror("recv");
					}
					printf("*********%d\n",chat_buf.command);
					
					switch(chat_buf.command)
					{
						//printf("676\n");
						case REGISTER:	    //注册
							
							register_handler(&chat_buf,fd[j]);//打开数据库查询、插入、返回结果
							break;
						
						case LOGIN:		    //登录
							//printf("686\n");
							login_handler(&chat_buf, fd[j], first);
							break;
							
						case EMAIL:         //匹配邮箱
							Email(&chat_buf, fd[j]);
							break;
							
						case FORGET:	    //修改密码
							//printf("686\n");
							Forget(&chat_buf, fd[j]);
							break;
						
						case PRIVATE:       //私聊
							PrivateChat(first, &chat_buf);
							break;

						case GROUP:         //群聊
							GroupChat(first, &chat_buf, fd[j]);														
							break;

						case ONLINENUM:    //在线人数	
							OnlineNum(first,&chat_buf, fd[j]);
							break;
								
						case EXIT:			//客户端退出							
							//printf("231\n");
							close(fd[j]);
							FD_CLR(fd[j], &readfd);//从集合中删除
							fd[j] = 0;
							break;
						
						case LOGOUT:    //用户下线
							LogOut(first,&chat_buf);
							break;
						
						case VIP:       //注册会员
							Vip(first,&chat_buf);
							break;
						
						case FORBID:	//禁言
							
							Forbid(first,&chat_buf);
							break;
							
						case RELIEVE:    //解除禁言
							Relieve(first,&chat_buf);
							break;
							
						case KICK:       //踢人
							Kick(first,&chat_buf);
							break;
							
						case FORCE_EXIT:  //强制退出
							LogOut(first,&chat_buf);
							close(fd[j]);
							FD_CLR(fd[j], &readfd);//从集合中删除
							fd[j] = 0;
							break;
							
						case FILE_NAME:  //发送文件名
							FileName(first, &chat_buf);
							break;
							
						case FILE_BUF:   //发送文件
							FileBuf(first, &chat_buf);
							break;
					}
					break;
				}
			}
		}
	}
}

主函数部分
main.c

#include <stdio.h>
#include <stdlib.h>
#include "link.h"
#include "../chat.h"
#include "socket.h"
#include "database.h"


int main()
{
	int sockfd;
	int ret;
	node *first; //链表头指针

	system("clear");
	printf("初始化网络.....\n");
	ret = InitSocket(&sockfd);
	if(FAILURE == ret)
	{
		printf("初始化网络失败\n");
	}
	sleep(1);

	printf("初始化数据.....\n");
	ret = InitLink(&first);
	if(FAILURE == ret)
	{
		printf("初始化数据失败\n");
		exit(1);
	}
	sleep(1);
	
	printf("初始化数据库...\n");
	ret = InitDataBase();
	if(-1 == ret)
	{
		printf("初始化数据库失败\n");
		exit(1);
	}
	handler(sockfd, first);	//死循环,服务器操作
	return 0;
}

接下来是客户端部分
main.c

#include "../chat.h"
#include "chatroom.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include "sys.h"


#define PORT   8888
#define IP     "127.0.0.1"


extern info global_chat;
int sockfd;
info chat_buf;

/*
时间:
作者:
函数名:handler
函数功能:处理强制退出,当用户按下CTRL C后,客户端向服务器发送下线命令,然后退出。
形参:无
返回值:无
*/
void handler(int sig)
{
	replace_chat(&chat_buf, &global_chat);
	chat_buf.command = FORCE_EXIT;
	int ret = send(sockfd, &chat_buf, sizeof(info), 0);
	if (-1 == ret)
	{
		perror("send");
	}
	//printf("***%d\n",chat_buf.command);
	close(sockfd);
	raise(SIGKILL);
}



/*
时间:
作者:
函数名:Register(注册)
函数功能:提示用户输入帐户密码,将用户的帐号密码发给客户端,
		  根据服务器的反馈消息提示用户 注册成功  或是  帐户已经存在。
形参:指向数据包的指针
返回值:无
*/
void Register(info *chat_buf)//注册操作
{
	
	chat_buf->command = REGISTER;//注册
	printf("请输入邮箱:\n");
	scanf("%s", chat_buf->email);

	printf("请输入昵称:\n");
	scanf("%s", chat_buf->nick);
	printf("请输入帐号:\n");
	scanf("%s", chat_buf->id);
	printf("请输入密码:\n");
	scanf("%s", chat_buf->password);
	
	send(sockfd, chat_buf, sizeof(info), 0);//将信息发送给服务器

	memset(chat_buf, 0, sizeof(info));
	recv(sockfd, chat_buf, sizeof(*chat_buf), 0);//接收服务器发来的信息
	switch(chat_buf->result)
	{
		case RET_SUCCESS:
			printf("注册成功!\n");
			sleep(1);
			break;
		case RET_EXIST:
			printf("用户已经存在,重新注册\n");
			sleep(1);
			break;
	}
}



/*
时间:
作者:
函数名:Login(登录操作)
函数功能:将帐号登录
形参:指向数据包的指针
返回值:无
*/
void Login(info *chat_buf)
{
	//入参判断//////////////////////
	if(NULL == chat_buf)
	{
		return;
	}
	////////////////////////////////
	
	
	chat_buf->command = LOGIN;//登录
	printf("请输入帐号:\n");
	scanf("%s", chat_buf->id);
	printf("请输入密码:\n");
	scanf("%s", chat_buf->password);
	
	send(sockfd, chat_buf, sizeof(info), 0);//将信息发送给服务器
	memset(chat_buf, 0, sizeof(info));
	recv(sockfd, chat_buf, sizeof(*chat_buf), 0);//接收服务器发来的信息
	switch(chat_buf->result)
	{
		case RET_NOT_EXIST:        //用户不存在
			printf("用户不存在!\n");
			sleep(1);
			break;
			
		case RET_PASS_ERROR:     //密码错误
			printf("密码错误\n");
			sleep(1);
			break;
		
		case RET_HAVE_ONLINE:       //用户已经在线 	
			printf("用户已经在线,不可登录!\n");
			sleep(1);
			break;

		
		case RET_SUCCESS:       //登录成功
			//进入聊天室界面
			Chat_room(sockfd, chat_buf);
			printf("122\n");
			break;

	}
	
}


/*
时间:
作者:
函数名:Forget
函数功能:忘记密码
形参:指向数据包的指针
返回值:无
*/
void Forget(info *pChat)
{
	if(NULL == pChat)
	{
		return;
	}
	char new[32];
	char new2[32];
	printf("请输入帐号:\n");
	scanf("%s", pChat->id);
	printf("请输入邮箱:\n");
	scanf("%s", pChat->email);
	
	pChat->command = EMAIL;
	//判断账号,邮箱是否匹配
	//将信息发送给服务器
	send(sockfd, pChat, sizeof(info), 0);
	memset(pChat, 0, sizeof(info));
	//接收服务器发来的信息
	recv(sockfd, pChat, sizeof(info), 0);
	if(pChat->result == RET_EMAIL_FAIL)
	{
		printf("邮箱不正确!\n");
		sleep(1);
	}
	else
	{
		printf("请输入新密码:\n");
		scanf("%s", new);
		printf("请再次输入密码:\n");
		scanf("%s", new2);
		if(!strcmp(new, new2))
		{
			//忘记密码
			pChat->command = FORGET;
			strcpy(pChat->password, new);
			send(sockfd, pChat, sizeof(info), 0);
			memset(pChat, 0, sizeof(info));
			
			//接收服务器发来的信息
			recv(sockfd, pChat, sizeof(info), 0);
			if(pChat->result == RET_SUCCESS)
			{
				printf("密码修改成功!\n");
				sleep(1);
			}
			else
			{
				printf("密码修改失败!\n");
				sleep(1);
			}
			
		}
		else
		{
			printf("两次输入的密码不一样!\n");
			sleep(1);
		}
	}
	
}



/*
时间:
作者:
函数名:main(客户端主函数)
函数功能:完成客户端以下操作:
			(一)客户端初始化操作:创建套接字、向服务器发起链接。
			(二)实现:注册、登录、退出三大功能
形参:指向数据包的指针
返回值:无
*/
int main()
{
	int ret;
	struct sockaddr_in server_addr;  //向服务器发起连接,所以结构体保存服务器的信息
	
	sockfd = socket(PF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
	{
		perror("socket");
		exit(1);
	}
	
	//初始化保存服务器信息的结构体////////////////////
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = PF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.s_addr = inet_addr(IP);
	//////////////////////////////////////////////////
	
	//向服务器发起连接////////////////////////////////////////////////////////////
	ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret)
	{
		perror("connect");
		exit(1);
	}
	///////////////////////////////////////////////////////////////////////////////
	
	//强制退出处理//////////////
	signal(SIGINT, handler);
	/////////////////////////////
	
	////注册、登录、退出(死循环)///////////////////////////////////////
	while(1)
	{
		menu1();
		int choice;
		scanf("%d", &choice);
		switch(choice)
		{
			case 1:				//注册
				Register(&chat_buf);
				break;
			case 2:				//登录
				Login(&chat_buf);
				break;

			case 4:				//退出
				Bye();
				chat_buf.command = EXIT;
				ret = send(sockfd, &chat_buf, sizeof(info), 0);
				close(sockfd);
				exit(1);
				break;
				
			case 3:            //忘记密码
				Forget(&chat_buf);
				break;
		}
	}
	///////////////////////////////////////////////////////////////////
	return 0;
}

sys.h

#ifndef SYS_H
#define SYS_H

void menu1();//客户端登录菜单
void menu2();//聊天室普通用户界面
void vip_menu();//聊天室会员用户界面
void private_menu();//私聊窗口
void Bye();//退出程序

info *replace_chat(info *str, const info *ptr);//数据包替换

#endif

sys.c

#include "../chat.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*
日期:
作者:
函数名:replace_chat(数据包替换)
功能:将ptr所指数据包的信息,放至str所指数据包中
形参:指向被修改的数据包的指针   指向数据包的指针
返回值:指向修改好的数据包的指针
*/
info *replace_chat(info *str, const info *ptr)
{
	if(NULL == str || NULL == ptr)
	{
		return NULL;
	}
	
	//替换id
	strcpy(str->id, ptr->id);
	//替换password
	strcpy(str->password, ptr->password);
	
	//替换nick
	strcpy(str->nick, ptr->nick);
	
	//替换vipflag
	str->vipflag = ptr->vipflag;
	
	//替换禁言标志
	str->forbid = ptr->forbid;
	
	/*其余标志位后续添加
	strcpy(str->id, ptr->id);
	strcpy(str->id, ptr->id);
	*/
	return str;

}


/*
//格式
//printf("\033[字背景颜色;字体颜色m字符串\033[0m");
	部分颜色代码:
					字背景颜色: 40--49                字颜色: 30--39
						40: 黑                           30: 黑
						41: 红                           31: 红
						42: 绿                           32: 绿
						43: 黄                           33: 黄
						44: 蓝                           34: 蓝
						45: 紫                           35: 紫
						46: 深绿                         36: 深绿
						47:白色                         37:白色
*/
//例子:printf("\033[47;31mThis is a color test.\033[0m");

/*
日期:
作者:
功能:客户端登录界面
形参:无
返回值:无
*/
void menu1()
{
	system("clear");
	printf("\t\t\033[47;31m*******************************************\033[0m \t\t\n");
	printf("\t\t\033[47;31m******************1、注册******************\033[0m \t\t\n");
	printf("\t\t\033[47;31m******************2、登录******************\033[0m \t\t\n");
	printf("\t\t\033[47;31m******************3、忘记密码**************\033[0m \t\t\n");
	printf("\t\t\033[47;31m******************4、退出******************\033[0m \t\t\n");
	printf("\t\t\033[47;31m*******************************************\033[0m \t\t\n");
	printf("请输入选项:\n");
}


/*
日期:
作者:
功能:退出界面
形参:无
返回值:无
*/
void Bye()
{
	system("clear");
	printf("\t\t********************************\t\t\n");
	printf("\t\t********bye!bye!bye!************\t\t\n");
	printf("\t\t********************************\t\t\n");
	sleep(1);
	system("clear");
}
/*
日期:
作者:
功能:聊天室普通用户界面
形参:无
返回值:无
*/
void menu2()
{
	system("clear");
	printf("\t\t\033[47;30m***********************************************\033[0m \n");
	printf("\t\t\033[47;33m******************欢迎进入聊天室***************\033[0m \n");
	printf("\t\t\033[47;33m*                  1、私聊                    *\033[0m \n");
	printf("\t\t\033[47;32m*                  2、群聊                    *\033[0m \n");
	printf("\t\t\033[47;32m*                  3、查看在线人数            *\033[0m \n");
	printf("\t\t\033[47;31m*                  4、升级为会员              *\033[0m \n");
	printf("\t\t\033[47;31m*                  5、传输文件                *\033[0m \n");
	printf("\t\t\033[47;31m*                  6、下线                    *\033[0m \n");
	printf("\t\t\033[47;31m***********************************************\033[0m \n");
	printf("\t\t\033[47;30m***********************************************\033[0m \n");
	printf("请输入选项:\n");
}

/*
日期:
作者:
函数名:private_menu
功能:聊天室普通用户私聊对话框
形参:无
返回值:无
*/
void private_menu()
{
	system("clear");
	printf("\t\t\033[47;30m***********************************************\033[0m \n");
	printf("\t\t\033[47;33m***********************************************\033[0m \n");
	printf("\t\t\033[47;33m*  表情包                                     *\033[0m \n");
	printf("\t\t\033[47;33m*  :)   笑脸            :hh 哈哈大笑          *\033[0m \n");
	printf("\t\t\033[47;33m*  :gogo  冲            :jiayou  加油         *\033[0m \n");
	printf("\t\t\033[47;33m*  :cold  冷            :fish 锦鲤            *\033[0m \n");
	printf("\t\t\033[47;33m*  :hen   哼                                  *\033[0m \n");
	printf("\t\t\033[47;32m*                                             *\033[0m \n");
	printf("\t\t\033[47;32m*                                             *\033[0m \n");
	printf("\t\t\033[47;31m*            byebyb: 退出私聊                 *\033[0m \n");
	printf("\t\t\033[47;31m***********************************************\033[0m \n");
	printf("\t\t\033[47;30m***********************************************\033[0m \n");
	printf("请输入内容\n");
}


/*
日期:
作者:
功能:聊天室会员用户界面
形参:无
返回值:无
*/
void vip_menu()
{
	system("clear");
	printf("\t\033[47;31m*****************************************************\033[0m \n");
	printf("\t\033[47;31m******************欢迎VIP进入聊天室******************\033[0m \n");
	printf("\t\033[47;31m*                   1、私聊                         *\033[0m \n");
	printf("\t\033[47;31m*                   2、群聊                         *\033[0m \n");
	printf("\t\033[47;31m*                   3、查看在线人数                 *\033[0m \n");
	printf("\t\033[47;31m*                   4、禁言                         *\033[0m \n");
	printf("\t\033[47;31m*                   5、解除禁言                     *\033[0m \n");
	printf("\t\033[47;31m*                   6、踢人                         *\033[0m \n");
	printf("\t\033[47;31m*                   7、传输文件                     *\033[0m \n");
	printf("\t\033[47;31m*                   8、下线                         *\033[0m \n");
	printf("\t\033[47;31m*****************************************************\033[0m \n");
	printf("请输入选项:\n");
}

chatroom.h

#ifndef CHAT_ROOM_H
#define CHAT_ROOM_H

void Chat_room(int sockfd, info *chat_buf);//普通聊天室界面
void PrivateChat(int sockfd, info *chat_buf);//私聊操作
void PrivateRoom(int sockfd,info *chat_buf);//私聊窗口
void GroupChat(int sockfd, info *chat_buf);//群聊
void OnlineNum(int sockfd, info *chat_buf);//查看在线人数
void LogOut(int sockfd, info *chat_buf);//下线
void ChoiceVip(int sockfd, info *chat_buf);//注册会员

#endif

chatroom.c

#include "../chat.h"
#include "sys.h"
#include "chatroom.h"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

pthread_t tid;//子线程号
int globalsockfd;//套接字(全局)
int flag = 0;
/*
	由于采用两个线程实现客户端的收发信息(主线程发信息、子线程收信息),
	为了防止收发数据包时数据包中某些成员的改变,引起客户端出现意想不到的bug
	所以设置了一个全局的数据包,其作用是保存每个客户端的重要信息,比如:账号、
	密码、vip标志位、禁言标志位等等,每次发信息时都需用该数据包进行校验工作。
*/
info global_chat;//数据包(全局)

void *RecvThread(void *arg);



/*
时间:
作者:
函数名:PrivateChat(私聊操作)
函数功能:私聊,实现两个客户端之间的私人聊天
形参:无
返回值:无
*/
void PrivateChat(int sockfd, info *chat_buf)
{
	if(global_chat.forbid == 1)
	{
		printf("你已被禁言,不能发消息!\n");
		sleep(1);
	}
	
	else
	{	
		printf("请输入对方id\n");
		scanf("%s",chat_buf->toid);
	
		//私聊界面
		PrivateRoom(sockfd,chat_buf);
	}
}


/*
时间:
作者:
函数名:PrivateRoom(私聊界面)
函数功能:私聊
形参:sockfd(套接字)  指向数据包的指针
返回值:无
*/
void PrivateRoom(int sockfd,info *chat_buf)
{
	//入参判断//////////////////////////
	if(NULL == chat_buf)
	{
		printf("PrivateRoom 错误\n");
		sleep(1);
		return;
	}
	////////////////////////////////////
	int ret;
	while(1)
	{
		//私聊窗口
		private_menu();
		
		//发信息前先更新自己的数据包
		replace_chat(chat_buf, &global_chat);
		
		//输入聊天内容
		scanf("%s",chat_buf->text);
		
		//遇到退出关键字退出私聊
		if(!strcmp(chat_buf->text, "byebyb"))
		{
			break;
		}
		
		//将命令设置为私聊
		chat_buf->command = PRIVATE;//私聊
	
		//将信息发给服务器
		ret = send(sockfd, chat_buf, sizeof(info), 0);
		if(-1 == ret)
		{
			perror("PrivateRoom send");
		}
	}
}



/*
时间:
作者:
函数名:LogOut(下线)
函数功能:退出普通聊天室
形参:
返回值:无
*/
void LogOut(int sockfd, info *chat_buf)
{
	//更新自己的信息
	replace_chat(chat_buf,&global_chat);
	
	//下线
	chat_buf->command = LOGOUT;
	
	//将信息发给服务器
	int ret = send(sockfd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("LogOut send");
	}
	pthread_cancel(tid);
	
}

/*
时间:
作者:
函数名:OnlineNum(查看在线人数)
函数功能:向服务器发送查看在线人数的命令
形参:sockfd(套接字),指向数据包的指针
返回值:无
*/
void OnlineNum(int sockfd, info *chat_buf)
{
	if(NULL == chat_buf)
	{
		return;
	}
	
	//更新自己的信息
	replace_chat(chat_buf,&global_chat);
	
	//查看在线人数命令
	chat_buf->command = ONLINENUM;
	
	//将信息发给服务器
	int ret = send(sockfd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("OnlineNum send");
	}

}

/*
时间:
作者:
函数名:GroupChat(群聊)
函数功能:将信息发给所有在线用户
形参:sockfd(套接字),指向数据包的指针
返回值:无
*/
void GroupChat(int sockfd, info *chat_buf)
{
	if(NULL == chat_buf)
	{
		return;
	}
	
	//更新自己的信息
	replace_chat(chat_buf,&global_chat);
	
	if(global_chat.forbid == 1)
	{
		printf("你已被禁言,不能发消息!\n");
		sleep(1);
	}
	
	else
	{
		printf("请输入内容\n");
		scanf("%s", chat_buf->text);
	
		//群聊命令
		chat_buf->command = GROUP;

		//将信息发给服务器
		int ret = send(sockfd, chat_buf, sizeof(info), 0);
		//printf("GroupChat212\n");
		if(-1 == ret)
		{
		perror("GroupChat send");
		}
	}
}


/*
时间:
作者:
函数名:ChoiceVip(注册会员)
函数功能:注册会员(),判断用户是否需要注册会员
形参:sockfd(套接字),指向数据包的指针
返回值:无
*/
void ChoiceVip(int sockfd, info *chat_buf)
{
	//入参判断//////////////////
	if(NULL == chat_buf)
	{
		return;
	}
	////////////////////////////
	
	//vip标志置为1
	global_chat.vipflag = 1;
	
	//更新自己的信息
	replace_chat(chat_buf,&global_chat);
	chat_buf->command = VIP;
		
	//将信息发给服务器
	int ret = send(sockfd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("ChoiceVip send");
	}
	
}


/*
时间:
作者:
函数名:Forbid
函数功能:会员用户指定某用户禁言。
形参:sockfd(套接字),指向数据包的指针
返回值:无
*/
void Forbid(int sockfd, info *chat_buf)
{
	//入参判断////////////////////
	if(NULL == chat_buf)
	{
		return;
	}
	//////////////////////////////
	
	//更新自己的信息
	replace_chat(chat_buf,&global_chat);
	printf("请输入要禁言的id\n");
	scanf("%s", chat_buf->toid);
	//禁言命令
	chat_buf->command = FORBID;
	
	//将信息发给服务器
	int ret = send(sockfd, chat_buf, sizeof(info), 0);
	//printf("311\n");
	if(-1 == ret)
	{
		perror("Forbid send");
	}
}


/*
时间:
作者:
函数名:Relieve
函数功能:会员用户指定某用户解除禁言。
形参:sockfd(套接字),指向数据包的指针
返回值:无
*/
void Relieve(int sockfd, info *chat_buf)
{
	if(NULL == chat_buf)
	{
		return;
	}
	//更新自己的信息
	replace_chat(chat_buf,&global_chat);
	printf("请输入要解除禁言的id\n");
	scanf("%s", chat_buf->toid);
	//禁言命令
	chat_buf->command = RELIEVE;
	
	//将信息发给服务器
	int ret = send(sockfd, chat_buf, sizeof(info), 0);
	//printf("345\n");
	if(-1 == ret)
	{
		perror("Forbid send");
	}

}


/*
时间:
作者:
函数名:Kick
函数功能:会员用户指定某用户退出。
形参:sockfd(套接字),指向数据包的指针
返回值:无
*/
void Kick(int sockfd, info *chat_buf)
{
	if(NULL == chat_buf)
	{
		return;
	}
	printf("请输入要踢掉的id\n");
	scanf("%s", chat_buf->toid);
	chat_buf->command = KICK;
	int ret = send(sockfd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("Kick send ");
	}
}


/*
时间:
作者:
函数名:meme
函数功能:显示表情包。
形参:指向数据包的指针
返回值:无
*/
void meme(info *chat_buf)
{
	if(!strcmp(chat_buf->text, ":)"))
	{
		printf("\t\t%s 向你微笑\n", chat_buf->nick);
	}
	else if(!strcmp(chat_buf->text, ":hh"))
	{
		printf("\t\t%s :哈哈哈哈!\n", chat_buf->nick);
	}
	else if(!strcmp(chat_buf->text, ":gogo"))
	{
		printf("\t\t%s : 冲冲冲!\n", chat_buf->nick);
	}
	else if(!strcmp(chat_buf->text, ":jiayou"))
	{
		printf("\t\t%s : (0>V<0)\n", chat_buf->nick);
	}
	else if(!strcmp(chat_buf->text, ":cold"))
	{
		printf("\t\%s : {{{(+^+)}}}\n", chat_buf->nick);
	}
	else if(!strcmp(chat_buf->text, ":hen"))
	{
		printf("\t\t%s : (o|^|o)\n", chat_buf->nick);
	}
	else if(!strcmp(chat_buf->text, ":fish"))
	{
		printf("\t\t%s : <.))))><\n", chat_buf->nick);
	}

}


/*
时间:
作者:
函数名:Ftp
函数功能:传输文件
形参:  套接字 指向数据包的指针
返回值:无
*/
void Ftp(int sockfd, info *chat_buf)
{
	if(NULL == chat_buf)
	{
		return;
	}
	
	printf("请输入对方id\n");
	scanf("%s", chat_buf->toid);
	printf("请输入文件名\n");	
	scanf("%s", chat_buf->file);
	
	//只读方式打开文件
	int fd = open(chat_buf->file, O_RDONLY);
	if(-1 == fd)
	{
		perror("open");
	}
	//发送文件名
	chat_buf->command = FILE_NAME;
	//发送文件名
	int ret = send(sockfd, chat_buf, sizeof(info), 0);
	if(-1 == ret)
	{
		perror("Ftp send 1");
	}
	
	//发送文件
	chat_buf->command = FILE_BUF;
	while(1)
	{
		ret = read(fd, chat_buf->file_buf, sizeof(chat_buf->file_buf) - 1);
		if(-1 == ret)
		{
			perror("Ftp read");
		}

		if(ret == 0)////发送完毕
		{
			printf("文件传输完毕!\n");
			strcpy(chat_buf->file_buf, "bye!!!");
			ret = send(sockfd, chat_buf, sizeof(info), 0);
			//printf("345\n");
			if(-1 == ret)
			{
				perror("Forbid send");
			}
			
			close(fd);
			break;
		}
		ret = send(sockfd, chat_buf, sizeof(info), 0);
		//printf("345\n");
		if(-1 == ret)
		{
			perror("Forbid send");
		}
		bzero(chat_buf->file_buf, sizeof(chat_buf->file_buf));
	}
	
}


/*
时间:
作者:
函数名:FileName
函数功能:接收文件名
形参:  指向数据包的指针
返回值:文件描述符
*/
int FileName(info *pChat)
{
	if(NULL == pChat)
	{
		return 0;
	}
	
	/////创建并打开文件///////////////////////////////////
	int fd = open(pChat->file, O_WRONLY | O_CREAT | O_EXCL);
	if(-1 == fd)
	{
		perror("open");
	}
	return fd;
}


/*
时间:
作者:
函数名:FileBuf
函数功能:接收文件
形参:  指向数据包的指针  文件描述符
返回值:无
*/
void FileBuf(info *pChat, int fd)
{
	if(NULL == pChat)
	{
		return;
	}
	if(!strcmp(pChat->file_buf, "bye!!!"))
	{
		printf("文件接受完毕!\n");
		sleep(1);
		close(fd);
		return;
	}
	int ret = write(fd, pChat->file_buf, strlen(pChat->file_buf));
	if(-1 == ret)
	{
		perror("write");
	}	
	
}


/*
日期:
作者:
功能:普通用户聊天界面
形参:sockfd(套接字),指向数据包的指针
返回值:无
*/
//主线程用于发送消息
void Chat_room(int sockfd, info *chat_buf)
{
	
	if(NULL == chat_buf)
	{
		return;
	}
	globalsockfd = sockfd;
	
	//给全局数据包赋值
	replace_chat(&global_chat, chat_buf);
	
	
	int ret;
	//char buf;
	//创建线程用于接收消息
	//创建线程   线程号    线程属性  线程函数   线程函数参数
	ret = pthread_create(&tid, NULL, RecvThread, (void *)chat_buf);
	if(ret != 0)
	{
		perror("pthread_create");
	}
	
	
	int choice;
	char scan;
	flag = 0;
	
	while(1)
	{
		if(global_chat.vipflag == 1)
		{
			if(flag == 1)
			{
				break;
			}
			
			vip_menu();
			scanf("%d", &choice);
			if(flag == 1)
			{
				break;
			}
			switch(choice)
			{
			
				case 1:      //私聊
					PrivateChat(sockfd, chat_buf);
					break;
			
				case 2:      //群聊
					GroupChat(sockfd, chat_buf);
					break;
			
				case 3:      //查看在线人数
					OnlineNum(sockfd, chat_buf);
					sleep(1);
					break;
			
				case 4:      //禁言
					Forbid(sockfd, chat_buf);
					break;
			
				case 5:     //解除禁言
					Relieve(sockfd, chat_buf);
					break;
			
				case 6:      //踢人
					Kick(sockfd, chat_buf);
					break;
			
				case 8:      //下线
					LogOut(sockfd, chat_buf);
					flag = 1;
					break;
				
				case 7:      //传输文件
					Ftp(sockfd, chat_buf);
					break;
				
				default:
					break;
			}
		}
		
		else
		{
			if(flag == 1)
			{
				break;
			}
			menu2();
			scanf("%d", &choice);
			if(flag == 1)
			{
				break;
			}
			switch(choice)
			{
				case 1:					//私聊
					PrivateChat(sockfd, chat_buf);
					break;
			
				case 2:			//群聊
					GroupChat(sockfd, chat_buf);
					break;
			
				case 3:			//查看在线人数
					OnlineNum(sockfd, chat_buf);
					sleep(1);
				
					break;

				case 6:			//下线
					LogOut(sockfd, chat_buf);
					flag = 1;
					break;
			
				case 4:         //升级为会员
					printf("成为会员需要100RMB,是否成为会员?y or n \n");
					getchar();
					scanf("%c", &scan);
					//printf("scan = %c\n",scan);
					if(scan == 'y')
					{
						ChoiceVip(sockfd, chat_buf);
					}
					break;
				
				case 5:         //传输文件
					Ftp(sockfd, chat_buf);
					break;
					
				default:
					break;
			}
		}
		if(flag == 1)
		{
			break;
		}
	}
	void *status;
	//等待线程结束, 回收线程资源
	pthread_join(tid, &status);
	return;
}


/////////////子线程,用于客户端消息的接收///////////////////////////////////////////////////////////////////////////
/*
时间:
作者:
函数名:RecvThread
函数功能:子线程,用于客户端消息的接收
形参:指向空的指针
返回值:无
*/
void *RecvThread(void *arg)
{
	info *chat_buf = (info *)arg;
	int ret,old;
	int ch = 0;
	//int ad;
	int fd;
	//pthread_detach(pthread_self());//线程分离,线程结束后自动释放线程资源
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);//属性修改为立即执行
	
	while(1)
	{
		ret = recv(globalsockfd, chat_buf, sizeof(info),0);//接收服务器发来的数据包
		if(-1 == ret)
		{
			perror("RecvThread recv");
		}
		
		switch(chat_buf->retcommand)
		{
			case RETURN_CHAT:        //输出聊天内容
				if(!strncmp(chat_buf->text, ":", 1))
				{
					meme(chat_buf);
				}
				else
				{
					printf("\t\t %s say :%s\n",chat_buf->nick, chat_buf->text);
				}
				
				break;
			
			case RETURN_ONLINENUM:   //输出在线人数
				
				printf("%d人在线\n",chat_buf->onlinenum);
				break;
			
			case RETURN_GROUP:        //输出聊天内容(群聊)
			
				if(!strncmp(chat_buf->text, ":", 1))
				{
					meme(chat_buf);
				}
				else
				{
					printf("\t\033[47;31m**** %s say :%s****\033[0m \t\t\n", chat_buf->nick, chat_buf->text);
					sleep(1);
				}
				break;
			
			case RETURN_FORBID:		 //被禁言
				printf("61\n");
				global_chat.forbid = 1;
				printf("你已被禁言!\n");
				sleep(1);
				break;
				
			case RETURN_RELIEVE:     //解除禁言
				global_chat.forbid = 0;
				printf("你被解除禁言!\n");
				sleep(1);
				break;
				
			case RETURN_KICK:	     //踢人
				printf("你已被踢出\n");
				printf("517\n");
				//sleep(1);
				flag = 1;
				printf("520\n");
				ch = 1;
				printf("522\n");
				break;
				
			case RETURN_FILE_NAME:   //传输文件名
				fd = FileName(chat_buf);
				break;
			
			case RETURN_FILE_BUF:    //传输文件
				FileBuf(chat_buf, fd);
				break;
				
		}
		memset(chat_buf->text, 0, sizeof(chat_buf->text));	//清空text
		chat_buf->retcommand = 0;//清空返回命令
		
		if(1 == ch)
		{
			break;
		}
	}

}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值