一個C寫的簡易聊天室

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

技術(shù)

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

設(shè)計(jì)

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

Server/Client

server端

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

client端

  • 兩個進(jìn)程,一個進(jìn)程獲得終端輸入,一個進(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; // 登錄時間結(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時間tm結(jié)構(gòu)轉(zhuǎn)YYYY-MM-DD HH:MM:SS字符串
    • int getCurTimeStr(char *); // 獲得當(dāng)前時間串
  • 服務(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;
}

其他

  • 目前已知的幾個缺陷:
    • 客戶端終端輸入輸出在一起,有時候輸入時會有輸出冒出來
    • Ctrl-C kill掉服務(wù)端后,建立的消息隊(duì)列沒有刪除
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容