Linux局域网多人聊天软件

项目工程 专栏收录该内容
2 篇文章 0 订阅

功能介绍

  1. 客户端:登陆及注册;列出当前在线用户列表、发送聊天消息、传输文件等。
  2. 服务端:记录注册及在线用户链表、记录用户聊天数据、显示用户的登陆退出等。

主要知识点:socket套接字、链表用户管理、线程创建管理、IO复用、select监听套接字、文件读写等。


程序下载

https://download.csdn.net/download/mrhjlong/10337335


服务端主函数代码:client.c

/*************************************************************************
    > File Name: client.c
    > Author: mrhjlong
    > Mail: mrhjlong@163.com 
    > Created Time: 2016年08月01日 星期一 14时40分39秒
 ************************************************************************/

#include "userlist.h"

void *func_ttl(void *arg)
{
    int fd = (int)arg;
    while(1)
    {
        cli_TTL(fd);
        sleep(20);
    }
    return NULL;
}

int main(int argc, char *argv[])
{   
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
        err_sys("socket error");

    struct sockaddr_in des_addr;
    des_addr.sin_family = AF_INET;
    des_addr.sin_port = htons(9999);
    inet_pton(AF_INET, "127.0.0.1", &des_addr.sin_addr);

    //连接客户端
    int ret = connect(fd, (struct sockaddr *)&des_addr, sizeof(des_addr));
    if(ret == -1)
        err_sys("connect error");

    //登陆和注册登陆
    ret = cli_REG_LOG(fd);
    if(ret == -1)   //放弃登陆或注册,直接退出
    {
        close(fd);
        return 0;
    }

    //创建线程发送保活信息
    pthread_t pid;
    ret = pthread_create(&pid, NULL, func_ttl, (void *)fd);
    if(ret != 0)
        err_sys("pthread create error");

    MSG msgdata;
    fd_set read_set;
    int n;
    int flag = 0;
    char recv_buf[BUFSIZE] = {0};
    char fname[50] = {0};

    while(1)
    {
        FD_ZERO(&read_set);
        if(flag == 0)
        {
            printf("Input a command: s-send | f-file | l-list | q-quit...\n");
            FD_SET(0, &read_set);
        }
        FD_SET(fd, &read_set);

        select(fd + 1, &read_set, NULL, NULL, NULL);

        if(FD_ISSET(0, &read_set))  //输入响应
        {
            bzero(&msgdata, sizeof(msgdata));
            fgets(msgdata.cmd, 50, stdin);
            if(strcmp(msgdata.cmd, "q\n") == 0) //退出
            {
                pthread_cancel(pid);    //关闭线程
                flag = 1;
                shutdown(fd, SHUT_WR);
                FD_CLR(0, &read_set);
                continue;
            }
            else if(strcmp(msgdata.cmd, "l\n") == 0)    //发送命令,列出当前在线用户
            {
                cli_LIST(fd);
                continue;
            }
            else if(strcmp(msgdata.cmd, "s\n") == 0)
            {
                cli_SEND(fd, &msgdata);
            }
            else if(strcmp(msgdata.cmd, "f\n") == 0)    //发送FILE命令
            {
                bzero(fname, 50);
                ret = cli_FILE(fd, &msgdata, fname);
                if(ret == 0)
                    flag = 1;
                continue;
            }
            else
            {
                printf("#################WARNING#################\n");
                printf("Input error! Please try again!\n");
                printf("#########################################\n");
                continue;
            }
        }

        if(FD_ISSET(fd, &read_set))     //接收信息
        {
            bzero(recv_buf, BUFSIZE);
            bzero(&msgdata, sizeof(msgdata));
            n = recv(fd, recv_buf, BUFSIZE, 0);
            if(n == 0)      //关闭
            {
                printf("closed!\n");
                break;
            }

            read_XML(recv_buf, &msgdata);
            if(strcmp(msgdata.cmd, "LISTD") == 0)
            {
                printf("**************Online users:**************\n");
                printf("%s\n", msgdata.text);
                printf("*****************************************\n");
                continue;
            }
            else if(strcmp(msgdata.cmd, "RECV") == 0)
            {
                printf("*****************RECV...*****************\n");
                printf("FROM:%s\n", msgdata.name);
                printf("MSG:%s\n", msgdata.text);
                printf("*****************************************\n");
            }
            else if(strcmp(msgdata.cmd, "NOUSR") == 0)
            {
                printf("#################WARNING#################\n");
                printf("User:%s is offline! Please try later.\n", msgdata.text);
                printf("#########################################\n");
                flag = 0;
            }
            else if(strcmp(msgdata.cmd, "LEAVE") == 0)
            {
                pthread_cancel(pid);    //关闭线程
                printf("cmd closed!\n");
                break;
            }
            else if(strcmp(msgdata.cmd, "FCNT") == 0)   //建立传输文件连接,发送
            {
                cli_FCNT(&msgdata, fname);
                flag = 0;
            }
            else if(strcmp(msgdata.cmd, "FLSN") == 0)   //建立传输文件监听, 接收信息
            {
                cli_FLSN(fd, &msgdata);
            }
        }
    }
    close(fd);
    return 0;
}

服务端主函数代码:server.c

/*************************************************************************
    > File Name: server.c
    > Author: mrhjlong
    > Mail: mrhjlong@163.com 
    > Created Time: 2016年08月01日 星期一 14时13分10秒
 ************************************************************************/

#include "userlist.h"

int main(void)
{
    struct list_head list;  //新建在线用户链表
    INIT_LIST_HEAD(&list);  //初始化链表头

    struct list_head usrList;   //新建已注册用户链表 
    INIT_LIST_HEAD(&usrList);

    FILE *fp = fopen("regUser.txt", "r");
    if(fp == NULL)
        err_sys("open regUser.txt error!\n");
    //获取已注册用户链表
    get_user_list(fp, &usrList);    

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
        err_sys("socket error");

    int optval = 1;
    int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
    if(ret == -1)
        err_sys("setsockopt error!");

    //服务器地址端口设置
    struct sockaddr_in my_addr;
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(9999);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //绑定服务器
    ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if(ret != 0)
        err_sys("bind error");
    //监听
    ret = listen(sockfd, 10);
    if(ret != 0)
        err_sys("listen error");
    printf("listening...\n");

    fd_set read_set;
    MSG msgdata;
    char recv_buf[BUFSIZE] = {0};
    int n;
    struData_t *p = NULL;
    struct list_head *pos = NULL;

    struct timeval timeout;
    LD ldata;

    while(1)
    {
        FD_ZERO(&read_set);
        FD_SET(sockfd, &read_set);
        FD_SET(0, &read_set);
        //遍历链表,添加套接字到select
        list_for_each(pos, &list)
        {
            p = list_entry(pos, struData_t, list);
            FD_SET(p->sockfd, &read_set);
        }
        //设置select
        timeout.tv_sec = 3;     //阻塞3秒
        timeout.tv_usec = 0;
        if(pos->prev == &list)
            ret = select(sockfd + 1, &read_set, NULL, NULL, &timeout);
        else
        {
            p = list_entry(pos->prev, struData_t, list);
            ret = select(p->sockfd + 1, &read_set, NULL, NULL, &timeout);
        }
        //超时返回,检测客户保活信息
        if(ret == 0)        
        {
            chk_ttl(&list);
            continue;
        }

        //服务端退出
        if(FD_ISSET(0, &read_set))
        {
            char cmd_quit[50] = {0};
            fgets(cmd_quit, 50, stdin);
            if(strcmp(cmd_quit, "q\n") == 0)    //输入q退出
            {
                ser_quit(&usrList, &list);
                printf("Closing server!\n");
                break;
            }
            else if(strcmp(cmd_quit, "s\n") == 0)   //输入s显示聊天记录
            {
                FILE *fp = fopen("Chatlog.txt", "r");
                char buffer[100] = {0};
                if(fp == NULL)
                    printf("open Chatlog.txt error!\n");

                printf("*****************Chat record:******************\n");
                fgets(buffer, 100, fp);
                while(strlen(buffer) >= 8)
                {
                    printf("%s", buffer);
                    bzero(buffer, 100);
                    fgets(buffer, 100, fp);
                }
                printf("***********************************************\n");
            }
            else if(strcmp(cmd_quit, "l\n") == 0)   //输入l获取已注册用户信息
            {
                FILE *fp = fopen("regUser.txt", "r");
                if(fp == NULL)
                    err_sys("open regUser.txt error!\n");
                //获取已注册用户链表
                get_user_list(fp, &usrList);
            }
            else
                printf("Input error! Please try again!\n");

            continue;
        }

        //监听套接字响应
        if(FD_ISSET(sockfd, &read_set)) 
        {   
            ldata.sockfd = sockfd;
            ldata.usrList = &usrList;
            ldata.list = &list;
            //创建处理注册和登陆操作的线程
            pthread_t pid;
            int n = pthread_create(&pid, NULL, listen_reg_log, (void *)&ldata);
            if(n != 0)
                err_sys("pthread create error");
            n = pthread_detach(pid);
            if(n != 0)
                err_sys("pthread detach error");

            usleep(200);    //等待线程accept,否则会重复创建线程
            ret--;
        }
        //客户端套接字响应,接收到信息
        if(ret > 0)                         
        {
            //遍历链表,找到响应的套接字
            list_for_each(pos, &list)       
            {
                p = list_entry(pos, struData_t, list);
                if(FD_ISSET(p->sockfd, &read_set))
                    break;
            }

            bzero(recv_buf, BUFSIZE);
            n = recv(p->sockfd, recv_buf, BUFSIZE, 0);
            //客户端主动关闭,服务端释放内存
            if(n == 0)      
            {
                printf("User:%s is leaving...\n", p->client_name);
                close(p->sockfd);
                list_del(pos);
                free(p);
                continue;
            }

            bzero(&msgdata, sizeof(msgdata));
            read_XML(recv_buf, &msgdata);   //解析XML数据

            if(strcmp(msgdata.cmd, "LIST") == 0)        //LIST命令处理
            {
                ser_LIST(p->sockfd, &list, &msgdata);
                continue;
            }
            else if(strcmp(msgdata.cmd, "TTL") == 0)    //TTL命令处理
            {
                time(&(p->ttl));    //重置保活时间
                continue;
            }
            else if(strcmp(msgdata.cmd, "SEND") == 0)   //SEND命令处理
            {
                ser_SEND(p->sockfd, p->client_name, &list, &msgdata);
                continue;
            }
            else if(strcmp(msgdata.cmd, "FILE") == 0)
            {
                ser_FILE(p->sockfd, p->client_name, &list, &msgdata);
            }
        }   
    }
    close(sockfd);
    return 0;
}
  • 0
    点赞
  • 1
    评论
  • 7
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

j衣l日

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值