Linux下简易网络聊天室

对于初学Linux的开发者而言,聊天室是个较好上手的练习小项目,本次实现简易群聊、聊天内容保存等功能,后面有时间再更新私聊、加好友.....,主要使用线程、TCP、多路复用实现

服务器代码:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define LISTENQ              100                  //最大监听队列
#define PORT                 6000                 //监听端口
#define MAXFD                100                  //最大的在线用户数量
#define NAMESIZE             20                   //客户端姓名占用的字节数
#define TIMESIZE             30                   //收到消息时的时间占用的字节数
#define SENDBUFSIZE          1024                 //消息的数组长度
#define FILENAMESIZE         40                   //聊天记录文件名的长度
#define NEWCLIENTINFORMSIZE  70                   //通知新用户上线的消息数组长度

int sockfd;                       //绑定服务器端的ip地址和端口的套接字
FILE* fp;                        //文件指针,用于指向保存聊天记录的文件
int maxi = 0;                     //maxi表示当前client数组中最大的用户的i值
static int client[MAXFD];         //全局数组,保存所有客户端对应的套接字描述符

void tcp_init(int argc, char* argv[])
{
    struct sockaddr_in server_addr;
    if (argc != 1) 
    {
        fprintf(stderr, "Usage:%s portnumber\a\n", argv[0]);
        exit(1);
    }
    /* 服务器端开始建立 socket 描述符 */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
    {
        fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
        exit(1);
    }
    /* 服务器端填充 sockaddr 结构 */
    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);
    int reuse = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(int));
    /* 捆绑 sockfd 描述符 */
    if (bind(sockfd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1) 
    {
        fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
        exit(1);
    }
    printf("Server listening port=%d...\n", PORT);
    /* 监听 sockfd 描述符 */
    if (listen(sockfd, LISTENQ) == -1) 
    {
        fprintf(stderr, "Listen error:%s\n\a", strerror(errno));
        exit(1);
    }
}

/*接收对应的客户端的消息,并转发给所有在线客户端*/
void recv_and_send_to_all_client(int index)
{
    int nbytes = 0;
    char sendbuf[SENDBUFSIZE] = { 0 };
    int outindex = 0;
    time_t tt;
    struct tm* pTm;
    nbytes = 0;
    nbytes = read(client[index], sendbuf, sizeof(sendbuf));
    sendbuf[0] = 'M'; //'M'表示正常聊天信息
    if (nbytes > 0) 
    {
        tt = time(NULL);
        pTm = localtime(&tt);
        sprintf(sendbuf + NAMESIZE, " %d-%d-%d %0d:%0d:%0d\n",
            (1900 + pTm->tm_year), (1 + pTm->tm_mon), (pTm->tm_mday),
            (pTm->tm_hour), (pTm->tm_min), (pTm->tm_sec));
        outindex = 0;
        /*以下循环将消息转发给所有客户端*/
        while (outindex < maxi) 
        {
            if (write(client[outindex], sendbuf, sizeof(sendbuf)) == -1) 
            {
                fprintf(stderr, "Write Error:%s\n", strerror(errno));
                close(sockfd);
                fclose(fp);
                exit(1);
            }
            outindex++;
        }
        /*如果某个客户端下线,则删除全局数组client中对应的套接字*/
        if (strcmp(sendbuf + NAMESIZE + TIMESIZE, "QUIT") == 0 ||
            strcmp(sendbuf + NAMESIZE + TIMESIZE, "Quit") == 0 ||
            strcmp(sendbuf + NAMESIZE + TIMESIZE, "quit") == 0) 
        {
            for (; index < maxi - 1; index++) 
            {
                client[index] = client[index + 1];
            }
            maxi--;
        }
        /*服务器打印出消息内容并保存消息到文件*/
        printf("%s", sendbuf + 1);
        printf("%s", sendbuf + NAMESIZE);
        printf(" %s\n", sendbuf + NAMESIZE + TIMESIZE);
        fprintf(fp, "%s", sendbuf + 1);
        fprintf(fp, "%s", sendbuf + NAMESIZE);
        fprintf(fp, "%s\n", sendbuf + NAMESIZE + TIMESIZE);
    }
}


//监听转发线程入口函数
void* check_client_send(void* arg)
{
    static int index = 0;//very important
    int select_n = 0;
    int in = 0;
    fd_set allset, rset;
    FD_ZERO(&allset);
    while (1) 
    {
        if (maxi > 0) 
        {
            for (index = 0; index < maxi; index++) 
            {
                FD_SET(client[index], &allset);
            }
            rset = allset;
            //阻塞监听所有的客户端对应的套接字有没有可读的,即是否有人发消息过来
            select_n = select(FD_SETSIZE, &rset, NULL, NULL, NULL);
            switch (select_n) 
            {
                case 0:
                    continue;
                case -1:
                    perror("select error!");
                    exit(-1);
                default:
                    for (index = 0, in = 0; index < maxi && in < select_n; index++)
                    {
                        if (FD_ISSET(client[index], &rset))
                        {
                            in++;
                            recv_and_send_to_all_client(index);//调用函数接收客户端的消息
                        }
                    } 
            }
        }
    }
}

/*该线程等待客户端连接,并将新的套接字存入全局数组static int client[MAXFD]中*/
void* wait_client_connect(void* pthidcheck)
{
    struct sockaddr_in client_addr;
    int sin_size, portnumber;
    int new_fd = 0;
    char clientname[NAMESIZE] = { 0 };
    char new_client_inform[NEWCLIENTINFORMSIZE] = { 0 };
    static int index = 0;
    time_t tt;
    struct tm* pTm = NULL;
    while (1) 
    {
        /* 服务器阻塞,直到客户程序建立连接 */
        if (maxi >= MAXFD) 
        {
            printf("The number of people online has been reached\n");
            continue;
        }
        sin_size = sizeof(struct sockaddr_in);
        if ((new_fd = accept(sockfd, (struct sockaddr*)(&client_addr), &sin_size)) == -1) 
        {
            fprintf(stderr, "Accept error:%s\n\a", strerror(errno));
            exit(1);
        }
        client[maxi] = new_fd;
        if (-1 == read(client[maxi], clientname, sizeof(clientname)))
        {
            fprintf(stderr, "Read client name error:%s\n", strerror(errno));
            close(sockfd);
            exit(1);
        }
        maxi++;
        tt = time(NULL);
        pTm = localtime(&tt);
        //'N'表示向所有在线用户通知新用户上线了
        sprintf(new_client_inform, "N%d-%d-%d %0d:%0d:%0d",
            (1900 + pTm->tm_year), (1 + pTm->tm_mon), (pTm->tm_mday),
            (pTm->tm_hour), (pTm->tm_min), (pTm->tm_sec));
        sprintf(new_client_inform + TIMESIZE, " A new user %s enters the chat room", clientname);
        int outindex = 0;
        while (outindex < maxi) 
        { //向聊天室中所有的客户通知新用户上线
            if (write(client[outindex], new_client_inform, sizeof(new_client_inform)) == -1) 
            {
                fprintf(stderr, "Write Error:%s\n", strerror(errno));
                close(sockfd);
                exit(1);
            }
            outindex++;
        }
        /*创建线程收客户端的消息并转发给所有其它在线客户端,同时保存聊天记录*/
        if (maxi == 1) 
        {
            if (-1 == pthread_create((pthread_t*)pthidcheck, NULL, check_client_send, NULL)) 
            {
                fprintf(stderr, "Creat pthread Error:%s\n", strerror(errno));
                close(sockfd);
                exit(1);
            }
            fp = fopen("server_msgrecord.txt", "ab");
            if (fp == NULL) 
            {
                fprintf(stderr, "Fopen Error:%s\a\n", strerror(errno));
                close(sockfd);
                exit(1);
            }
        }
        else if (maxi > 1) 
        {
            pthread_cancel(*(pthread_t*)pthidcheck);
            if (-1 == pthread_create((pthread_t*)pthidcheck, NULL, check_client_send, NULL)) 
            {
                fprintf(stderr, "Creat pthread Error:%s\n", strerror(errno));
                fclose(fp);
                close(sockfd);
                exit(1);
            }
        }
        printf("%s\n", new_client_inform + 1);
        printf("%s\n", new_client_inform + TIMESIZE);
        fprintf(fp, "%s\n", new_client_inform + 1);
        fprintf(fp, "%s\n", new_client_inform + TIMESIZE);
    }
}

void* quit(void* qu)
{
    char buff[10] = { 0 };
    while (1)
    {
        fgets(buff, sizeof(buff), stdin);
        if (strcmp(buff, "quit\n") == 0)
        {
            break;
        }
    }
    close(sockfd);
    fclose(fp);
    exit(0);
}

int main(int argc, char* argv[])
{
    pthread_t thidaccept, thidcheck = -1,qid;
    tcp_init(argc, argv);
    printf("Welcome to this chat room\n");
    /*创建线程等待接受客户端连接请求*/
    if (-1 == pthread_create(&thidaccept, NULL, wait_client_connect, (void*)&thidcheck)) 
    {
        fprintf(stderr, "Creat pthread Error:%s\n", strerror(errno));
        close(sockfd);
        exit(1);
    }
    pthread_create(&qid, NULL, quit, (void*)&thidcheck);//退出线程 方便下次进入
    pthread_join(thidaccept, NULL);
    if (thidcheck != -1) 
    {
        pthread_join(thidcheck, NULL);
    }
    close(sockfd);
    fclose(fp);
    exit(0);
}

客户端代码:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>

#define PORT                6000        //监听端口
#define NAMESIZE            20          //客户端姓名占用的字节数
#define TIMESIZE            30          //收到消息时的时间占用的字节数
#define SENDBUFSIZE         1024        //消息的数组长度
#define FILENAMESIZE        40          //聊天记录文件名的长度
#define NEWCLIENTINFORMSIZE 70          //通知新用户上线的消息数组长度

FILE* fp;                              //文件指针,用于指向保存聊天记录的文件
static int sockfd;                      //绑定服务器端的ip地址和端口的套接字
char clientname[NAMESIZE] = { 0 };        //保存自己的昵称

/*连接服务器*/
void connect_server()
{
    struct sockaddr_in server_addr;
    struct hostent* host;
    char strhost[16];
    printf("Please enter the server ip address\n");
    scanf("%s", strhost);
    //strcpy(strhost, "192.168.0.101");
    if ((host = gethostbyname(strhost)) == NULL) 
    {
        fprintf(stderr, "Gethostname error\n");
        exit(1);
    }
    /* 客户程序开始建立 sockfd 描述符 */
    printf("The socket is being created...\n");
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
    {
        fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
        exit(1);
    }
    /* 客户程序填充服务端的资料 */
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr = *((struct in_addr*)host->h_addr);
    printf("The socket has been created successfully and is connecting to the server...\n");
    /* 客户程序发起连接请求 */
    if (connect(sockfd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1) 
    {
        fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
        close(sockfd);
        exit(1);
    }
    /* 连接成功了 */
    printf("Successfully connected to server \n  Welcome to chat room\n");
    printf("Please enter your user name\n>");
    fflush(stdout);//清空文件缓冲区(或标准输入输出缓冲区)
    scanf("%s", clientname);
    write(sockfd, clientname, sizeof(clientname));
    char recvbuf[SENDBUFSIZE] = { 0 };
    if (-1 == read(sockfd, recvbuf, sizeof(recvbuf))) 
    {
        fprintf(stderr, "Read my inform Error:%s\n", strerror(errno));
        close(sockfd);
        exit(1);
    }
    if (recvbuf[0] == 'N') 
    {
        char filename[FILENAMESIZE] = { 0 };
        strcpy(filename, clientname);
        strcat(filename, "_msgrecord.txt");
        fp = fopen(filename, "ab");
        if (fp == NULL) 
        {
            fprintf(stderr, "Fopen Error:%s\a\n", strerror(errno));
            close(sockfd);
            exit(1);
        }
        printf("%s\n", recvbuf + 1);
        printf("%s\n", recvbuf + TIMESIZE);
        fprintf(fp, "%s\n", recvbuf + 1);
        fprintf(fp, "%s\n", recvbuf + TIMESIZE);
        printf("Start chatting(\"QUIT\"disconnect)\n");
    }
}


/*接收服务器消息线程入口函数*/
void* recvfromserver(void* arg)
{
    time_t tt;
    struct tm* pTm;
    char recvbuf[SENDBUFSIZE];
    int nbytes = 0;
    while (1) 
    {
        memset(recvbuf, 0, sizeof(recvbuf));
        if (0 < read(sockfd, recvbuf, sizeof(recvbuf))) 
        {
            if (recvbuf[0] == 'M') 
            { //如果消息是'M'表示消息收到服务器的正常聊天消息
                fprintf(fp, "\n%s", recvbuf + 1);
                fprintf(fp, " %s", recvbuf + NAMESIZE);
                fprintf(fp, " %s", recvbuf + NAMESIZE + TIMESIZE);
                fflush(fp);
                if (strcmp(recvbuf + 1, clientname) == 0) 
                {
                    continue;
                }
                printf("\n%s", recvbuf + 1);
                printf(" %s", recvbuf + NAMESIZE);
                printf(" %s", recvbuf + NAMESIZE + TIMESIZE);
            }
            else if (recvbuf[0] == 'N') 
            { //如果是'N'表示收到服务器的新用户通知上线消息
                printf("%s\n", recvbuf + 1);
                printf("%s\n", recvbuf + TIMESIZE);
                fprintf(fp, "%s\n", recvbuf + 1);
                fprintf(fp, "%s\n", recvbuf + TIMESIZE);
            }
            else 
            {
                printf("An unrecognized message was received\n");
            }
            /*显示时间*/
            tt = time(NULL);
            pTm = localtime(&tt);
            printf("\n%s  %d-%d-%d %0d:%0d:%0d\n>", clientname,
                (1900 + pTm->tm_year), (1 + pTm->tm_mon), (pTm->tm_mday),
                (pTm->tm_hour), (pTm->tm_min), (pTm->tm_sec));
            fflush(stdout);
        }
    }
}

/*向服务器发送消息线程入口函数*/
void* sendtoserver(void* arg)
{
    time_t tt;
    struct tm* pTm;
    char sendbuf[SENDBUFSIZE];
    //清除输入缓冲区中的换行符
    fgets(sendbuf + NAMESIZE + TIMESIZE, sizeof(sendbuf) - NAMESIZE + TIMESIZE, stdin);
    while (1)
    {
        memset(sendbuf, 0, sizeof(sendbuf));
        tt = time(NULL);
        pTm = localtime(&tt);
        printf("%s  %d-%d-%d %0d:%0d:%0d\n>", clientname,
            (1900 + pTm->tm_year), (1 + pTm->tm_mon), (pTm->tm_mday),
            (pTm->tm_hour), (pTm->tm_min), (pTm->tm_sec));
        fflush(stdin);
        fflush(stdout);
        fgets(sendbuf + NAMESIZE + TIMESIZE, sizeof(sendbuf) - NAMESIZE + TIMESIZE, stdin);
        sendbuf[49 + strlen(sendbuf + NAMESIZE + TIMESIZE)] = '\0';
        strcpy(sendbuf + 1, clientname);//在消息前面加上自己的呢称
        if ((write(sockfd, sendbuf, sizeof(sendbuf))) == -1) 
        {
            fprintf(stderr, "Write Error:%s\n", strerror(errno));
            close(sockfd);
            fclose(fp);
            exit(1);
        }
        /*如果输入"quit"或"Quit"或"QUIT"都会退出*/
        if (strcmp(sendbuf + NAMESIZE + TIMESIZE, "QUIT") == 0 ||
            strcmp(sendbuf + NAMESIZE + TIMESIZE, "Quit") == 0 ||
            strcmp(sendbuf + NAMESIZE + TIMESIZE, "quit") == 0) 
        {
            close(sockfd);
            fclose(fp);
            printf("You have quit the chat room\n");
            exit(1);
        }
    }
}

int main(int argc, char* argv[])
{
    pthread_t thidrcv, thidsnd;
    connect_server(); //连接服务器
    /*创建发送和接收消息的线程*/
    if (-1 == pthread_create(&thidrcv, NULL, recvfromserver, NULL))
    {
        fprintf(stderr, "Creat pthread Error:%s\n", strerror(errno));
        close(sockfd);
        exit(1);
    }
    if (-1 == pthread_create(&thidsnd, NULL, sendtoserver, NULL)) 
    {
        fprintf(stderr, "Creat pthread Error:%s\n", strerror(errno));
        close(sockfd);
        fclose(fp);
        exit(1);
    }
    pthread_join(thidrcv, NULL);
    pthread_join(thidsnd, NULL);
    close(sockfd);
    fclose(fp);
    exit(0);
}



  • 11
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在Linux环境下实现简易聊天,可以采用Socket编程和多线程技术。首先,创建一个服务器程序和多个客户端程序。 在服务器程序中,首先创建一个Socket并绑定到指定的IP地址和端口号。接着,通过监听端口,等待客户端的连接请求。一旦有客户端连接,服务器就会为该客户端创建一个新的线程来处理与其通信。在客户端进程和服务器线程之间,通过Socket进行数据传输。 服务器线程中的主要工作是接收来自客户端的消息,然后将消息转发给其他所有连接到服务器的客户端。服务器通过维护一个客户端列表,记录所有连接到服务器的客户端。当有新的消息到达时,服务器遍历列表,将消息发送给每个客户端。 对于客户端程序,首先创建一个Socket并连接到服务器指定的IP地址和端口号。然后,启动一个新线程用来接收服务器发送的消息,并将其显示到客户端界面上。同时,客户端还需要一个线程用来读取用户输入的消息,并将其发送给服务器。 在客户端界面上,可以使用图形界面或者命令行界面。通过界面,用户可以看到所有在线的用户列表,选择用户进行私聊,发送消息给所有用户等。 需要注意的是,为了保证消息的同步和安全,需要采取一些机制,比如使用互斥锁来保护共享数据,使用条件变量来实现线程之间的等待和通知,避免消息的丢失或者重复发送等问题。 总而言之,Linux环境下的简易聊天需要通过Socket编程和多线程技术实现服务器和客户端之间的通信。通过服务器转发消息给所有在线用户,实现聊天的功能。 ### 回答2: Linux环境下的简易聊天,可以使用Socket编程来实现。 首先,我们需要创建一个服务器端程序和多个客户端程序。服务器端程序用于接收来自客户端的消息并进行处理,而客户端程序用于连接服务器,并发送和接收消息。 服务器端程序需要监听指定的端口,等待客户端连接。一旦有客户端连接成功,服务器端程序需要分配一个新的线程或进程来处理该客户端的消息。服务器端程序可以使用C或Python等编程语言来实现。 客户端程序可以通过输入服务器的IP地址和端口来连接服务器。连接成功后,客户端可以输入消息,并将消息发送给服务器。客户端也可以接收来自服务器的消息并在本地显示。 在服务器端,需要将连接的客户端保存到一个列表中,以便于后续的消息发送。当服务器接收到客户端的消息时,可以将消息发送给所有在线客户端(除了发送方之外),从而实现消息的广播。 另外,在服务器端和客户端程序中,可以使用多线程或多进程来处理并发连接。这样可以实现多个客户端之间的实时通信。 总之,Linux环境下的简易聊天可以通过使用Socket编程,在服务器端和多个客户端之间建立连接,并通过发送和接收消息来实现实时通信。这样可以让用户方便地在Linux环境下进行在线交流和沟通。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值