Linux聊天室项目 -- ChatRome(select实现)

项目简介:采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室!

OS:Ubuntu 15.04

IDE:vim gcc make

DB:Sqlite 3文章地址https://www.yii666.com/article/754260.html

Time:2015-12-09 ~ 2012-12-21

项目功能架构:

  1. 采用client/server结构;
  2. 给出客户操作主界面(注册、登录、帮助和退出)、登录后主界面(查看在线列表、私聊、群聊、查看聊天记录、退出);
  3. 多客户可同时连接服务器进行自己操作;

部分操作如下图所示:

  1. 多用户注册:

    (1)服务器监听终端

    Linux聊天室项目 -- ChatRome(select实现)

    (2)用户注册终端

    Linux聊天室项目 -- ChatRome(select实现)

  2. 多用户登录、聊天:

    (1)用户yy登录

    Linux聊天室项目 -- ChatRome(select实现)

    Linux聊天室项目 -- ChatRome(select实现)

    (2)用户rr登录

    Linux聊天室项目 -- ChatRome(select实现)

    (3)服务器监听终端

    Linux聊天室项目 -- ChatRome(select实现)

程序结构

公共数据库

chatRome.db

服务器端

Linux聊天室项目 -- ChatRome(select实现)文章来源地址:https://www.yii666.com/article/754260.html

  1. server.c:服务器端主程序代码文件;
  2. config.h:服务器端配置文件(包含需要的头文件、常量、数据结构及函数声明);
  3. config.c:服务器端公共函数的实现文件;
  4. list.c:链表实现文件,用于维护在线用户链表的添加、更新、删除操作;
  5. register.c:服务器端实现用户注册;
  6. login.c:服务器端实现用户登录;
  7. chat.c:服务器端实现用户的聊天互动操作;
  8. Makefile:服务器端make文件,控制台执行make命令可直接生成可执行文件server

客户端

Linux聊天室项目 -- ChatRome(select实现)

1. client.c:客户端主程序代码文件;

2. config.h:客户端配置文件(包含需要的头文件、常量、数据结构及函数声明);

3. config.c:客户端公共函数的实现文件;

4. register.c:客户端实现用户注册;

5. login.c:客户端实现用户登录;

6. chat.c:客户端实现用户的聊天互动操作;

7. Makefile:客户端make文件,控制台执行make命令可直接生成可执行文件client;

8. interface.c:客户端界面文件;

源码

GitHub下ChatRome源码网址

CSDN资源下载

服务器端

server.c

/*******************************************************************************
* 服务器端程序代码server.c
* 2015-12-09 yrr实现
*
********************************************************************************/ #include "config.h" /*定义全局变量 -- 在线用户链表*/
ListNode *userList = NULL; /*********************************************
函数名:main
功能:聊天室服务器main函数入口
参数:无
返回值:正常退出返回 0 否则返回 1
**********************************************/
int main(void)
{
/*声明服务器监听描述符和客户链接描述符*/
int i , n , ret , maxi , maxfd , listenfd , connfd , sockfd; socklen_t clilen; pthread_t pid; /*套接字选项*/
int opt = 1; /*声明服务器地址和客户地址结构*/
struct sockaddr_in servaddr , cliaddr; /*声明描述符集*/
fd_set rset , allset;
//nready为当前可用的描述符数量
int nready , client_sockfd[FD_SETSIZE]; /*声明消息变量*/
Message message;
/*声明消息缓冲区*/
char buf[MAX_LINE]; /*UserInfo*/
User user; /*(1) 创建套接字*/
if((listenfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
{
perror("socket error.\n");
exit(1);
}//if /*(2) 初始化地址结构*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT); /*(3) 绑定套接字和端口*/
setsockopt(listenfd , SOL_SOCKET , SO_REUSEADDR , &opt , sizeof(opt)); if(bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
{
perror("bind error.\n");
exit(1);
}//if /*(4) 监听*/
if(listen(listenfd , LISTENEQ) < 0)
{
perror("listen error.\n");
exit(1);
}//if /*(5) 首先初始化客户端描述符集*/
maxfd = listenfd;
maxi = -1;
for(i=0; i<FD_SETSIZE; ++i)
{
client_sockfd[i] = -1;
}//for /*清空allset描述符集*/
FD_ZERO(&allset); /*将监听描述符加到allset中*/
FD_SET(listenfd , &allset); /*(6) 接收客户链接*/
while(1)
{
rset = allset;
/*得到当前可读的文件描述符数*/
nready = select(maxfd+1 , &rset , NULL , NULL , 0); /*测试listenfd是否在rset描述符集中*/
if(FD_ISSET(listenfd , &rset))
{
/*接收客户端的请求*/
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0)
{
perror("accept error.\n");
exit(1);
}//if printf("server: got connection from %s\n", inet_ntoa(cliaddr.sin_addr)); /*查找空闲位置,设置客户链接描述符*/
for(i=0; i<FD_SETSIZE; ++i)
{
if(client_sockfd[i] < 0)
{
client_sockfd[i] = connfd; /*将处理该客户端的链接套接字设置在该位置*/
break;
}//if
}//for if(i == FD_SETSIZE)
{
perror("too many connection.\n");
exit(1);
}//if /* 将来自客户的连接connfd加入描述符集 */
FD_SET(connfd , &allset); /*新的连接描述符 -- for select*/
if(connfd > maxfd)
maxfd = connfd; /*max index in client_sockfd[]*/
if(i > maxi)
maxi = i; /*no more readable descriptors*/
if(--nready <= 0)
continue;
}//if
/*接下来逐个处理连接描述符*/
for(i=0 ; i<=maxi ; ++i)
{
if((sockfd = client_sockfd[i]) < 0)
continue; if(FD_ISSET(sockfd , &rset))
{
/*如果当前没有可以读的套接字,退出循环*/
if(--nready < 0)
break;
pthread_create(&pid , NULL , (void *)handleRequest , (void *)&sockfd); }//if
/*清除处理完的链接描述符*/
FD_CLR(sockfd , &allset);
client_sockfd[i] = -1;
}//for
}//while close(listenfd);
return 0;
} /*处理客户请求的线程*/
void* handleRequest(int *fd)
{
int sockfd , ret , n;
/*声明消息变量*/
Message message;
/*声明消息缓冲区*/
char buf[MAX_LINE]; sockfd = *fd; memset(buf , 0 , MAX_LINE);
memset(&message , 0 , sizeof(message)); //接收用户发送的消息
n = recv(sockfd , buf , sizeof(buf)+1 , 0);
if(n <= 0)
{
//关闭当前描述符,并清空描述符数组
fflush(stdout);
close(sockfd);
*fd = -1;
printf("来自%s的退出请求!\n", inet_ntoa(message.sendAddr.sin_addr));
return NULL;
}//if
else{
memcpy(&message , buf , sizeof(buf));
/*为每个客户操作链接创建一个线程*/
switch(message.msgType)
{
case REGISTER:
{
printf("来自%s的注册请求!\n", inet_ntoa(message.sendAddr.sin_addr));
ret = registerUser(&message , sockfd);
memset(&message , 0 , sizeof(message));
message.msgType = RESULT;
message.msgRet = ret;
strcpy(message.content , stateMsg(ret));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
/*发送操作结果消息*/
send(sockfd , buf , sizeof(buf) , 0);
printf("注册:%s\n", stateMsg(ret));
close(sockfd);
*fd = -1;
return NULL;
break;
}//case
case LOGIN:
{
printf("来自%s的登陆请求!\n", inet_ntoa(message.sendAddr.sin_addr));
ret = loginUser(&message , sockfd);
memset(&message , 0 , sizeof(message));
message.msgType = RESULT;
message.msgRet = ret;
strcpy(message.content , stateMsg(ret));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
/*发送操作结果消息*/
send(sockfd , buf , sizeof(buf) , 0);
printf("登录:%s\n", stateMsg(ret));
/*进入服务器处理聊天界面*/
enterChat(&sockfd);
break;
}//case
default:
printf("unknown operation.\n");
break;
}//switch
}//else close(sockfd);
*fd = -1;
return NULL;
}

config.h

/*******************************************************************************
* 基本配置文件 -- 包含所需头文件
* 用户信息结构体定义
* 在线用户链表定义
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <memory.h> /*使用memcpy所需的头文件*/ #include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h> #include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
#include <pthread.h> #include <sqlite3.h> /*FD_SETSIZE定义描述符集的大小,定义在sys/types.h中*/
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif #define MAX_LINE 8192
#define PORT 8888
#define LISTENEQ 6000 /*预定义数据库名称*/
#define DB_NAME "/home/yangrui/projects/Socket/ChatRome_select/chatRome.db" /*标志*/
enum Flag{
YES, /*代表被禁言*/
NO /*代表没有被禁言*/
}; /*定义服务器--客户端 消息传送类型*/
enum MessageType{
REGISTER = 1, /*注册请求*/
LOGIN, /*登陆请求*/
HELP, /*帮助请求*/
EXIT, /*退出请求*/
VIEW_USER_LIST, /*查看在线列表*/
GROUP_CHAT, /*群聊请求*/
PERSONAL_CHAT, /*私聊请求*/
VIEW_RECORDS, /*查看聊天记录请求*/
RESULT, /*结果消息类型*/
UNKONWN /*未知请求类型*/
}; /*定义操作结果 */
enum StateRet{
EXCEED, //已达服务器链接上限
SUCCESS, //成功
FAILED, //失败
DUPLICATEID, //重复的用户名
INVALID, //不合法的用户名
ID_NOT_EXIST, //账号不存在
WRONGPWD, //密码错误
ALREADY_ONLINE, //已经在线
ID_NOT_ONLINE, //账号不在线
ALL_NOT_ONLINE, //无人在线
MESSAGE_SELF //消息对象不能选择自己
}; /*定义服务器 -- 客户端 消息传送结构体*/
typedef struct _Message{
char content[2048]; /*针对聊天类型的消息,填充该字段*/
int msgType; /*消息类型 即为MessageType中的值*/
int msgRet; /*针对操作结果类型的消息,填充该字段*/
struct sockaddr_in sendAddr; /*发送者IP*/
struct sockaddr_in recvAddr;
char sendName[20]; /*发送者名称*/
char recvName[20]; /*接收者名称*/
char msgTime[20]; /*消息发送时间*/
}Message; //用户信息结构体
typedef struct _User{
char userName[20]; //用户名
char password[20];
struct sockaddr_in userAddr; //用户IP地址,选择IPV4
int sockfd; //当前用户套接字描述符
int speak; //是否禁言标志
char registerTime[20]; //记录用户注册时间
}User; /*定义用户链表结构体*/
typedef struct _ListNode{
User user;
struct _ListNode *next;
}ListNode; /*定义在线用户链表*/
extern ListNode *userList; /*server.c 客户请求处理函数*/
extern void* handleRequest(int *fd); /*config.c文件函数声明*/
extern char *stateMsg(int stateRet);
extern void copyUser(User *user1 , User *user2); /*chat.c文件函数声明*/
extern void enterChat(int *fd);
extern int groupChat(Message *msg , int sockfd);
extern int personalChat(Message *msg , int sockfd);
extern int viewUserList(Message *msg , int sockfd);
extern int viewRecords(Message *msg , int sockfd); /*list.c文件函数声明*/
extern ListNode* insertNode(ListNode *list , User *user);
extern int isOnLine(ListNode *list , User *user);
extern void deleteNode(ListNode *list , User *user);
extern void displayList(ListNode *list); /*login.c文件函数声明*/
extern int loginUser(Message *msg , int sockfd); /*register.c文件函数声明*/
extern int registerUser(Message *msg , int sockfd);

config.c

/*******************************************************************************
* 基本配置文件实现 -- 包含所需头文件
* 用户信息结构体定义
* 在线用户链表定义
********************************************************************************/
#include "config.h" /*************************************
函数名:StateMsg
功能:根据操作结果得到相应的消息内容
参数:stateRet -- 操作结果整数值
返回值:操作结果字符串
**************************************/
char *stateMsg(int stateRet)
{
switch(stateRet)
{
case EXCEED://已达服务器链接上限
return "已达服务器链接上限!\n";
break;
case SUCCESS: //成功
return "操作成功!\n";
break;
case FAILED: //失败
return "操作失败!\n";
break;
case DUPLICATEID: //重复的用户名
return "重复的用户名!\n";
break;
case INVALID: //不合法的用户名
return "不合法输入!\n";
break;
case ID_NOT_EXIST: //账号不存在
return "账号不存在!\n";
break;
case WRONGPWD: //密码错误
return "密码错误!\n";
break;
case ALREADY_ONLINE:
return "该用户已在线!\n";
break;
case ID_NOT_ONLINE:
return "该用户不在线!\n";
break;
case ALL_NOT_ONLINE:
return "无人在线!\n";
break;
case MESSAGE_SELF: //消息对象不能选择自己
return "不能给自己发送消息\n";
break;
default:
return "未知操作结果!\n";
break;
}//switch
}; /*************************************
函数名:copyUser
功能:用户结构体对象拷贝操作
参数:user1--目标拷贝对象 user2--源拷贝对象
返回值:无
**************************************/
void copyUser(User *user1 , User *user2)
{
strcpy((*user1).userName , (*user2).userName);
strcpy((*user1).password , (*user2).password);
(*user1).userAddr = (*user2).userAddr;
(*user1).sockfd = (*user2).sockfd;
(*user1).speak = (*user2).speak;
strcpy((*user2).registerTime , (*user2).registerTime); }

list.c

/*******************************************************************************
* 服务器端 在线客户 链表结构与操作
* 2015-12-14 yrr实现
*
********************************************************************************/ #include "config.h" /****************************************************
函数名:insertNode
功能:插入在线用户链表新节点
参数:list--当前在线用户链表 elem--要插入的元素
返回值:返回创建的链表
***************************************************/
ListNode* insertNode(ListNode *list , User *user)
{
/*建立新节点*/
ListNode *node = (ListNode *)calloc(1, sizeof(ListNode)); copyUser(&(node->user) , user); node->next = NULL;
if(list == NULL)
{
list = node;
}//if
else{
ListNode *p = list;
while(p->next != NULL)
{
p = p->next;
}//while
p->next = node;
}//else printf("更新在线列表!\n");
return list;
} /****************************************************
函数名:isOnLine
功能:查看某用户是否在线
参数:list--当前在线用户链表 elem--要查看的用户元素
返回值:true or false
***************************************************/
int isOnLine(ListNode *list , User *user)
{
ListNode *p = list , *pre = p;
while(p!=NULL && strcmp(p->user.userName , (*user).userName) != 0)
{
pre = p;
p = p->next;
}//while /*不存在该在线用户*/
if(p == NULL)
return 0;
return 1;
} /****************************************************
函数名:deleteNode
功能:删除在线用户链表指定节点
参数:list--当前在线用户链表 elem--要删除的元素
返回值:返回创建的链表
*****************************************************/
void deleteNode(ListNode *list , User *user)
{
if(list == NULL)
return; ListNode *p = list , *pre = p;
while(p!=NULL && strcmp(p->user.userName , (*user).userName) != 0)
{
pre = p;
p = p->next;
}//while /*不存在该在线用户*/
if(p == NULL)
return ;
/*该用户位于链表头部*/
else if(p == list)
{
list = list->next;
}//elif
/*该用户位于链表尾部*/
else if(p->next == NULL)
{
pre->next = NULL;
}//elif
/*该用户节点位于链表中间*/
else
{
pre->next = p->next;
}//else
/*释放该用户节点占用的空间*/
free(p);
p = NULL;
} /****************************************************
函数名:displayList
功能:显示在线用户链表
参数:list--当前在线用户链表
返回值:返回创建的链表
*****************************************************/
void displayList(ListNode *list)
{
if(list == NULL)
return;
else
{
ListNode *p = list;
while(p->next != NULL)
{
printf("%s --> ", p->user.userName);
p = p->next;
}//while
printf("%s\n", p->user.userName);
}//else
}

register.c

/*******************************************************************************
* 服务器处理用户基本操作处理实现文件
* 2015-12-10 yrr实现
*
********************************************************************************/ #include "config.h" /*********************************************
函数名:registerUser
功能:用户注册函数实现
参数:msg--用户发送的注册消息 sockfd--套接字描述符
返回值:成功登陆返回SUCCESS 否则返回异常类型
**********************************************/
int registerUser(Message *msg , int sockfd)
{
int ret;
/*声明用户需要的注册信息*/
User user;
char buf[MAX_LINE]; /*声明数据库变量*/
sqlite3 *db;
sqlite3_stmt *stmt;
const char *tail; /*声明sql语句存储变量*/
char sql[128]; /*当前系统时间*/
time_t timeNow; /*存储操作结果消息*/
Message message; /*接收用户注册信息*/
recv(sockfd , buf , sizeof(user) , 0);
memset(&user , 0 , sizeof(user));
memcpy(&user , buf , sizeof(buf));
user.userAddr = (*msg).sendAddr;
user.sockfd = sockfd; if(strlen(user.userName) > 20)
{
return INVALID;
}//if /*(1)打开数据库*/
ret = sqlite3_open(DB_NAME, &db);
if(ret != SQLITE_OK)
{
printf("unable open database.\n");
return FAILED;
}//if
printf("Opened database successfully.\n"); /*(2)检查要注册用户名是否已存在?*/
memset(sql , 0 , sizeof(sql));
sprintf(sql , "select * from User where userName='%s';",(user.userName)); ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);
if(ret != SQLITE_OK)
{
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
printf("database select fail!\n");
return FAILED;
}//if
/*执行*/
ret = sqlite3_step(stmt);
//如果有数据则返回SQLITE_ROW,当到达末尾返回SQLITE_DONE
while (ret == SQLITE_ROW)
{
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
return FAILED;
}
/*销毁句柄,关闭数据库*/
sqlite3_finalize(stmt); /*执行插入操作*/
memset(sql , 0 , sizeof(sql));
time(&timeNow);
sprintf(sql , "insert into User(userName , password , userAddr , sockfd , speak , registerTime)\
values('%s','%s','%s',%d, %d , '%s');",user.userName , user.password ,
inet_ntoa(user.userAddr.sin_addr),user.sockfd , YES, asctime(gmtime(&timeNow))); ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);
if(ret != SQLITE_OK)
{
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
return FAILED;
}//if /*顺利注册*/
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
/*注册成功*/
return SUCCESS;
}

login.c

/*******************************************************************************
* 服务器处理用户基本操作处理实现文件
* 2015-12-14 yrr实现
*
********************************************************************************/ #include "config.h" /*声明全局变量 -- 在线用户链表*/
extern ListNode *userList; /**************************************************
函数名:loginUser
功能:用户登陆函数实现
参数:msg--用户发送的登陆消息 sockfd--套接字描述符
返回值:成功登陆返回SUCCESS 否则返回异常类型
****************************************************/
int loginUser(Message *msg , int sockfd)
{
int ret;
/*声明用户信息*/
User user;
char buf[MAX_LINE]; /*声明数据库变量*/
sqlite3 *db;
sqlite3_stmt *stmt;
const char *tail; /*声明sql语句存储变量*/
char sql[128]; /*存储操作结果消息*/
Message message; /*接收用户信息*/
recv(sockfd , buf , sizeof(user) , 0);
memset(&user , 0 , sizeof(user));
memcpy(&user , buf , sizeof(buf));
user.userAddr = (*msg).sendAddr;
user.sockfd = sockfd; /*查看在线用户列表,该用户是否已在线*/
if(isOnLine(userList , &user) == 1)
return ALREADY_ONLINE; /*(1)打开数据库*/
ret = sqlite3_open(DB_NAME, &db);
if(ret != SQLITE_OK)
{
printf("unable open database.\n");
return FAILED;
}//if /*(2)检查登陆用户名和密码*/
memset(sql , 0 , sizeof(sql));
sprintf(sql , "select * from User where userName='%s' and password='%s';",user.userName , user.password); ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);
if(ret != SQLITE_OK)
{
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
printf("database select fail!\n");
return FAILED;
}//if
/*执行*/
ret = sqlite3_step(stmt);
//如果有数据则返回SQLITE_ROW,当到达末尾返回SQLITE_DONE
while(ret == SQLITE_ROW)
{
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
ret = SUCCESS;
/*如果登陆操作成功,添加到在线用户链表*/
userList = insertNode(userList , &user);
return ret;
}//while
/*销毁句柄,关闭数据库*/
sqlite3_finalize(stmt);
sqlite3_close(db); return FAILED;
}

chat.c

/*******************************************************************************
* 服务器处理用户聊天操作实现文件
* 2015-12-16 yrr实现
*
********************************************************************************/ #include "config.h" extern ListNode *userList; /**************************************************
函数名:groupChat
功能:群聊函数实现
参数:msg--用户发送的群聊消息 sockfd -- 发送者套接字
返回值:成功登陆返回SUCCESS 否则返回异常类型
****************************************************/
int groupChat(Message *msg , int sockfd)
{
ListNode *p; int ret; /*声明数据库变量*/
sqlite3 *db;
sqlite3_stmt *stmt;
const char *tail;
/*声明sql语句存储变量*/
char sql[128]; /*消息发送缓冲区*/
char buf[MAX_LINE];
/*消息内容*/
Message message;
memset(&message , 0 , sizeof(message));
strcpy(message.sendName , (*msg).sendName);
strcpy(message.recvName , (*msg).recvName);
message.msgType = (*msg).msgType; /*查看在线用户*/
p = userList;
/*除了自己无人在线*/
if(p->next == NULL)
{
/*改变消息类型为RESULT*/
message.msgType = RESULT;
strcpy(message.content, stateMsg(ALL_NOT_ONLINE));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
return ALL_NOT_ONLINE;
}//if
/*向所有在线用户发送消息*/
else
{
strcpy(message.recvName , "");
strcpy(message.content , (*msg).content);
strcpy(message.msgTime , (*msg).msgTime);
while(p!=NULL)
{
if(strcmp((p->user).userName , message.sendName) != 0)
{
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send((p->user).sockfd , buf , sizeof(buf) , 0);
}//else
p = p->next;
}//while
/*(1)打开数据库*/
ret = sqlite3_open(DB_NAME, &db);
if(ret != SQLITE_OK)
{
printf("unable open database!\n");
return FAILED;
}//if
/*(2)执行插入操作*/
memset(sql , 0 , sizeof(sql));
sprintf(sql , "insert into Message(msgType , sendName , recvName , content , msgTime)\
values(%d,'%s','%s','%s', '%s');",message.msgType , message.sendName ,
message.recvName,message.content , message.msgTime); ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);
if(ret != SQLITE_OK)
{
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
return FAILED;
}//if /*(3)顺利插入*/
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
/*群聊处理成功*/
return SUCCESS;
}//else
} /**************************************************
函数名:personalChat
功能:私聊函数实现
参数:msg--用户发送的群聊消息 sockfd -- 发送者套接字
返回值:成功登陆返回SUCCESS 否则返回异常类型
****************************************************/
int personalChat(Message *msg , int sockfd)
{
ListNode *p; int ret; /*声明数据库变量*/
sqlite3 *db;
sqlite3_stmt *stmt;
const char *tail;
/*声明sql语句存储变量*/
char sql[128]; /*消息发送缓冲区*/
char buf[MAX_LINE];
/*消息内容*/
Message message;
memset(&message , 0 , sizeof(message));
strcpy(message.sendName , (*msg).sendName);
strcpy(message.recvName , (*msg).recvName);
message.msgType = (*msg).msgType;
/*消息发送对象和接收对象相同*/
if(strcmp((*msg).sendName , (*msg).recvName) == 0)
{
printf("消息不能发送到自己!\n");
/*改变消息类型为RESULT*/
message.msgType = RESULT;
strcpy(message.content, stateMsg(MESSAGE_SELF));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
return MESSAGE_SELF;
}//if /*查找接收信息用户*/
p = userList;
while(p != NULL && strcmp((p->user).userName , (*msg).recvName) != 0)
{
p = p->next;
}//while if(p == NULL)
{
printf("该用户不在线!\n");
/*改变消息类型为RESULT*/
message.msgType = RESULT;
strcpy(message.content, stateMsg(ID_NOT_ONLINE));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
return ID_NOT_ONLINE;
}//if
else{
strcpy(message.content , (*msg).content);
strcpy(message.msgTime , (*msg).msgTime);
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send((p->user).sockfd , buf , sizeof(buf) , 0); /*写到数据库*/
/*(1)打开数据库*/
ret = sqlite3_open(DB_NAME, &db);
if(ret != SQLITE_OK)
{
printf("unable open database!\n");
return FAILED;
}//if
/*(2)执行插入操作*/
memset(sql , 0 , sizeof(sql));
sprintf(sql , "insert into Message(msgType , sendName , recvName , content , msgTime)\
values(%d,'%s','%s','%s', '%s');",message.msgType , message.sendName ,
message.recvName,message.content , message.msgTime);
printf("%s\n" , sql); ret = sqlite3_prepare(db , sql , strlen(sql) , &stmt , &tail);
if(ret != SQLITE_OK)
{
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
return FAILED;
}//if /*(3)顺利插入*/
ret = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
/*私聊处理成功*/
return SUCCESS;
}//else
} /**************************************************
函数名:viewUserList
功能:查看在线用户列表函数实现
参数:msg--用户发送的群聊消息 sockfd -- 发送者套接字
返回值:成功登陆返回SUCCESS 否则返回异常类型
****************************************************/
int viewUserList(Message *msg , int sockfd)
{
ListNode *p;
int ret; /*消息发送缓冲区*/
char buf[MAX_LINE];
/*消息内容*/
Message message;
memset(&message , 0 , sizeof(message));
strcpy(message.sendName , (*msg).sendName);
strcpy(message.recvName , (*msg).recvName);
message.msgType = (*msg).msgType; /*查看在线用户*/
p = userList;
if(p == NULL)
{
/*改变消息类型为RESULT*/
message.msgType = RESULT;
strcpy(message.content, stateMsg(ALL_NOT_ONLINE));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
return ALL_NOT_ONLINE;
}//if
else{
/*否则消息类型不变*/
strcpy(message.content , "");
while(p!=NULL)
{
strcat(message.content , "\t");
strcat(message.content , (p->user).userName); p = p->next;
}//while
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
printf("查看在线列表结果:%s\n", message.content);
}
return SUCCESS;
} /**************************************************
函数名:viewUserList
功能:查看聊天记录
参数:msg--用户发送的群聊消息 sockfd -- 发送者套接字
返回值:成功登陆返回SUCCESS 否则返回异常类型
****************************************************/
int viewRecords(Message *msg , int sockfd)
{
int ret; char buf[MAX_LINE] , record[MAX_LINE]; /*声明数据库变量*/
sqlite3 *db;
char *errmsg = NULL;
char **dbRet;
int nRow , nCol , i , j , idx; /*声明sql语句存储变量*/
char sql[128]; /*存储操作结果消息*/
Message message;
memset(&message , 0 , sizeof(message));
strcpy(message.sendName , (*msg).sendName);
/*判断是否接收群消息*/
if(strcmp( (*msg).recvName , "all") == 0)
strcpy(message.recvName , "");
else
strcpy(message.recvName , (*msg).recvName);
message.msgType = (*msg).msgType; /*(1)打开数据库*/
ret = sqlite3_open(DB_NAME, &db);
if(ret != SQLITE_OK)
{
printf("unable open database.\n");
/*改变消息类型为RESULT*/
message.msgType = RESULT;
strcpy(message.content, stateMsg(FAILED));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0); return FAILED;
}//if /*(2)读出两者的聊天记录,以二进制方式*/
memset(sql , 0 , sizeof(sql));
if(strcmp(message.recvName , "") == 0)
sprintf(sql , "select * from Message where recvName='%s' order by msgTime;",message.recvName);
else
sprintf(sql , "select * from Message where sendName='%s' and recvName='%s' or sendName='%s' and recvName='%s' order by msgTime;",message.sendName , message.recvName , message.recvName , message.sendName); ret = sqlite3_get_table(db , sql , &dbRet , &nRow , &nCol , &errmsg);
/*查询不成功*/
if(ret != SQLITE_OK)
{
sqlite3_close(db);
printf("database select fail!\n");
/*改变消息类型为RESULT*/
message.msgType = RESULT;
strcpy(message.content, stateMsg(FAILED));
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
return FAILED;
}//if /*查询成功,dbRet 前面第一行数据是字段名称,从 nColumn 索引开始才是真正的数据*/
idx = nCol;
for(i=0; i<nRow; ++i)
{
memset(record , 0 , MAX_LINE);
sprintf(record , "%s\t%s\n\t%s\n\n", dbRet[idx+1] , dbRet[idx+4] , dbRet[idx+3]);
//printf("第%d条记录:%s\n",i,record);
idx = idx + nCol;
strcat(message.content , record);
}//for
message.content[strlen(message.content)-1] = '\0';
/*关闭数据库*/
sqlite3_close(db); /*直接发送控制台*/
memset(buf , 0 , MAX_LINE);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
return SUCCESS;
} /**************************************************
函数名:enterChat
功能:服务器处理登录成功函数实现
参数:sockfd -- 用户套接字
返回值:成功登陆返回SUCCESS 否则返回异常类型
****************************************************/
void enterChat(int *fd)
{
int n,ret,sockfd;
User user;
/*消息发送缓冲区*/
char buf[MAX_LINE];
memset(buf , 0 , MAX_LINE); /*消息内容*/
Message message;
memset(&message , 0 , sizeof(message)); sockfd = *fd; while(1)
{
//接收用户发送的消息
n = recv(sockfd , buf , sizeof(buf)+1 , 0);
//printf("enterChat n = %d\n" , n);
if(n == 0)
{
//关闭当前描述符
close(sockfd);
return ;
}//if
else{
memcpy(&message , buf , sizeof(buf));
//printf("server msgType = %d\n" , message.msgType);
switch(message.msgType)
{
case GROUP_CHAT:
{
printf("来自%s的群聊请求!\n", message.sendName);
/*转到群聊处理函数*/
ret = groupChat(&message , sockfd);
printf("群聊:%s\n", stateMsg(ret));
break;
}
case PERSONAL_CHAT:
{
printf("来自%s的私聊请求!\n", message.sendName);
/*转到私聊处理函数*/
ret = personalChat(&message , sockfd);
printf("私聊:%s\n", stateMsg(ret));
}
break;
case VIEW_USER_LIST:
{
printf("来自%s的查看在线用户列表请求!\n", message.sendName);
/*转到查看在线用户列表处理函数*/
ret = viewUserList(&message , sockfd);
printf("查看在线列表:%s\n", stateMsg(ret));
break;
}
case VIEW_RECORDS:
{
printf("来自%s的查看聊天记录的请求!\n", message.sendName);
ret = viewRecords(&message , sockfd);
printf("查看聊天记录:%s\n", stateMsg(ret));
break;
}
case EXIT:
{
/*用户退出聊天室*/
printf("用户%s退出聊天室!\n", message.sendName);
memset(&user , 0 , sizeof(user));
strcpy(user.userName , message.sendName);
deleteNode(userList , &user);
close(sockfd);
return;
}
default:
break;
}//switch
}//else
}//while
return ;
}

Makefile

MYNAME = makefile
CC = gcc objects = server.o register.o login.o list.o config.o chat.o server: $(objects)
cc -g -o server $(objects) -lsqlite3 -lpthread server.o: server.c config.h
cc -c server.c register.o: register.c config.h
cc -c register.c login.o: login.c config.h
cc -c login.c list.o: list.c config.h
cc -c list.c config.o: config.c config.h
cc -c config.c chat.o: chat.c config.h
cc -c chat.c
#比较稳健的clean做法,表示clean是一个伪目标
.PHONY: clean #前面-的意思是:也许某些文件出现问题,忽略,继续执行
clean:
-rm server $(objects)

客户端

client.c

/*******************************************************************************
* 客户端程序代码server.c
* 2015-12-09 yrr实现
*
********************************************************************************/ #include "config.h" /*********************************************
函数名:main
功能:聊天室客户端main函数入口
参数:参数个数argc 用户链接地址argv
返回值:正常退出返回 0 否则返回 1
**********************************************/
int main(int argc , char *argv[])
{
int sockfd , choice , ret; //choice代表用户在主界面所做选择,ret代表操作结果
struct sockaddr_in servaddr;
struct hostent *host; /*声明消息变量*/
Message message;
/*声明消息缓冲区*/
char buf[MAX_LINE]; /*UserInfo*/
User user;
strcpy(user.userName , "***");
user.speak = 1; /*判断是否为合法输入*/
if(argc != 2)
{
perror("usage:tcpcli <IPaddress>");
exit(1);
}//if while(1)
{
/*(1) 创建套接字*/
if((sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
{
perror("socket error");
exit(1);
}//if /*(2) 设置链接服务器地址结构*/
bzero(&servaddr , sizeof(servaddr)); /*清空地址结构*/
servaddr.sin_family = AF_INET; /*使用IPV4通信域*/
servaddr.sin_port = htons(PORT); /*端口号转换为网络字节序*/
//servaddr.sin_addr = *((struct in_addr *)host->h_addr); /*可接受任意地址*/
if(inet_pton(AF_INET , argv[1] , &servaddr.sin_addr) < 0)
{
printf("inet_pton error for %s\n",argv[1]);
exit(1);
}//if /*(3) 发送链接服务器请求*/
if( connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
{
perror("connect error");
exit(1);
}//if /*(4) 显示聊天室主界面*/
mainInterface();
setbuf(stdin,NULL); //是linux中的C函数,主要用于打开和关闭缓冲机制
scanf("%d",&choice);
setbuf(stdin,NULL);
while(choice != 1 && choice != 2 && choice != 3 && choice !=4)
{
printf("未找到命令,请重新输入!\n");
setbuf(stdin,NULL); //是linux中的C函数,主要用于打开和关闭缓冲机制
scanf("%d",&choice);
setbuf(stdin,NULL);
}//while /*清空缓冲区*/
switch(choice)
{
case REGISTER: /*注册请求*/
memset(&message , 0 , sizeof(message));
memset(buf , 0 , MAX_LINE);
message.msgType = REGISTER;
strcpy(message.content , "");
message.sendAddr = servaddr;
/*首先向服务器发送注册请求*/
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
registerUser(sockfd);
//goto sign;
break;
case LOGIN: /*登陆请求*/
memset(&message , 0 , sizeof(message));
memset(buf , 0 , MAX_LINE);
message.msgType = LOGIN;
strcpy(message.content , "");
message.sendAddr = servaddr;
/*向服务器发送登陆请求*/
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
loginUser(sockfd);
break;
case HELP: /*帮助请求,显示帮助界面*/
helpInterface();
//goto sign;
break;
case EXIT:
close(sockfd);
printf("退出聊天室!\n");
exit(0); /*用户退出*/
break;
default:
printf("unknown operation.\n");
//goto sign;
break;
}//switch
}//while
close(sockfd);
return 0;
}

config.h

/*******************************************************************************
* 基本配置文件 -- 包含所需头文件
* 用户信息结构体定义
* 在线用户链表定义
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <memory.h> /*使用memcpy所需的头文件*/ #include <time.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h> #include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
#include <pthread.h> #include <sqlite3.h> /*FD_SETSIZE定义描述符集的大小,定义在sys/types.h中*/
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif #define MAX_LINE 8192
#define PORT 8888
#define LISTENEQ 6000 /*预定义数据库名称*/
#define DB_NAME "/home/yangrui/projects/Socket/ChatRome_select/chatRome.db" /*标志*/
enum Flag{
YES, /*代表被禁言*/
NO /*代表没有被禁言*/
}; /*定义服务器--客户端 消息传送类型*/
enum MessageType{
REGISTER = 1, /*注册请求*/
LOGIN, /*登陆请求*/
HELP, /*帮助请求*/
EXIT, /*退出请求*/
VIEW_USER_LIST, /*查看在线列表*/
GROUP_CHAT, /*群聊请求*/
PERSONAL_CHAT, /*私聊请求*/
VIEW_RECORDS, /*查看聊天记录请求*/
RESULT, /*结果消息类型*/
UNKONWN /*未知请求类型*/
}; /*定义操作结果 */
enum StateRet{
EXCEED, //已达服务器链接上限
SUCCESS, //成功
FAILED, //失败
DUPLICATEID, //重复的用户名
INVALID, //不合法的用户名
ID_NOT_EXIST, //账号不存在
WRONGPWD, //密码错误
ALREADY_ONLINE, //已经在线
ID_NOT_ONLINE, //账号不在线
ALL_NOT_ONLINE, //无人在线
MESSAGE_SELF //消息对象不能选择自己
}; /*定义服务器 -- 客户端 消息传送结构体*/
typedef struct _Message{
char content[2048]; /*针对聊天类型的消息,填充该字段*/
int msgType; /*消息类型 即为MessageType中的值*/
int msgRet; /*针对操作结果类型的消息,填充该字段*/
struct sockaddr_in sendAddr; /*发送者IP*/
struct sockaddr_in recvAddr;
char sendName[20]; /*发送者名称*/
char recvName[20]; /*接收者名称*/
char msgTime[20]; /*消息发送时间*/
}Message; //用户信息结构体
typedef struct _User{
char userName[20]; //用户名
char password[20];
struct sockaddr_in userAddr; //用户IP地址,选择IPV4
int sockfd; //当前用户套接字描述符
int speak; //是否禁言标志
char registerTime[20]; //记录用户注册时间
}User; /*定义用户链表结构体*/
typedef struct _ListNode{
User user;
struct _ListNode *next;
}ListNode; /*定义在线用户链表*/
ListNode *userList; extern char *stateMsg(int stateRet);
extern void copyUser(User *user1 , User *user2);

config.c

/*******************************************************************************
* 基本配置文件实现 -- 包含所需头文件
* 用户信息结构体定义
* 在线用户链表定义
********************************************************************************/
#include "config.h" /*************************************
函数名:StateMsg
功能:根据操作结果得到相应的消息内容
参数:stateRet -- 操作结果整数值
返回值:操作结果字符串
**************************************/
char *stateMsg(int stateRet)
{
switch(stateRet)
{
case EXCEED://已达服务器链接上限
return "已达服务器链接上限!\n";
break;
case SUCCESS: //成功
return "操作成功!\n";
break;
case FAILED: //失败
return "操作失败!\n";
break;
case DUPLICATEID: //重复的用户名
return "重复的用户名!\n";
break;
case INVALID: //不合法的用户名
return "不合法输入!\n";
break;
case ID_NOT_EXIST: //账号不存在
return "账号不存在!\n";
break;
case WRONGPWD: //密码错误
return "密码错误!\n";
break;
case ALREADY_ONLINE:
return "该用户已在线!\n";
break;
case ID_NOT_ONLINE:
return "该用户不在线!\n";
break;
case ALL_NOT_ONLINE:
return "无人在线!\n";
break;
case MESSAGE_SELF: //消息对象不能选择自己
return "不能给自己发送消息\n";
break;
default:
return "未知操作结果!\n";
break;
}//switch
}; /*************************************
函数名:copyUser
功能:用户结构体对象拷贝操作
参数:user1--目标拷贝对象 user2--源拷贝对象
返回值:无
**************************************/
void copyUser(User *user1 , User *user2)
{
strcpy((*user1).userName , (*user2).userName);
strcpy((*user1).password , (*user2).password);
(*user1).userAddr = (*user2).userAddr;
(*user1).sockfd = (*user2).sockfd;
(*user1).speak = (*user2).speak;
strcpy((*user2).registerTime , (*user2).registerTime); }

interface.c

/*******************************************************************************
* 客户端界面设计
* 2015-12-15 yrr实现
*
********************************************************************************/ #include "config.h" /***************************************************
函数名:mainInterface
功能:登录界面
传入参数:无
返回值:无
***************************************************/
int mainInterface()
{ printf("-------------------------------------\n");
printf(" 欢迎进入小Q聊天室~ \n");
printf(" 1.注册 \n");
printf(" 2.登陆 \n");
printf(" 3.帮助 \n");
printf(" 4.退出 \n");
printf("-------------------------------------\n\n\n");
} /***************************************************
函数名:helpInterface
功能:主界面的帮助选项
传入参数:无
返回值:无
***************************************************/
int helpInterface()
{ printf("-------------------------------------\n");
printf(" 欢迎进入小帮助~ \n");
printf(" ^_^ \n");
printf(" 请在主界面选择操作~ \n");
printf(" ^_^ \n");
printf("-------------------------------------\n\n\n");
} /***************************************************
函数名:helpInterface
功能:主界面的帮助选项
传入参数:无
返回值:无
***************************************************/
void chatInterface(char userName[])
{
printf("------------------------------------------\n");
printf("你好,%s \n", userName);
printf(" 1. 查看在线用户 \n");
printf(" 2. 私聊 \n");
printf(" 3. 群聊 \n");
printf(" 4. 查看聊天记录 \n");
printf(" 5. 退出 \n");
printf("请选择操作~ \n");
printf("------------------------------------------\n\n\n");
}

register.c

/*******************************************************************************
* 客户端用户基本操作处理实现文件
* 2015-12-10 yrr实现
*
********************************************************************************/ #include "config.h" /*********************************************
函数名:registerUser
功能:用户注册函数实现
参数:套接字描述符
返回值:成功登陆返回SUCCESS 否则返回异常类型
**********************************************/
int registerUser(int sockfd)
{
int ret;
/*声明用户需要的注册信息*/
User user;
char buf[MAX_LINE];
Message message;
/*获取用户输入*/
printf("请输入注册用户名:\n");
memset(user.userName , 0 , sizeof(user.userName));
scanf("%s" , user.userName);
printf("user.UserName = %s\n" , user.userName); printf("请输入注册用户密码:\n");
memset(user.password , 0 , sizeof(user.password));
scanf("%s" , user.password);
printf("user.password = %s\n" , user.password);
//当前用户允许发言
user.speak = YES; memset(buf , 0 , MAX_LINE);
memcpy(buf , &user , sizeof(user));
send(sockfd , buf , sizeof(buf) , 0); /*接收注册结果*/
memset(buf , 0 , MAX_LINE);
recv(sockfd , buf , sizeof(buf) , 0);
memset(&message , 0 , sizeof(message));
memcpy(&message , buf , sizeof(buf)); printf("%s\n",message.content);
return message.msgRet;
}

login.c

/*******************************************************************************
* 客户端用户登陆处理实现文件
* 2015-12-14 yrr实现
*
********************************************************************************/ #include "config.h" /*********************************************
函数名:loginUser
功能:用户登陆函数实现
参数:套接字描述符
返回值:成功登陆返回SUCCESS 否则返回异常类型
**********************************************/
int loginUser(int sockfd)
{
int ret;
/*声明用户登陆信息*/
User user;
char buf[MAX_LINE];
Message message;
/*获取用户输入*/
printf("请输入用户名:\n");
memset(user.userName , 0 , sizeof(user.userName));
scanf("%s" , user.userName);
printf("user.UserName = %s\n" , user.userName); printf("请输入用户密码:\n");
memset(user.password , 0 , sizeof(user.password));
scanf("%s" , user.password);
printf("user.password = %s\n" , user.password); /*发送用户登陆信息到服务器*/
memset(buf , 0 , MAX_LINE);
memcpy(buf , &user , sizeof(user));
send(sockfd , buf , sizeof(buf) , 0); /*接收登陆结果*/
memset(buf , 0 , MAX_LINE);
recv(sockfd , buf , sizeof(buf) , 0);
memset(&message , 0 , sizeof(message));
memcpy(&message , buf , sizeof(buf)); printf("%s\n",message.content); /*如果登陆成功,则进入聊天界面*/
if(SUCCESS == message.msgRet)
{
enterChat(&user , sockfd);
}//if
return message.msgRet;
}

chat.c

/*******************************************************************************
* 客户端用户聊天界面处理实现文件
* 2015-12-14 yrr实现
*
********************************************************************************/ #include "config.h" /***********************************************
函数名:enterChat
功能:用户登陆成功后进入聊天模式
参数:user--当前用户 , sockfd -- 套接字描述符
返回值:正常退出返回 0 , 否则返回 1
*************************************************/
void recvMsg(int *sockfd)
{
int connfd = *sockfd;
int nRead; char buf[MAX_LINE] , str[MAX_LINE];
Message message; time_t timep; printf("^_^ 接收聊天信息中~\n");
while(1)
{
/*接收服务器发来的消息*/
nRead = recv(connfd , buf , sizeof(message) , 0);
/*recv函数返回值 <0 出错 =0 链接关闭 >0接收到的字节数*/
if(nRead <= 0)
{
printf("您已经异常掉线,请重新登录!\n");
close(connfd);
exit(0);
}//if memset(&message , 0 , sizeof(message));
memcpy(&message , buf , sizeof(buf)); switch(message.msgType)
{
case VIEW_USER_LIST:
printf("当前在线用户有:\n %s\n", message.content);
break;
case PERSONAL_CHAT:
sprintf(str , "%s \t %s \t对你说: %s\n", message.sendName , message.msgTime , message.content);
printf("\n%s\n", str);
break;
case GROUP_CHAT:
sprintf(str , "%s \t %s \t发送群消息: %s\n", message.sendName , message.msgTime , message.content);
printf("\n%s\n", str);
break;
case VIEW_RECORDS:
if(strcmp(message.recvName , "") == 0)
printf("你参与的群消息记录:\n\n");
else
printf("你和%s的聊天记录:\n\n", message.recvName);
printf("%s\n" , message.content);
break;
case RESULT:
printf("你的操作结果:%s\n", message.content);
default:
break;
}//switch
}//while
} /***********************************************
函数名:enterChat
功能:用户登陆成功后进入聊天模式
参数:user--当前用户 , sockfd -- 套接字描述符
返回值:正常退出返回 0 , 否则返回 1
*************************************************/
void enterChat(User *user , int sockfd)
{
int choice , ret;
char c , buf[MAX_LINE] , str[MAX_LINE];
Message message; /*消息对象*/
time_t timep; /*存储当前时间*/ pthread_t pid; /*处理接收消息线程*/ /*创建接收消息线程*/
ret = pthread_create(&pid , NULL , (void *)recvMsg , (void *)&sockfd);
if(ret != 0)
{
printf("软件异常,请重新登录!\n");
memset(&message , 0 , sizeof(message));
strcpy(message.sendName , (*user).userName);
message.msgType = EXIT;
send(sockfd , buf , sizeof(buf) , 0);
close(sockfd);
exit(1);
}
/*清空标准输入缓冲区*/
setbuf(stdin, NULL); /*进入处理用户发送消息缓冲区*/
while(1)
{
memset(&message , 0 , sizeof(message));
strcpy(message.sendName , (*user).userName);
memset(&str , 0 , MAX_LINE);
memset(buf , 0 , MAX_LINE);
/*usleep函数将该进程挂起一定时间,单位微秒,头文件unistd.h*/
usleep(100000); /*进入聊天主界面*/
chatInterface((*user).userName);
setbuf(stdin,NULL); //是linux中的C函数,主要用于打开和关闭缓冲机制
scanf("%d",&choice);
while(choice != 1 && choice != 2 && choice != 3 && choice !=4 && choice != 5)
{
printf("未知操作,请重新输入!\n");
setbuf(stdin,NULL); //是linux中的C函数,主要用于打开和关闭缓冲机制
scanf("%d",&choice);
setbuf(stdin,NULL);
}//while switch(choice)
{
case 1: /*查看当前在线用户列表*/
message.msgType = VIEW_USER_LIST;
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
break;
case 2: /*私聊*/
message.msgType = PERSONAL_CHAT;
printf("请输入聊天对象:\n");
setbuf(stdin , NULL);
scanf("%s" , str);
strcpy(message.recvName , str); printf("请输入聊天内容:\n");
setbuf(stdin , NULL);
fgets(message.content , MAX_LINE , stdin);
(message.content)[strlen(message.content) - 1] = '\0'; /*获得当前时间*/
time(&timep);
strcpy(message.msgTime , ctime(&timep));
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
break;
case 3: /*群聊*/
message.msgType = GROUP_CHAT;
strcpy(message.recvName , ""); printf("请输入聊天内容:\n");
setbuf(stdin , NULL);
fgets(message.content , MAX_LINE , stdin);
(message.content)[strlen(message.content) - 1] = '\0'; /*获得当前时间*/
time(&timep);
strcpy(message.msgTime , ctime(&timep));
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
break;
case 4: /*查看聊天记录*/
message.msgType = VIEW_RECORDS;
printf("请输入查看的聊天对象:\n");
setbuf(stdin , NULL);
scanf("%s" , str);
strcpy(message.recvName , str);
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
break;
case 5: /*退出登陆*/
message.msgType = EXIT;
memcpy(buf , &message , sizeof(message));
send(sockfd , buf , sizeof(buf) , 0);
close(sockfd);
exit(0);
default: /*未知操作类型*/
break;
}//switch
}//while
//close(sockfd);
}

Makefile

MYNAME = makefile
CC = gcc objects = client.o config.o register.o login.o interface.o chat.o server: $(objects)
cc -g -o client $(objects) -lsqlite3 -lpthread client.o: client.c config.h
cc -c client.c register.o: register.c config.h
cc -c register.c login.o: login.c config.h
cc -c login.c interface.o: interface.c config.h
cc -c interface.c chat.o: chat.c config.h
cc -c chat.c config.o: config.c config.h
cc -c config.c
#比较稳健的clean做法,表示clean是一个伪目标
.PHONY: clean #前面-的意思是:也许某些文件出现问题,忽略,继续执行
clean:
-rm client $(objects)

总结

以上便是此小项目的全部内容,如有不当,敬请指教!谢谢!文章来源地址https://www.yii666.com/article/754260.html网址:yii666.com<网址:yii666.com

版权声明:本文内容来源于网络,版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。文本页已经标记具体来源原文地址,请点击原文查看来源网址,站内文章以及资源内容站长不承诺其正确性,如侵犯了您的权益,请联系站长如有侵权请联系站长,将立刻删除

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信图片_20190322181744_03.jpg

微信扫一扫打赏

请作者喝杯咖啡吧~

支付宝扫一扫领取红包,优惠每天领

二维码1

zhifubaohongbao.png

二维码2

zhifubaohongbao2.png