一个C写的简易聊天室

近期有些空闲,正好趁着这段时间做些练手的项目巩固一些Linux下C编程:)

技术

  • TCP Socket编程
  • SYS V消息队列
  • curses lib
  • pthread lib

设计

几乎没有设计。。简单的服务端/客户端设计,使用TCP socket传输数据,消息队列做进程间通信。

Server/Client

server端

  • 主进程监听,子进程处理每个客户端
  • 子进程的子进程收其他进程消息发送给当前客户端

client端

  • 两个进程,一个进程获得终端输入,一个进程收socket数据输出
  • 派生线程在输出窗口输出服务端信息

结构定义

消息队列结构

    typedef struct mqmesg{
        long mtype;
        long mlen;
        char mdata[MAXLEN];
    }message;

用户登录信息结构

    typedef struct lginfo{
        struct tm       *login_time; // 登录时间结构
        struct sockaddr_in  *cliaddr; // 客户端IPV4结构
        char            login_name[50+1]; // 用户登录名
    }loginfo;

客户端线程参数结构

typedef struct thr_arg{
        WINDOW  *wnd;
        int     socket;
        char    *servip;
}thrarg;

模块

  • 通用模块 util.c

    • ssize_t readn(int, void *, size_t); // 循环read函数,确保收取n字节
    • ssize_t writen(int, void *, size_t); // 循环write函数,确保发送n字节
    • int sendMsg(int, void *, int); // 发送socket封装函数
    • int recvMsg(int, void *, int *); // 接收socket封装函数
    • int mqMsgSTInit(message *, char *, long, long); // 消息队列结构赋值函数
    • ssize_t sendMq(int, message *); // 发送消息队列封装函数
    • ssize_t recvMq(int, message *); // 接收消息队列封装函数
    • int tm2DateTimeStr(struct tm *, char *); // linux时间tm结构转YYYY-MM-DD HH:MM:SS字符串
    • int getCurTimeStr(char *); // 获得当前时间串
  • 服务端功能模块 servfunc.c

    • int getClientCount(int); // 读取type 1消息代表的客户端数量
    • int putClientCount(int, int); // 向消息队列写入客户端数量
    • int login_serv(int, loginfo *); // 服务端登入处理函数
  • 客户端功能模块 clifunc.c
    • int login_cli(int); // 客户端登入处理函数
    • void *thr_fn(thrarg *); // 客户端处理输出线程函数

实现

server 端

#include "chatroom.h"

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    char buf[MAXLEN+1];
    char buf2[MAXLEN] = {0};
    char addr[INET_ADDRSTRLEN];
    int listenfd,connfd;
    int i,n,len;
    int pid;
    int client_count = 0;
    int fpid = getpid();
    char tt[19+1] = {0};


    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERVPORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);
    printf("Accepting connect...\n");

    int mq_fd = msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT);
    printf("msgid:%d\n",mq_fd);
    putClientCount(mq_fd, client_count);
    message *msg = (message *)malloc(sizeof(message));

    while(1){
        cliaddr_len = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        client_count = getClientCount(mq_fd);   
        client_count++;
        putClientCount(mq_fd, client_count);
        pid = fork();
        if(pid<0) printf("fork err\n");
        else if(pid>0){
            continue;
        }else{
            // child
            printf("this is child process[%d]\n", getpid());
            inet_ntop(AF_INET, &cliaddr.sin_addr, addr, sizeof(addr));
            printf("Recieved connection form [%s] at PORT [%d]\n", addr, ntohs(cliaddr.sin_port));
            client_count = getClientCount(mq_fd);   
            int cliNo = client_count;
            putClientCount(mq_fd, client_count);

            loginfo *cli_log_info = (loginfo *)malloc(sizeof(loginfo));
            cli_log_info->cliaddr = &cliaddr;
            login_serv(connfd, cli_log_info); // client login

            getCurTimeStr(tt);
            sprintf(buf2, "(%s) %s join the chatroom", tt, cli_log_info->login_name);
            client_count = getClientCount(mq_fd);
            for(i=1;i<=client_count;i++){
                if(i==cliNo) continue;
                mqMsgSTInit(msg, buf2, strlen(buf2), 10000+i);
                sendMq(mq_fd, msg);
            }
            putClientCount(mq_fd, client_count);


            char welcome[100] = {0};
            sprintf(welcome, "%s%d%s", "-----welcome to chat room, current user no: ", client_count, "------");
            sendMsg(connfd, welcome, strlen(welcome));

            int pid2 = fork();
            if(pid2<0){ printf("fork err\n"); continue;}
            else if(pid2>0){
                while(1){
                    memset(buf, 0x00, sizeof(buf));
                    if(recvMsg(connfd, buf, &len)<0){
                        printf("The client [%d] closed the connection.\n", getpid());

                        getCurTimeStr(tt);
                        sprintf(buf2, "(%s) %s quit the chatroom", tt, cli_log_info->login_name);
                        client_count = getClientCount(mq_fd);
                        for(i=1;i<=client_count;i++){
                            if(i==cliNo) continue;
                            mqMsgSTInit(msg, buf2, strlen(buf2), 10000+i);
                            sendMq(mq_fd, msg);
                        }
                        putClientCount(mq_fd, client_count);

                        kill(pid2, SIGKILL);
                        break;
                    }
                    printf("CLIENT[%s]:PID[%d]:LOGIN_NAME[%s]:LEN[%d]:MSG[%s]\n", addr, getpid(), cli_log_info->login_name, len, buf);
                    
                    getCurTimeStr(tt);
                    
                    sprintf(buf2, "(%s) %s: %s", tt, cli_log_info->login_name, buf);
                //  strcat(buf, "[B]");
    //              printf("DEBUG: [%d], buf[%s]\n", strlen(buf), buf);
                    client_count = getClientCount(mq_fd);
                    for(i=1;i<=client_count;i++){
                        //if(i==cliNo) continue;
                        mqMsgSTInit(msg, buf2, strlen(buf2), 10000+i);
                        sendMq(mq_fd, msg);
                    }
                    putClientCount(mq_fd, client_count);
                }
            }else{
                while(1){
                    mqMsgSTInit(msg, NULL, 0, 10000+cliNo);
                    if(recvMq(mq_fd, msg)<=0) continue;
                    else{
                        sendMsg(connfd, msg->mdata, msg->mlen);
                    }
                }
            }
            client_count = getClientCount(mq_fd);
            client_count--;
            putClientCount(mq_fd, client_count);
            close(connfd);
            break;
        }
    }
    close(listenfd);
    free(msg);
    if(getpid() == fpid){
        msgctl(mq_fd, IPC_RMID, NULL);
    }

    return 0;
}

client 端

#include "chatroom.h"

int nrows, ncols;
pthread_t ntid;


int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLEN], buf2[MAXLEN];
    int sockfd;
    int n,len,flag;
    int pid;
    char servip[15+1];
    int ret;

    if(argc == 2)
    {
        strcpy(servip, argv[1]);
    }else{
        printf("USAGE: client [serverip]\n");
        exit(0);
    }

    WINDOW *wnd = initscr();
    getmaxyx(wnd, nrows, ncols);

    WINDOW *logwin = newwin(0,0,0,0);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, (const char *)servip, &servaddr.sin_addr);
    servaddr.sin_port = htons(SERVPORT);

    
    ret = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if(!ret){
        wprintw(logwin,"Connect succeed!\n");
        wrefresh(logwin);
    }else{
        wprintw(logwin,"Cant connect to the server:%s\n", servip);
        wrefresh(logwin);
        exit(1);
    }
    // login
    login_cli_cgi(sockfd, logwin);

    werase(logwin);
    delwin(logwin);

    WINDOW *winin, *winout;
    winin = newwin(0, 0, nrows-1, 0);
    winout = newwin(nrows-2, 0, 0, 0);
    scrollok(winout, 1);

    thrarg ta = {winout, sockfd, servip};

    ret = pthread_create(&ntid, NULL, (void *)thr_fn, &ta);
    if(ret != 0){
        wprintw(winout, "cant create thread\n");
        exit(ret);
    }

    wprintw(winin, "> ");
    wrefresh(winin);
    while(!wgetnstr(winin, buf, MAXLEN)){
        sendMsg(sockfd, buf, strlen(buf));
        wclrtoeol(winin);
        wprintw(winin, "> ");
        wrefresh(winin);
    }
    
    close(sockfd);

    delwin(winin);
    delwin(winout);

    endwin();
    return 0;
}

其他

  • 目前已知的几个缺陷:
    • 客户端终端输入输出在一起,有时候输入时会有输出冒出来
    • Ctrl-C kill掉服务端后,建立的消息队列没有删除
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,960评论 0 7
  • 史上最全的iOS面试题及答案 iOS面试小贴士———————————————回答好下面的足够了----------...
    Style_伟阅读 2,345评论 0 35
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,282评论 0 6
  • 因为遇见你,让我的生活变成了彩虹,你给我带来一些欢乐。在上学的时候,你帮我辅导数学,认真教我,有时候搞一点恶作剧,...
    欣悦生化阅读 446评论 0 2