Linux聊天室项目 -- ChatRome(select实现)
2015-12-22 11:39
543 查看
序
项目简介:采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室!OS:Ubuntu 15.04
IDE:vim gcc make
DB:Sqlite 3
Time:2015-12-09 ~ 2012-12-21
项目功能架构:
采用client/server结构;
给出客户操作主界面(注册、登录、帮助和退出)、登录后主界面(查看在线列表、私聊、群聊、查看聊天记录、退出);
多客户可同时连接服务器进行自己操作;
部分操作如下图所示:
Make命令:
1. 多用户注册:
(1)服务器监听终端
(2)用户注册终端
多用户登录、聊天:
(1)用户yy登录
(2)用户rr登录
(3)服务器监听终端
程序结构
公共数据库
chatRome.db服务器端
server.c:服务器端主程序代码文件;
config.h:服务器端配置文件(包含需要的头文件、常量、数据结构及函数声明);
config.c:服务器端公共函数的实现文件;
list.c:链表实现文件,用于维护在线用户链表的添加、更新、删除操作;
register.c:服务器端实现用户注册;
login.c:服务器端实现用户登录;
chat.c:服务器端实现用户的聊天互动操作;
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内存管理 -- malloc,kmalloc,vmalloc区别
- Linux文件类型及如何查看,修改文件读写权限
- 01.The Introduction of Linux
- 【OpenCV】arm-linux-gcc 3.4.1 移植OpenCV 1.0 出现[cvpyrsegmentation.lo] Error 1 错误
- rocketmq命令行自动补全工具
- Centos修炼----->Centos7办公环境打造(No5-安装Chrome浏览器)
- Linux ext3 ext4 区别
- 环境变量错误导致Linux指令不可用
- 10个趣味Linux动画命令
- Linux ldconfig
- 【学习笔记】linux与windows中wchar_t的问题
- 【Linux 驱动】netfilter/iptables (三) 注册和注销Netfilter hook
- Linux makefile 教程 非常详细,且易懂
- Centos修炼----->Centos7办公环境打造(No4 一音频和视频文件如何播放)
- 习惯的养成—在解决问题的过程中提炼需要总结地地方(Linux 输入框架问题)
- Linux中vi使用
- 以snull为例分析linux网卡驱动的技术文档[转载]二
- CentOS VmwareTools安装
- 克隆linux虚拟机报错Bringing up interface eth0
- linux vim 快速配置及常用命令