一個(gè)C寫的簡(jiǎn)易聊天室

近期有些空閑竭讳,正好趁著這段時(shí)間做些練手的項(xiàng)目鞏固一些Linux下C編程:)

技術(shù)

  • TCP Socket編程
  • SYS V消息隊(duì)列
  • curses lib
  • pthread lib

設(shè)計(jì)

幾乎沒有設(shè)計(jì)梭冠。空执。簡(jiǎn)單的服務(wù)端/客戶端設(shè)計(jì),使用TCP socket傳輸數(shù)據(jù)婴梧,消息隊(duì)列做進(jìn)程間通信下梢。

Server/Client

server端

  • 主進(jìn)程監(jiān)聽,子進(jìn)程處理每個(gè)客戶端
  • 子進(jìn)程的子進(jìn)程收其他進(jìn)程消息發(fā)送給當(dāng)前客戶端

client端

  • 兩個(gè)進(jìn)程塞蹭,一個(gè)進(jìn)程獲得終端輸入孽江,一個(gè)進(jìn)程收socket數(shù)據(jù)輸出
  • 派生線程在輸出窗口輸出服務(wù)端信息

結(jié)構(gòu)定義

消息隊(duì)列結(jié)構(gòu)

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

用戶登錄信息結(jié)構(gòu)

    typedef struct lginfo{
        struct tm       *login_time; // 登錄時(shí)間結(jié)構(gòu)
        struct sockaddr_in  *cliaddr; // 客戶端IPV4結(jié)構(gòu)
        char            login_name[50+1]; // 用戶登錄名
    }loginfo;

客戶端線程參數(shù)結(jié)構(gòu)

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

模塊

  • 通用模塊 util.c

    • ssize_t readn(int, void *, size_t); // 循環(huán)read函數(shù),確保收取n字節(jié)
    • ssize_t writen(int, void *, size_t); // 循環(huán)write函數(shù)番电,確保發(fā)送n字節(jié)
    • int sendMsg(int, void *, int); // 發(fā)送socket封裝函數(shù)
    • int recvMsg(int, void *, int *); // 接收socket封裝函數(shù)
    • int mqMsgSTInit(message *, char *, long, long); // 消息隊(duì)列結(jié)構(gòu)賦值函數(shù)
    • ssize_t sendMq(int, message *); // 發(fā)送消息隊(duì)列封裝函數(shù)
    • ssize_t recvMq(int, message *); // 接收消息隊(duì)列封裝函數(shù)
    • int tm2DateTimeStr(struct tm *, char *); // linux時(shí)間tm結(jié)構(gòu)轉(zhuǎn)YYYY-MM-DD HH:MM:SS字符串
    • int getCurTimeStr(char *); // 獲得當(dāng)前時(shí)間串
  • 服務(wù)端功能模塊 servfunc.c

    • int getClientCount(int); // 讀取type 1消息代表的客戶端數(shù)量
    • int putClientCount(int, int); // 向消息隊(duì)列寫入客戶端數(shù)量
    • int login_serv(int, loginfo *); // 服務(wù)端登入處理函數(shù)
  • 客戶端功能模塊 clifunc.c
    • int login_cli(int); // 客戶端登入處理函數(shù)
    • void *thr_fn(thrarg *); // 客戶端處理輸出線程函數(shù)

實(shí)現(xiàn)

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;
}

其他

  • 目前已知的幾個(gè)缺陷:
    • 客戶端終端輸入輸出在一起岗屏,有時(shí)候輸入時(shí)會(huì)有輸出冒出來
    • Ctrl-C kill掉服務(wù)端后,建立的消息隊(duì)列沒有刪除
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末漱办,一起剝皮案震驚了整個(gè)濱河市担汤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洼冻,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隅很,死亡現(xiàn)場(chǎng)離奇詭異撞牢,居然都是意外死亡率碾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門屋彪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來所宰,“玉大人,你說我怎么就攤上這事畜挥∽兄啵” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵蟹但,是天一觀的道長(zhǎng)躯泰。 經(jīng)常有香客問我,道長(zhǎng)华糖,這世上最難降的妖魔是什么麦向? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮客叉,結(jié)果婚禮上诵竭,老公的妹妹穿的比我還像新娘。我一直安慰自己兼搏,他們只是感情好卵慰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佛呻,像睡著了一般裳朋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上件相,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天再扭,我揣著相機(jī)與錄音,去河邊找鬼夜矗。 笑死泛范,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的紊撕。 我是一名探鬼主播罢荡,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼对扶!你這毒婦竟也來了区赵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤浪南,失蹤者是張志新(化名)和其女友劉穎笼才,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體络凿,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骡送,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年昂羡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摔踱。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虐先,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出派敷,到底是詐尸還是另有隱情蛹批,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布篮愉,位于F島的核電站腐芍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏潜支。R本人自食惡果不足惜甸赃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冗酿。 院中可真熱鬧埠对,春花似錦、人聲如沸裁替。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弱判。三九已至襟沮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昌腰,已是汗流浹背开伏。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遭商,地道東北人固灵。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像劫流,于是被迫代替她去往敵國和親巫玻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容