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

项目简介:采用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. 多客户可同时连接服务器进行自己操作;

部分操作如下图所示:
Make命令:
Make
1. 多用户注册:
(1)服务器监听终端
1
(2)用户注册终端
2

  1. 多用户登录、聊天:
    (1)用户yy登录
    yy
    yy2
    (2)用户rr登录
    rr
    (3)服务器监听终端
    server

程序结构

公共数据库

chatRome.db

服务器端

server

  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

客户端

client
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

/************************************
  • 6
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值