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

OS:Ubuntu 15.04

IDE:vim gcc make

DB:Sqlite 3

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

项目功能架构:

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

部分操作如下图所示:

  1. 多用户注册:

    (1)服务器监听终端



    (2)用户注册终端

  2. 多用户登录、聊天:

    (1)用户yy登录





    (2)用户rr登录



    (3)服务器监听终端

程序结构

公共数据库

chatRome.db

服务器端

  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

客户端



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)

总结

以上便是此小项目的全部内容,如有不当,敬请指教!谢谢!

Linux聊天室项目 -- ChatRome(select实现)的更多相关文章

  1. JavaSE项目之聊天室

    引子: 当前,互联网 体系结构的参考模型主要有两种,一种是OSI参考模型,另一种是TCP/IP参考模型. 一.OSI参考模型,即开放式通信系统互联参考模型(OSI/RM,Open Systems In ...

  2. JavaSE项目之聊天室swing版

    引子: 当前,互联网 体系结构的参考模型主要有两种,一种是OSI参考模型,另一种是TCP/IP参考模型. 一.OSI参考模型,即开放式通信系统互联参考模型(OSI/RM,Open Systems In ...

  3. 聊天室(下篇)GatewayWorker 与 Laravel 的整合

    思路 上一篇大概梳理了一下 GatewayWorker 的基础知识.这篇就来准备整合 GatewayWorker 到 Laravel. GatewayWorker 是基于 Socket 监听的服务器框 ...

  4. Python实现网络多人聊天室 - Windows

    项目名称:多人聊天室项目结构: client.py server.py settings.py项目思路:服务端接收客户端连接,客户端发送信息给服务端,服务端将信息发送给所有客户端.项目实现:主进程负责 ...

  5. SilverLight搭建WCF聊天室详细过程[转]

    http://www.silverlightchina.net/html/zhuantixilie/getstart/2011/0424/7148.html 默认节点 SilverLight搭建WCF ...

  6. react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

    一.前言 9月,又到开学的季节.为每个一直默默努力的自己点赞!最近都沉浸在react native原生app开发中,之前也有使用vue/react/angular等技术开发过聊天室项目,另外还使用RN ...

  7. Nuxt+Vue聊天室|nuxt仿微信App界面|nuxt.js聊天实例

    一.项目简述 nuxt-chatroom 基于Nuxt.js+Vue.js+Vuex+Vant+VPopup等技术构建开发的仿微信|探探App界面社交聊天室项目.实现了卡片式翻牌滑动.消息发送/emo ...

  8. redis实现简易在线聊天室

    redis_flask简易聊天室 项目构建 这时一个基于Redis数据库的简单小项目,使用redis缓存数据,并通过flask部署到浏览器,运行截图如下: 输入名字后,就可以登陆到聊天室,主要包括三个 ...

  9. 基于LINUX的多功能聊天室

    原文:基于LINUX的多功能聊天室 基于LINUX的多功能聊天室 其实这个项目在我电脑已经躺了多时,最初写完项目规划后,我就认认真真地去实现了它,后来拿着这个项目区参加了面试,同样面试官也拿这个项目来 ...

随机推荐

  1. Bugtags 与其它产品的区别

    如果您刚刚接触 Bugtags,可能心里会有这样的疑问,下面将介绍 Bugtags 与其它的一些产品的区别. Bugtags 不是做统计的 SDK 大家都会在 App 里集成用户数据统计的 SDK,但 ...

  2. 每天一个Linux命令(3):pwd命令

    Linux中用 pwd 命令来查看"当前工作目录"的完整路径. 简单得说,每当你在终端进行操作时,你都会有一个当前工作目录. 在不太确定当前位置时,就会使用pwd来判定当前目录在文 ...

  3. sp_help 快速查看表结构、视图信息

    sp_helptext: 是MS SQL Server的一个系统存储过程,可以通过它来查看存储过程或者视图.函数源码 示例:sp_helptext viewName (viewName  即要查询的存 ...

  4. 14. Reverse Linked List II

    Reverse Linked List II Reverse a linked list from position m to n. Do it in-place and in one-pass. F ...

  5. Hardmard 变换

    阿达马(Hadamard)矩阵是由+1和-1元素构成的正交方阵.阿达马变换多被用来计算SATD(一种视频残差信号大小的衡量). 这里介绍三个内容,1. SATD 2. H264中阿达马的应用 3. 阿 ...

  6. CSS从大图片上截取小图标的操作

    注:图片名称(tabicons.png)每个小图标width:18px;height:18px从左上角坐标为(-0px;-0px;); 例如第一个对号的坐标为(-0px;-0px;)第二个加号的图标为 ...

  7. tsclient rdesktop remina freerdp

  8. java se the operation is not applicable to the current selection

    当新建某的类时,需要自动构建 set get方法时. 我们一般 会直接让Myeclipse自动生动.偶尔 .他会犯2. the operation is not applicable to the c ...

  9. 简洁既是美—用while语句复制数组

    简洁既是美,程序员应尽量尝试编写简洁的表达式,争取用简单的代码来实现更多的功能,当然,这也要看情况了(有时候也得考虑程序运行的时间嘛). 在阅读C++Prime Plus到while语句时有一个讲一个 ...

  10. C#winform如何最小化主窗口

    1.如果不想让程序在任务栏中显示,请把窗体的属性ShowInTaskbar设置为false;2.如果想让程序启动时就最小化,请设置窗体的属性WindowState设置为Minimized.(Minim ...