基于UDP传输的C语言聊天小程序

概述

任务要求

​    利用系统编程和网络编程相关知识,选择TCP或者UDP,编写一个程序,实现用户客户端与客户端之间,客户端与服务器之间的交互,客户端只能通过服务器获取其他客户端数据和执行相关操作,客户端的数据修改必须保存到服务器,需要实现的基本功能如下:

1.用户注册,并保存在服务器

2.用户登入,登入成功执行下一级操作

3.找回密码,

4.修改密码,并更新至服务器

5.注销账号,删除用户的所有文件和痕迹

6.退出

下一级菜单主要功能如下:

1.群聊,群成员发送的消息所有群成员都能收到,输入quit退出群聊

2.添加好友,添加指定用户为好友,对方通过申请才算添加成功

3.好友申请,查看他人发送给你的好友申请,你可以同意或者拒绝

4.好友列表,查看你的好友和状态,离线,在线,注销

5.私聊,选择指定好友进行聊天,只能与在线好友聊天

6.朋友圈,可以查看朋友圈和发朋友圈

7.聊天纪录,可以查看和删除与指定好友的聊天记录

8.传文件,给指定好友传输文件

9.下载文件,下载指定好友发送的文件

10.返回上一级菜单

流程图如下:

核心思想

​    本次项目设计主要通过UDP转发和接收数据,由于UDP只能发送一大块内存内容,不区分类型,为了实现多种多样的功能,定义了不同种类的消息结构体,发送前将这次消息结构体统一打包进消息包裹里面,并贴上消息类型的“标签”俗称“打包”,接收方收到后再根据消息包裹上面的“标签”,将包裹拆分复原成指定的消息类型,俗称“拆包”,

示意图如下:

 注册

​    用户发送自己的注册账号,密码和电话号码给服务器进行处理,如果用户的账号与以及注册的账号同名则注册失败,反之服务器将用户的注册信息保存到服务器。

客户端代码如下
void registerUser(int sd, struct sockaddr_in *addr)
{
    zhuce info; // 注册信息结构体
    printf("\e[33m请依次输入你要注册的账号、电话号码、密码:\e[0m\n");
    scanf("%s %s %s", info.name, info.tel, info.pwd);
    package pack;                               // 用来封装信息结构体
    memcpy(pack.msgBlock, &info, sizeof(info)); // 封装成统一的包
    pack.msgType = REGISTER;                    // 贴上消息类型标签
    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
    recvfrom(sd, &pack, sizeof(pack), 0, NULL, NULL);
    printf("%s\n", pack.msgBlock);
}
```

服务器端代码如下

```
 if (pack.msgType == REGISTER)
        {
            // 如果注册名字已经存在就注册失败
            printf("\e[33m游客注册信息:\e[0m\n");
            zhuce *info1 = (zhuce *)pack.msgBlock; // 拆开数据包还原
            printf("\e[34mname:%s  tel:%s  pwd:%s\e[0m\n", info1->name, info1->tel, info1->pwd);

            int fd = open("./server_file/userInfo.txt", O_CREAT | O_RDWR | O_APPEND, 0666); // 写入文件
            int flag = 0;
            // 判断是否读到同名
            zhuce info3;
            int ret = read(fd, &info3, sizeof(info3));
            while (ret != 0) // 判断是否到达文件末尾
            {
                if (!strcmp(info3.name, info1->name)) // 名字相同
                {
                    strcpy(buf, "\e[31m注册名字已存在,注册失败\e[0m");
                    puts(buf);
                    memcpy(pack.msgBlock, buf, sizeof(buf));
                    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                    flag = 1;
                    close(fd);
                    break;
                }
                ret = read(fd, &info3, sizeof(info3));
            }
            if (flag == 0) // 没有重名的
            {
                // 为注册用户在系统创建个人文件夹
                int dir_mode = 0755; // 目录权限
                char dirname[128];
                sprintf(dirname, "./server_file/%s", info1->name);
                if (mkdir(dirname, dir_mode) == -1)
                {
                    if (errno != EEXIST) // 如果创建失败
                    {
                        perror("Failed to create directory A");
                        continue;
                    }
                }
                write(fd, info1, sizeof(zhuce));
                close(fd); //
                strcpy(buf, "\e[1;32m注册成功\e[0m");
                puts(buf);
                memcpy(pack.msgBlock, buf, sizeof(buf));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                continue;
            }
        }
```

 头文件

​    头文件定义了各种消息类型和消息结构体以及主要头文件

```
#ifndef __REGISTER_H__
#define __REGISTER_H__
#include <malloc.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
#include <wxx.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <time.h>
#include <sys/types.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <dirent.h>
#define REGISTER 1
#define DENGLU 2
#define SEEKPWD 3
#define TUICHU 4
#define QUNLIAO 5
#define DANLIAO 6
#define JIAQUN 7
#define QUNMSG 8
#define APPLY 9
#define REQUEST 10
#define REFUSE 11
#define AGREE 12
#define NOBODY 13
#define HAOYOU 14
#define UEXIT 15
#define XIUGAI 16
#define ZHUXIAO 17
#define DONGTAI 18
#define JIRU 19
#define CSFILE 20
#define CHUAN 21
#define SHOU 22

void dongHua();
void registerUser(int sd, struct sockaddr_in *addr);
void seekPwd(int sd, struct sockaddr_in *addr);
void deletePwd(int sd, struct sockaddr_in *addr);
void exitSys(int sd, struct sockaddr_in *addr);
void dengluUser(int sd, struct sockaddr_in *addr);
void next_menu(int sd, struct sockaddr_in *addr);
void changePwd(int sd, struct sockaddr_in *addr);
void *work(void *p);
// 消息载体结构体
typedef struct package
{
    char msgType;        // 消息类型
    char msgBlock[1024]; // 消息内容载体
} package;

// 注册结构体
typedef struct zhuce
{
    char name[128];
    char tel[128];
    char pwd[128];
} zhuce;

// 登入结构体
typedef struct denglu
{
    char name[128];
    char pwd[128];
} denglu;

// 找回密码结构体
typedef struct mima
{
    char name[128];
    char tel[128];
} mima;

typedef struct newPwd
{
    char name[128];
    char opwd[128];
    char npwd[128];
} newPwd;
// 单聊结构体
typedef struct ptalk
{
    int flag;
    char srcname[128]; // 自己名字
    char desname[128]; // 目标名字
    char text[256];    // 聊天内容
} ptalk;

// 群聊结构体
typedef struct mtalk
{
    char type;
    char name[128]; // 自己名字
    char text[256]; // 内容
} mtalk;

// 将用户的地址信息和名字绑定起来,便用服务器通过名字转发消息
typedef struct userAddrInfo
{
    char name[128];
    struct sockaddr_in addr;
} userAddrInfo;

typedef struct friendApr
{
    char type;
    char myname[128];  // 自己名字
    char hisname[128]; // 要添加好友的名字
} friendApr;

// 还可以保存其他消息
typedef struct friend
{
    char name[40]; // 好友名字
    char time[40];
}
friend;

typedef struct friendState
{
    char name[40];   // 名字
    char isLine[10]; // 受否在线
} friendState;

typedef struct friendList
{
    char name[128];        // 谁的好友列表
    friendState flist[15]; // 好友列表,由于数据包大小原因,目前只设置最多15个好友
    int num;               // 好友数量
} friendList;

typedef struct pyq
{
    char name[40];  // 谁发的
    char time[40];  // 什么时候发的
    char text[256]; // 发了什么
    int flag;       // 0代表发朋友圈,1代表看朋友圈,2代表最后一条朋友圈
} pyq;

typedef struct ltjr
{
    char time[30];  // 什么时候
    char name[20];  // 谁说的
    char text[256]; // 说了什么
} ltjr;
// 用户进程发送消息的地址结构体

typedef struct opjr
{
    int flag;         // 0代表清空,1代表查看
    int isOK;         // 0代表没有,1代表完成
    char desname[20]; // 谁要看
    char srcname[20]; // 看谁的
    ltjr lt;          // 一条聊天记录
} opjr;

typedef struct fileBlock
{
    int isOK;       // 1表示传完了
    int bytes;      // 文件块大小
    char text[800]; // 文件快内容
} fileBlock;

typedef struct chuanShou
{
    char type; // 传还是收文件
    char filename[100];
    char desname[20]; // 发给谁
    char srcname[20]; // 谁发的
    fileBlock data;
} chuanShou;

typedef struct workAddr
{
    int fd;
    struct sockaddr_in addr;
} workAddr;
#endif
```

 登录

​    用户输入账号和密码发送给服务器判断,如果账号未注册,或者密码错误,以及账号已经登录了,则登录失败,反之登录成功,进入下一级菜单

客户端代码如下:

```
void dengluUser(int sd, struct sockaddr_in *addr)
{
    denglu info;
    puts("\e[33m请输入你要登录的账号和密码:\e[0m");
    scanf("%s %s", info.name, info.pwd);
    package pack;
    memcpy(pack.msgBlock, &info, sizeof(info));
    pack.msgType = DENGLU;
    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
    recvfrom(sd, &pack, sizeof(pack), 0, NULL, NULL);
    if (!strcmp(pack.msgBlock, "登录成功")) // 登入成功继续
    {
        strcpy(loginUserName, info.name); // 拷贝名字用于发送
        next_menu(sd, addr);
    }
    else
    {
        printf("%s\n", pack.msgBlock);
    }
}
```

服务器代码如下:

```
 else if (pack.msgType == DENGLU)
        {
            printf("\e[33m游客登录信息:\e[0m\n");
            denglu *info2 = (denglu *)pack.msgBlock;
            printf("\e[34mname:%s  pwd:%s\e[0m\n", info2->name, info2->pwd);
            int fd = open("./server_file/userInfo.txt", O_RDONLY);
            int flag = 0;
            zhuce info3;
            int ret = read(fd, &info3, sizeof(info3));
            while (ret != 0)
            {
                if (!strcmp(info2->name, info3.name)) // 账号正确
                {
                    if (strcmp(info2->pwd, info3.pwd)) // 密码错误
                    {
                        strcpy(buf, "\e[1;31m密码错误\e[0m");
                        puts(buf);
                        flag = 2;
                        memcpy(pack.msgBlock, buf, sizeof(buf));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                        break;
                    }
                    else
                    {
                        int i;
                        // 判断账号是不是已经登录了
                        for (i = 0; i < users; i++)
                        {
                            if (!strcmp(loginUser[i].name, info2->name))
                            {
                                strcpy(buf, "\e[1;31m账号已经被登录了,登陆失败\e[0m");
                                puts(buf);
                                flag = 3;
                                memcpy(pack.msgBlock, buf, sizeof(buf));
                                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                                break;
                            }
                        }
                        if (i >= users)
                        {
                            strcpy(buf, "登录成功");
                            printf("\e[1;32m用户%s%s\e[0m\n", info3.name, buf);
                            strcpy(pack.msgBlock, buf);
                            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                            // 保存下来,用来收发消息,名字和地址绑定起来发送
                            flag = 1;
                            loginUser[users].addr = clientAddr;        // 保存登录用户的地址
                            strcpy(loginUser[users].name, info3.name); // 保存用户名字
                            users++;                                   // 用户数量加一
                            break;
                        }
                    }
                }
                ret = read(fd, &info3, sizeof(info3));
            }
            if (flag == 0)
            {
                strcpy(buf, "\e[1;31m该账号还没有注册\e[0m");
                puts(buf);
                memcpy(pack.msgBlock, buf, sizeof(buf));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
            }
            close(fd);
        }
```

找回密码

​    客户不记得账号密码时,可通过绑定的电话号码找回原账号密码。

客户端代码如下;

```
void seekPwd(int sd, struct sockaddr_in *addr)
{
    mima mm;
    puts("\e[33m请输入你要找回的账号:\e[0m");
    scanf("%s", mm.name);
    puts("\e[33m请输入你绑定的电话号码来找回密码:\e[0m");
    scanf("%s", mm.tel);
    package pack;
    memcpy(pack.msgBlock, &mm, sizeof(mm));
    pack.msgType = SEEKPWD;
    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
    recvfrom(sd, &pack, sizeof(pack), 0, NULL, NULL);
    printf("%s\n", pack.msgBlock);
}
```

服务器端代码如下:

```
else if (pack.msgType == SEEKPWD)
        {
            mima mm = *(mima *)pack.msgBlock;
            printf("\e[33m游客找回密码消息:\e[0m\n");
            printf("\e[34mname:%s  tel:%s\e[0m\n", mm.name, mm.tel);
            int fd = open("./server_file/userInfo.txt", O_RDONLY);
            zhuce zc;
            int ret = read(fd, &zc, sizeof(zc));
            int flag = 0;
            while (ret)
            {
                if (!strcmp(zc.name, mm.name)) // 账号一样
                {
                    if (!strcmp(zc.tel, mm.tel)) // 电话号码也一样
                    {
                        flag = 1;
                        char buf[128];
                        sprintf(buf, "\e[34m账号密码是:%s\e[0m", zc.pwd);
                        puts(buf);
                        memcpy(pack.msgBlock, buf, sizeof(buf));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                        break;
                    }
                    else // 电话号码不一样
                    {
                        flag = 2;
                        char buf[128] = "\e[1;31m输入的电话号码错误\e[0m";
                        puts(buf);
                        memcpy(pack.msgBlock, buf, sizeof(buf));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                        break;
                    }
                }
                ret = read(fd, &zc, sizeof(zc));
            }
            if (flag == 0)
            {
                char buf[128] = "\e[1;31m输入的账号未注册\e[0m";
                puts(buf);
                memcpy(pack.msgBlock, buf, sizeof(buf));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
            }
            close(fd);
        }
```

 修改密码

​    客户可以通过输入原账号及密码来修改密码

客户端代码如下:

```
void changePwd(int sd, struct sockaddr_in *addr)
{
    newPwd npd;
    printf("\e[33m请输入你要修改的账号:\e[0m");
    scanf("%s", npd.name);
    printf("\e[33m请输入账号原密码:\e[0m");
    scanf("%s", npd.opwd);
    printf("\e[33m请输入账号新密码:\e[0m");
    scanf("%s", npd.npwd);
    package pack;
    pack.msgType = XIUGAI;
    memcpy(pack.msgBlock, &npd, sizeof(npd));
    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
    recvfrom(sd, &pack, sizeof(pack), 0, NULL, NULL);
    printf("%s\n", pack.msgBlock);
}
```

服务器端代码如下:

```
else if (pack.msgType == XIUGAI)
        {
            newPwd npd = *(newPwd *)pack.msgBlock;
            printf("\e[33m游客的修改账号密码消息:\e[0m\n");
            printf("\e[34mname:%s\toname:%s\tnname:%s\e[0m\n", npd.name, npd.opwd, npd.npwd);
            int fd = open("./server_file/userInfo.txt", O_RDONLY);
            zhuce zh;
            int ret = read(fd, &zh, sizeof(zh));
            zhuce sumInfo[100];
            int num = 0;
            int flag = 0;
            while (ret)
            {
                if (!strcmp(zh.name, npd.name)) // 账号相同
                {
                    if (strcmp(zh.pwd, npd.opwd)) // 账号密码不相等
                    {
                        flag = 1;
                        char buf[128] = "\e[31m账号原密码错误,修改密码失败!\e[0m";
                        puts(buf);
                        memcpy(pack.msgBlock, buf, sizeof(buf));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                        break;
                    }
                    else // 找到要修改的账号
                    {
                        flag = 2;
                        strcpy(zh.pwd, npd.npwd); // 修改原密码
                        sumInfo[num++] = zh;
                    }
                }
                else
                {
                    sumInfo[num++] = zh;
                }
                ret = read(fd, &zh, sizeof(zh));
            }
            close(fd);
            if (flag == 0)
            {
                char buf[128] = "\e[1;31m输入的账号未注册\e[0m";
                puts(buf);
                memcpy(pack.msgBlock, buf, sizeof(buf));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
            }
            else if (flag == 2)
            {
                fd = open("./server_file/userInfo.txt", O_TRUNC | O_WRONLY);
                for (int i = 0; i < num; i++)
                {
                    write(fd, &sumInfo[i], sizeof(zhuce));
                }
                close(fd);
                char buf[128] = "\e[31m账号密码修改成功\e[0m";
                puts(buf);
                memcpy(pack.msgBlock, buf, sizeof(buf));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
            }
        }
```

注销账号

​    删除服务器为用户创建的所有数据和文件,将用户从服务器的用户账号信息表中删除。

客户端代码如下:

```
void deletePwd(int sd, struct sockaddr_in *addr)
{
    zhuce zh;
    printf("\e[33m请输入你要注销的账号:\e[0m");
    scanf("%s", zh.name);
    printf("\e[33m请输入你绑定的电话号码:\e[0m");
    scanf("%s", zh.tel);
    printf("\e[33m请输入你的密码:\e[0m");
    scanf("%s", zh.pwd);
    package pack;
    pack.msgType = ZHUXIAO;
    memcpy(pack.msgBlock, &zh, sizeof(zh));
    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
    recvfrom(sd, &pack, sizeof(pack), 0, NULL, NULL);
    printf("%s\n", pack.msgBlock);
}
```

服务器端代码如下:

```
else if (pack.msgType == ZHUXIAO)
        {
            zhuce np = *(zhuce *)pack.msgBlock;
            printf("\e[33m游客的注销账号消息:\e[0m\n");
            printf("\e[34mname:%s\ttel:%s\tpwd:%s\n\e[0m", np.name, np.tel, np.pwd);
            // 删除该用户在系统的所有文件和痕迹
            int fd = open("./server_file/userInfo.txt", O_RDONLY);
            zhuce zh;
            int ret = read(fd, &zh, sizeof(zh));
            zhuce sumInfo[100];
            int num = 0;
            int flag = 0;
            while (ret)
            {
                if (!strcmp(zh.name, np.name)) // 本人
                {
                    if (strcmp(zh.pwd, np.pwd)) // 密码错误
                    {
                        flag = 1;
                        char buf[128] = "\e[31m密码错误,注销失败\e[0m";
                        puts(buf);
                        memcpy(pack.msgBlock, buf, sizeof(buf));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                        break;
                    }
                    if (strcmp(zh.tel, np.tel))
                    {
                        flag = 1;
                        char buf[128] = "\e[31m电话号码错误,注销失败\e[0m";
                        puts(buf);
                        memcpy(pack.msgBlock, buf, sizeof(buf));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                        break;
                    }
                    flag = 2;
                }
                else
                {
                    sumInfo[num++] = zh;
                }
                ret = read(fd, &zh, sizeof(zh));
            }
            close(fd);
            if (flag == 0)
            {
                char buf[128] = "\e[1;31m输入的账号未注册\e[0m";
                puts(buf);
                memcpy(pack.msgBlock, buf, sizeof(buf));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
            }
            else if (flag == 2)
            {
                // 删除服务器为注册用户创建的个人文件夹和里面的文件
                char dirname[128];
                sprintf(dirname, "./server_file/%s", np.name);
                DIR *dp = opendir(dirname);
                struct dirent *mem;
                while ((mem = readdir(dp)) != NULL)
                {

                    if (strcmp(mem->d_name, ".") == 0 || strcmp(mem->d_name, "..") == 0)
                    {
                        continue;
                    }
                    char filename[128];
                    sprintf(filename, "%s/%s", dirname, mem->d_name);
                    remove(filename);
                }
                closedir(dp);
                rmdir(dirname);

                fd = open("./server_file/userInfo.txt", O_TRUNC | O_WRONLY);
                for (int i = 0; i < num; i++)
                {
                    write(fd, &sumInfo[i], sizeof(zhuce));
                }
                close(fd);
                char buf[128] = "\e[31m账号注销成功\e[0m";
                puts(buf);
                memcpy(pack.msgBlock, buf, sizeof(buf));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
            }
        }
```

 退出

​    客户退出功能实现比较简单,告诉服务器退出后,清屏终端界面

客户端代码如下:

```
void exitSys(int sd, struct sockaddr_in *addr)
{
    package pack;
    char buf[128] = "退出系统";
    memcpy(pack.msgBlock, buf, sizeof(buf));
    pack.msgType = TUICHU;
    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
    recvfrom(sd, &pack, sizeof(pack), 0, NULL, NULL);
    printf("\e[H\e[J"); // 清屏
    printf("%s\n", pack.msgBlock);
    close(sd);
    exit(0);
}
```

服务器代码如下:

```
 else if (pack.msgType == TUICHU)
        {
            printf("\e[34m游客退出消息:%s\e[0m\n", pack.msgBlock);
            char buf[128] = "\e[1;32m退出成功\e[0m";
            puts(buf);
            memcpy(pack.msgBlock, buf, sizeof(buf));
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
        }
```

下一级菜单

1.群聊

​    客户加入群聊后,可以收到群内成员发送的消息,也可以给群内所有成员发送信息,谁加入和退出群聊都可以告诉群内其他成员,客户可以输入quit主动退出群聊:

客户端代码如下:

```
case 1: // 群聊
        {
            // 告诉系统你要加入群聊
            mtalk mta;
            package pack;
            mta.type = JIAQUN;                        // 加群申请,不输出消息内容,只保存地址信息和名字
            strcpy(mta.name, loginUserName);          // 告诉大家是谁群发的消息
            memset(mta.text, 0, sizeof(mta.text));    // 没有内容
            memcpy(pack.msgBlock, &mta, sizeof(mta)); // 数据封装
            pack.msgType = QUNLIAO;
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));

            printf("\e[33m\t华清嵌入式2403班级群\e[0m\n"); // 进入群聊
            printf("\e[37msystem:输入quit可退出群聊\e[0m\n");
            while (1)
            {
                scanf("%s", mta.text);
                mta.type = QUNMSG;
                memcpy(pack.msgBlock, &mta, sizeof(mta)); // 数据封装
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
                if (!strcmp(mta.text, "quit"))
                {
                    printf("\32m退出群聊成功\e[0m\n");
                    break;
                }
            }
            sem_post(&sem1);
        }
        break;
```

客户接收线程代码如下:

```
if (pack.msgType == QUNLIAO)                            // 群聊
        {
            mtalk mta = *(mtalk *)pack.msgBlock; // 拆包
            printf("\e[34m%s(群发):%s\n\e[0m", mta.name, mta.text);
        }
```

服务器代码如下:

```
else if (pack.msgType == QUNLIAO)
        {
            mtalk mta = *(mtalk *)pack.msgBlock;
            if (mta.type == JIAQUN) // 用户加入群聊
            {
                strcpy(groupMem[groupNum].name, mta.name); // 保存群员名字
                groupMem[groupNum].addr = clientAddr;      // 保存群员地址
                groupNum++;
                // 告诉群员谁加入群聊了
                char buf[128];
                sprintf(buf, "\e[31m%s加入群聊\e[0m", mta.name);
                printf("%s\n", buf);
                strcpy(mta.text, buf);
                memcpy(&pack.msgBlock, &mta, sizeof(mta));
                for (int i = 0; i < groupNum; i++)
                {
                    // 告诉其他群成员谁退出群聊了
                    if (strcmp(mta.name, groupMem[i].name))
                    {
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&groupMem[i].addr, length);
                    }
                }
            }
            else // 用户发送群消息
            {
                if (!strcmp(mta.text, "quit")) // 用户退出群聊,删除群成员
                {
                    char buf[128];
                    sprintf(buf, "\e[31m%s退出群聊\e[0m", mta.name);
                    printf("%s\n", buf);
                    strcpy(mta.text, buf);
                    memcpy(&pack.msgBlock, &mta, sizeof(mta));
                    for (int i = 0; i < groupNum; i++)
                    {
                        // 告诉其他群成员谁退出群聊了
                        if (strcmp(mta.name, groupMem[i].name))
                        {
                            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&groupMem[i].addr, length);
                        }
                    }
                    // 删除它
                    for (int i = 0; i < groupNum; i++)
                    {
                        if (!strcmp(mta.name, groupMem[i].name)) // 找到他
                        {
                            for (int j = i; j < groupNum - 1; j++)
                            {
                                groupMem[j] = groupMem[j + 1];
                            }
                        }
                    }
                    groupNum--; // 数量减一
                }
                else
                {
                    printf("\e[34m%s的群发消息:%s\n\e[0m", mta.name, mta.text);
                    for (int i = 0; i < groupNum; i++)
                    {
                        // 由于名字是唯一的,因此可以通过比较名字来判断是不是自己,不是自己就转发
                        if (strcmp(mta.name, groupMem[i].name))
                        {
                            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&groupMem[i].addr, length);
                        }
                    }
                }
            }
        }
```

2.添加好友

​    客户可通过输入其他客户名字进行好友添加,必须是在线且注册过的名字

客户端代码如下:

```
case 2: // 只能添加登录用户为好友
        {
            friendApr fri;
            package pack;
            pack.msgType = APPLY;
            strcpy(fri.myname, loginUserName); // 谁申请添加好友
            printf("\e[33m请输入你要添加的好友名字:\e[0m");
            scanf("%s", fri.hisname);
            fri.type = REQUEST;
            memcpy(pack.msgBlock, &fri, sizeof(fri));
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
            sem_post(&sem1);
        }
        break;
```

客户接收线程如下:

```
else if (pack.msgType == APPLY)
        {
            friendApr fri = *(friendApr *)pack.msgBlock;
            // 只有接收方才能收到
            if (fri.type == REQUEST) // 请求添加好友
            {
                printf("\e[31m%s申请添加你为好友\e[0m\n", fri.myname);
                sem_wait(&sem2);
                printf("\e[31m%s申请添加你为好友\e[0m\n", fri.myname);
                printf("\e[33m请输入你的选择(同意或者拒绝):\e[0m");
                char sel[20];
                scanf("%s", sel);
                if (!strcmp(sel, "同意")) // 同意好友申请
                {
                    fri.type = AGREE; // 修改状态,同意好友申请
                    memcpy(pack.msgBlock, &fri, sizeof(fri));
                    sendto(waddr.fd, &pack, sizeof(pack), 0, (struct sockaddr *)&waddr.addr, sizeof(struct sockaddr_in));
                }
                else // 拒绝
                {
                    fri.type = REFUSE; // 修改为拒绝
                    memcpy(pack.msgBlock, &fri, sizeof(fri));
                    sendto(waddr.fd, &pack, sizeof(pack), 0, (struct sockaddr *)&waddr.addr, sizeof(struct sockaddr_in));
                }
                sem_post(&sem1);
            }
            // 只有发送好友申请的才能收到
            else if (fri.type == AGREE)
            {
                printf("\e[31m%s同意了你的好友申请,你们已经是好友了\e[0m\n", fri.hisname);
            }
            //
            else if (fri.type == REFUSE)
            {
                printf("\e[31m%s拒绝了你的好友申请\e[0m\n", fri.hisname);
            }
            else if (fri.type == NOBODY)
            {
                printf("%s\n", fri.myname);
            }
        }
```

服务器代码如下:

```
else if (pack.msgType == APPLY)
        {
            friendApr fri = *(friendApr *)pack.msgBlock;
            if (fri.type == REQUEST)
            {
                printf("\e[31m%s的好友申请消息:请求添加%s为好友\e[0m\n", fri.myname, fri.hisname);
                int i;
                for (i = 0; i < users; i++)
                {
                    if (!strcmp(loginUser[i].name, fri.hisname)) // 找到要添加的好友
                    {
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&loginUser[i].addr, length); // 转发好友申请
                        break;
                    }
                }
                if (i >= users) // 没有找个人
                {
                    sprintf(fri.myname, "\e[31m%s未上线或者不存在,添加失败!\e[0m", fri.hisname);
                    printf("%s\n", fri.myname);
                    fri.type = NOBODY;
                    memcpy(pack.msgBlock, &fri, sizeof(fri));
                    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
                }
            }
            else if (fri.type == REFUSE)
            {
                printf("\e[31m%s拒绝了%s的好友申请!\e[0m\n", fri.hisname, fri.myname);
                for (int i = 0; i < users; i++)
                {
                    if (!strcmp(loginUser[i].name, fri.myname)) // 找到发好友申请的人
                    {
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&loginUser[i].addr, length); // 转发好友申请
                        break;
                    }
                }
            }
            else if (fri.type == AGREE)
            {
                printf("\e[31m%s同意了%s的好友申请\e[0m\n", fri.hisname, fri.myname);
                // 他把我加入好友列表
                char fileName[128];
                sprintf(fileName, "./server_file/%s/friends.dat", fri.hisname);
                // 创建文件保存用户的好友
                int fd = open(fileName, O_CREAT | O_WRONLY | O_APPEND, 0666);
                friend friInfo;
                strcpy(friInfo.name, fri.myname); // 写入好友名字
                time_t t1;
                time(&t1);
                struct tm *now;
                now = localtime(&t1);
                char nowtime[128];
                sprintf(nowtime, "%d年%d月%d日%d时", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour);
                strcpy(friInfo.time, nowtime);        // 写入时间
                write(fd, &friInfo, sizeof(friInfo)); // 写入文件
                close(fd);
                // 我把他加入好友列表
                sprintf(fileName, "./server_file/%s/friends.dat", fri.myname);
                fd = open(fileName, O_CREAT | O_WRONLY | O_APPEND, 0666);
                strcpy(friInfo.name, fri.hisname);    // 写入好友名字
                strcpy(friInfo.time, nowtime);        // 写入时间
                write(fd, &friInfo, sizeof(friInfo)); // 写入文件
                close(fd);
                for (int i = 0; i < users; i++)
                {
                    if (!strcmp(loginUser[i].name, fri.myname)) // 找到发好友申请的人
                    {
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&loginUser[i].addr, length); // 转发好友申请
                        break;
                    }
                }
            }
        }
```

 3.好友申请

​    客户给其他客户发送好友申请后会提示其他用户,其他用户可以通过好友申请,主动输入同意或者拒绝,输入同意则成为好友,输入拒绝则添加失败:

客户端线程接受代码同上;

客户端代码如下,因为需要在子线程进行输入,所以必须先抵消掉主线程的输入,然后锁住主线程,等待子线程执行完后释放主线程,所以设置了好友申请这一环节,目的在于抵消掉主线程的读取输入;

```
 case 3:
            sem_post(&sem2);
            break;
```

服务器代码同上;

4.好友列表

​    客户可以查看自己添加的所有好友以及好友的状态,在线,离线,注销,我在这里设置了一个好友结构体数组来传输所有好友信息给客户端;

客户端代码如下

```
 case 4:
        {
            friendList fli;
            package pack;
            pack.msgType = HAOYOU;
            // 初始化
            strcpy(fli.name, loginUserName);
            memset(&fli.flist, 0, sizeof(fli.flist));
            fli.num = 0;
            memcpy(pack.msgBlock, &fli, sizeof(fli));
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
            sem_post(&sem3);
        }
```

客户接收线程代码如下:

```
 else if (pack.msgType == HAOYOU)
        {
            sem_wait(&sem3);
            friendList fli = *(friendList *)pack.msgBlock;
            printf("----\e[36m%s的好友列表\e[0m----\n", fli.name);
            if (fli.num == 0)
            {
                printf("\n[1;31m你还没有好友!\e[0m\n");
            }
            else
            {
                printf("\e[33m共有%d位好友\e[0m\n", fli.num);
                printf("\e[32m好友\t状态\e[0m\n");
                for (int i = 0; i < fli.num; i++)
                {
                    if (!strcmp(fli.flist[i].isLine, "在线"))
                    {
                        printf("\e[34m%s\t%s\e[0m\n", fli.flist[i].name, fli.flist[i].isLine);
                    }
                    else
                    {
                        printf("%s\t%s\n", fli.flist[i].name, fli.flist[i].isLine);
                    }
                }
            }
            sem_post(&sem1);
        }
```

服务器代码如下:

```
else if (pack.msgType == HAOYOU)
        {
            friendList fli = *(friendList *)pack.msgBlock;
            printf("用户%s查看好友列表\n", fli.name);
            char filename[128];
            sprintf(filename, "./server_file/%s/friends.dat", fli.name);
            int fd = open(filename, O_RDONLY);
            friend fri;
            int ret = read(fd, &fri, sizeof(fri));
            while (ret)
            {
                strcpy(fli.flist[fli.num].name, fri.name);
                int fd2 = open("./server_file/userInfo.txt", O_RDONLY);
                zhuce zh;
                int flag = 0;
                while (read(fd2, &zh, sizeof(zh)))
                {
                    if (!strcmp(zh.name, fri.name))
                    {
                        flag = 1;
                        break;
                    }
                }
                // 判断好友是否在线
                if (flag == 1)
                {
                    int i;
                    for (i = 0; i < users; i++)
                    {
                        if (!strcmp(fri.name, loginUser[i].name))
                        {
                            strcpy(fli.flist[fli.num].isLine, "在线");
                            break;
                        }
                    }
                    if (i >= users)
                    {
                        strcpy(fli.flist[fli.num].isLine, "离线");
                    }
                }
                else
                {
                    strcpy(fli.flist[fli.num].isLine, "注销");
                }
                fli.num++;
                ret = read(fd, &fri, sizeof(fri));
            }
            memcpy(pack.msgBlock, &fli, sizeof(fli));
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, length);
        }
```

5.返回上一级菜单

​    退出至登入界面菜单,将客户从登入用户里面删除

客户端代码如下:

```
 case 5:
        {
            package pack;
            pack.msgType = UEXIT;
            memcpy(pack.msgBlock, loginUserName, sizeof(loginUserName));
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
            pthread_cancel(pt); // 取消子线程
            printf("\e[H\e[J");
            return;
        }
```

服务器端代码如下:

```
else if (pack.msgType == UEXIT)
        {
            printf("\e[31m%s退出登录\e[0m\n", pack.msgBlock);
            // 删除他
            for (int i = 0; i < users; i++)
            {
                if (!strcmp(pack.msgBlock, loginUser[i].name))
                {
                    for (int j = i; j < users - 1; j++)
                    {
                        loginUser[j] = loginUser[j + 1];
                    }
                    break;
                }
            }
            users--;
        }
```

6.私聊

​    客户可以跟他的在线好友进行一对一聊天

客户端代码如下:

```
case 6:
        {
            package pack;
            pack.msgType = DANLIAO;
            ptalk ptk;
            strcpy(ptk.srcname, loginUserName);
            printf("\e[33m请输入你要单聊的好友名字:\e[0m");
            scanf("%s", ptk.desname);
            ptk.flag = 0;
            // 判断是不是好有,好友在不在线
            while (1)
            {
                printf("\e[34m请输入你要发送的消息:\e[0m");
                scanf("%s", ptk.text);
                memcpy(pack.msgBlock, &ptk, sizeof(ptk));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
                if (!strcmp(ptk.text, "quit"))
                {
                    printf("\e[32m退出私聊成功\e[0m\n");
                    break;
                }
            }
            sem_post(&sem1);
        }
        break;
```

客户端接收线程代码如下:

```
 else if (pack.msgType == DANLIAO) // 私聊
        {
            ptalk pta = *(ptalk *)pack.msgBlock;
            if (pta.flag == 1)
            {
                printf("\e[31m服务器提示消息:%s\e[0m\n", pta.text);
                printf("\e[33m请输入quit退出私聊界面\e[0m\n");
            }
            else if (pta.flag == 0)
            {
                if (!strcmp(pta.text, "quit"))
                {
                    printf("\e[33m好友%s退出聊天了\e[0m\n", pta.srcname);
                }
                else
                {
                    printf("\e[34m好友%s(私发):%s\e[0m\n", pta.srcname, pta.text);
                }
            }
        }
```

服务器接收代码如下:

```
 else if (pack.msgType == DANLIAO)
        {
            ptalk ptk = *(ptalk *)pack.msgBlock;
            printf("\e[34m%s私发给%s的消息:%s\e[0m\n", ptk.srcname, ptk.desname, ptk.text);
            // 先判断是不是好友
            char fileName[128];
            sprintf(fileName, "./server_file/%s/friends.dat", ptk.srcname);
            int fd = open(fileName, O_RDONLY);
            friend fri;
            int flag = 0;
            int ret = read(fd, &fri, sizeof(fri));
            while (ret)
            {
                if (!strcmp(fri.name, ptk.desname)) // 他是你的好友
                {
                    flag = 1;
                    // 判断好友是否在线
                    for (int i = 0; i < users; i++)
                    {
                        if (!strcmp(ptk.desname, loginUser[i].name)) // 在线
                        {
                            flag = 2;
                            printf("\e[33m%s是%s的好友而且在线,可以私聊\e[0m\n", ptk.desname, ptk.srcname);
                            // 保存聊天记录
                            char filename[128];
                            sprintf(filename, "./server_file/%s/%s.dat", ptk.srcname, ptk.desname);
                            int fd2 = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666); // 创建聊天记录文件夹
                            ltjr lt;
                            strcpy(lt.name, ptk.srcname); // 谁发的
                            strcpy(lt.text, ptk.text);
                            time_t t1;
                            time(&t1);
                            struct tm *now;
                            now = localtime(&t1);
                            char nowtime[128];
                            sprintf(nowtime, "%d月%d日%d时%d分", now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min);
                            strcpy(lt.time, nowtime); // 写入时间
                            write(fd2, &lt, sizeof(lt));
                            close(fd2);
                            sprintf(filename, "./server_file/%s/%s.dat", ptk.desname, ptk.srcname);
                            fd2 = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666); // 创建聊天记录文件夹
                            write(fd2, &lt, sizeof(lt));
                            close(fd2);
                            //
                            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&loginUser[i].addr, sizeof(struct sockaddr_in));
                            break;
                        }
                    }
                    break;
                }
                ret = read(fd, &fri, sizeof(fri));
            }
            close(fd);
            if (flag != 2)
            {
                ptk.flag = 1;
                char buf[128];
                if (flag == 0)
                {
                    sprintf(buf, "\e[31m%s不是%s的好友,不可以私聊\e[0m", ptk.desname, ptk.srcname);
                }
                else if (flag == 1)
                {
                    sprintf(buf, "\e[31m%s的好友%s不在线,不可以私聊\e[0m", ptk.srcname, ptk.desname);
                }
                puts(buf);
                strcpy(ptk.text, buf);
                memcpy(pack.msgBlock, &ptk, sizeof(ptk));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
                recvfrom(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, &length); // 接收quit但不处理
            }
        }
```

7. 朋友圈

发朋友圈

​    客户可以将自己写的说说发送给他的所有好友,

 看朋友圈

​    客户可以通过选择查看朋友圈查看自己和自己所有好友发送的说说

客户端代码如下:

```
 case 7:
        {
            printf("\e[033m1.发朋友圈------2.看朋友圈\e[0m\n");
            int xz;
            scanf("%d", &xz);
            getchar();
            if (xz == 1)
            {
                package pack;
                pack.msgType = DONGTAI;
                pyq tiez;
                tiez.flag = 0; // 告诉服务器这是发朋友圈
                strcpy(tiez.name, loginUserName);
                time_t t1;
                time(&t1);
                struct tm *now;
                now = localtime(&t1);
                char nowtime[128];
                sprintf(nowtime, "%d年%d月%d日%d时%d分", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min);
                strcpy(tiez.time, nowtime); // 写入时间
                printf("\e[33m请输入你要发表的说说:\e[0m");
                scanf("%[^\n]", tiez.text);
                memcpy(pack.msgBlock, &tiez, sizeof(tiez));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
            }
            else if (xz == 2)
            {
                printf("----------\e[1;32m朋友圈\e[0m----------\n");
                package pack;
                pack.msgType = DONGTAI;
                pyq tiez;
                tiez.flag = 1;
                strcpy(tiez.name, loginUserName);
                memset(tiez.text, 0, sizeof(tiez.text));
                memset(tiez.time, 0, sizeof(tiez.time));
                memcpy(pack.msgBlock, &tiez, sizeof(tiez));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in)); // 告诉服务器你要看朋友圈
            }
            sem_post(&sem5);
        }
        break;
```

客户端接收线程代码如下:

```
else if (pack.msgType == DONGTAI)
        {
            sem_wait(&sem5);
            pyq tiez = *(pyq *)pack.msgBlock;
            if (tiez.flag == 0)//发朋友圈
            {
                printf("\e[31m发表成功,等待好友评论和点赞\e[0m\n");
                sem_post(&sem1);
            }
            else if (tiez.flag == 1) // ,查看朋友圈,至少有一条朋友圈
            {
                while (1)
                {
                    printf("\e[36m%s\t%s\e[0m\n", tiez.time, tiez.name);
                    printf("  \e[34m%s\n\n\e[0m", tiez.text);
                    recvfrom(waddr.fd, &pack, sizeof(pack), 0, NULL, NULL);
                    tiez = *(pyq *)pack.msgBlock;
                    if (tiez.flag == 2)
                    {
                        break;
                    }
                }
                sem_post(&sem1);
            }
            else if (tiez.flag == 3) // 朋友圈为空
            {
                printf("\e[31m%s\t%s\e[0m\n", tiez.time, tiez.name);
                printf("  \e[31m%s\n\n\e[0m", tiez.text);
                sem_post(&sem1);
            }
        }
```

服务器端代码如下:

```
else if (pack.msgType == DONGTAI)
        {
            pyq tiez = *(pyq *)pack.msgBlock;
            if (tiez.flag == 0)
            {
                printf("\e[033m%s\e[0m\n", tiez.time);
                printf("\e[34m%s发的朋友圈:%s\e[0m\n", tiez.name, tiez.text);
                char filename[128];
                sprintf(filename, "./server_file/%s/pyq.dat", tiez.name);
                int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
                write(fd, &tiez, sizeof(tiez));
                close(fd);
                sprintf(filename, "./server_file/%s/friends.dat", tiez.name);
                fd = open(filename, O_RDONLY | O_CREAT, 0666);
                friend fri;
                int ret = read(fd, &fri, sizeof(fri));
                // 应该判断一下好友注销没
                while (ret)
                {
                    int fd3 = open("./server_file/userInfo.txt", O_RDONLY);
                    zhuce zh;
                    int flag = 0;
                    while (read(fd3, &zh, sizeof(zh)))
                    {
                        if (!strcmp(fri.name, zh.name))
                        {
                            flag = 1; // 没有注销
                            break;
                        }
                    }
                    close(fd3);
                    if (flag == 1)
                    {
                        sprintf(filename, "./server_file/%s/pyq.dat", fri.name);
                        int fd2 = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
                        write(fd2, &tiez, sizeof(tiez));
                        close(fd2);
                    }
                    ret = read(fd, &fri, sizeof(fri));
                }
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in)); // 告诉用户发表成功
                close(fd);
            }
            else if (tiez.flag == 1) // 看朋友圈,把朋友圈的系统文件发送给用户
            {
                printf("\e[34m用户%s查看朋友圈\e[0m\n", tiez.name);
                char filename[128];
                sprintf(filename, "./server_file/%s/pyq.dat", tiez.name);
                int fd = open(filename, O_RDONLY); // 打开保存用户朋友圈的服务器文件
                if (fd < 0)                        // 如果没有人发过朋友圈
                {
                    close(fd);
                    strcpy(tiez.text, "你的朋友圈为空");
                    strcpy(tiez.name, "");
                    strcpy(tiez.time, "");
                    tiez.flag = 3; // 告诉客户发完了
                    memcpy(pack.msgBlock, &tiez, sizeof(tiez));
                    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
                    perror("open fail");
                }
                else
                {
                    while (read(fd, &tiez, sizeof(tiez))) // 取出朋友圈
                    {
                        printf("\e[36m%s\t%s\e[0m\n", tiez.time, tiez.name);
                        printf("  \e[34m%s\n\n\e[0m", tiez.text);
                        tiez.flag = 1; // TMD,我忘了我朋友圈文件里面也有标准位
                        memcpy(pack.msgBlock, &tiez, sizeof(tiez));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
                        usleep(4); // 延时一下,然客户端接收
                    }
                    close(fd);
                    tiez.flag = 2; // 告诉客户发完了
                    memcpy(pack.msgBlock, &tiez, sizeof(tiez));
                    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
                }

                printf("\e[32m朋友圈发送完毕\n\e[0m");
            }
        }
```

8. 聊天记录

查看聊天记录

​    客户可以通过好友名字查看和好友的聊天记录

 清空聊天记录

​    客户可以通过好友名字清空与该好友的聊天记录

客户端代码如下:

```
case 8:
        {
            printf("\e[35m请输入你要操作的好友名:\e[0m");
            char name[20];
            scanf("%s", name);
            printf("\e[033m1.查看聊天记录------2.清空聊天记录\e[0m\n");
            int xz;
            scanf("%d", &xz);
            opjr op1;
            strcpy(op1.srcname, loginUserName);
            strcpy(op1.desname, name);
            memset(&op1.lt, 0, sizeof(op1.lt));
            op1.isOK = 0;
            if (xz == 1)
            {
                op1.flag = 1;
            }
            else if (xz == 2)
            {
                op1.flag = 0;
            }
            package pack;
            pack.msgType = JIRU;
            memcpy(pack.msgBlock, &op1, sizeof(op1));
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
            sem_post(&sem4);
        }
        break;
```

客户端接收线程代码如下:

```
else if (pack.msgType == JIRU)
        {
            sem_wait(&sem4);
            opjr op1 = *(opjr *)pack.msgBlock;
            if (op1.flag == 0) // 清空完成
            {
                printf("\e[31m清空聊天记录成功\n\e[0m");
                sem_post(&sem1); // 回到菜单栏
            }
            else // 查看聊天记录
            {
                // 想查看那一天的聊天记录也可以,只需要对时间进行分类查看输出就行
                if (op1.isOK == 0) // 还没传完
                {
                    if (strcmp(op1.lt.time, temp.time)) // 时间不相同
                    {
                        // 时间居中对齐输出
                        for (int i = 0; i < (tty_size - strlen(op1.lt.time) + 4) / 2; i++)
                            printf(" ");
                        printf("%s\n", op1.lt.time);
                    }
                    if (strcmp(loginUserName, op1.lt.name)) // 不是自己发的
                    {
                        if (strcmp(op1.lt.text, "quit"))
                        {
                            printf("\e[34m%s:%s\e[0m\n", op1.lt.name, op1.lt.text); // 蓝色对方用,自古红蓝出CP
                        }
                    }
                    else
                    {
                        /*因为在UTF-8编码中汉字占3个字节,但在终端输出是占2个空格
                          因此汉字字符串要乘以2/3,但因为字符串中不全是汉字,因此算出来的
                          结果会偏小,输出的空格过多,以至于想要字符串输出紧靠终端行末而溢出
                          到下一行,在这里没有实现汉字和普通字符的区分,有待下一步提高
                        */
                        if (strcmp(op1.lt.text, "quit")) // 不打印quit
                        {
                            char buf[256];
                            sprintf(buf, "%s:%s", op1.lt.text, op1.lt.name);
                            for (int i = 0; i < tty_size - strlen(buf) * 2 / 3 - 2; i++)
                                printf(" ");
                            printf("\e[31m%s\n\e[0m", buf);
                        }
                    }
                    temp = op1.lt;   // temp是上一条聊天记录
                    sem_post(&sem4); // 让线程继续接收消息
                }
                else // 传输完成
                {
                    sem_post(&sem1);
                }
            }
        }
```

服务器端代码如下:

```
else if (pack.msgType == JIRU)
        {
            opjr op1 = *(opjr *)pack.msgBlock;
            if (op1.flag == 1)
            {
                printf("\e[34m用户%s想要查看和%s的聊天记录\e[0m\n", op1.srcname, op1.desname);
                char filename[128];
                sprintf(filename, "./server_file/%s/%s.dat", op1.srcname, op1.desname);
                int fd = open(filename, O_RDONLY);
                ltjr lt;
                while (read(fd, &lt, sizeof(lt)))
                {
                    op1.lt = lt;
                    memcpy(pack.msgBlock, &op1, sizeof(op1));
                    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
                    usleep(4); // 延时一下,然客户端接收
                }
                op1.isOK = 1; // 告诉用户发完了
                memcpy(pack.msgBlock, &op1, sizeof(op1));
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
            }
            else
            {
                printf("\e[34m用户%s想要清空和%s的聊天记录\e[0m\n", op1.srcname, op1.desname);
                char filename[128];
                sprintf(filename, "./server_file/%s/%s.dat", op1.srcname, op1.desname);
                int fd = open(filename, O_TRUNC); // 清空文件
                close(fd);
                sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
            }
        }
```

9.传输文件

​    用户输入指定好友名字和需要传输的文件名字(包括路径)上传至服务器,服务器会暂时保存该文件,上传完后服务器会通知用户的好友。

服务器代码如下:

```
 case 9:
        {
            package pack;
            pack.msgType = CSFILE;
            printf("\e[33m请输入你要发送的好友名:\e[0m");
            chuanShou chs;
            scanf("%s", chs.desname);
            strcpy(chs.srcname, loginUserName);
            chs.type = CHUAN;
            printf("\e[33m请输入你要发送的文件名:\e[0m");
            scanf("%s", chs.filename);             // 把路径也传过去了,要去掉文件路径
            int fd = open(chs.filename, O_RDONLY); // 打开文件
            char *ch;
            for (int i = 0; i < strlen(chs.filename); i++)
            {
                if (chs.filename[i] == '/')
                {
                    ch = &chs.filename[i];
                }
            }
            ch++; // 去掉文件的路径,只保留文件名
            puts(ch);
            if (fd < 0)
            {
                perror("open fail");
                close(fd);
                continue;
            }
            else
            {
                struct stat info;
                stat(chs.filename, &info);
                int leftSize = info.st_size; // 取出传输文件大小文件
                strcpy(chs.filename, ch);    // 之传输文件名,不包括路径
                printf("%d\n", leftSize);
                int sendSize = 0;
                while (leftSize)
                {
                    if (leftSize > 800)
                    {
                        chs.data.isOK = 0;
                        chs.data.bytes = 800;
                    }
                    else
                    {
                        chs.data.isOK = 1; // 告诉服务器这是最后一个文件块
                        chs.data.bytes = leftSize;
                    }
                    read(fd, chs.data.text, chs.data.bytes);
                    leftSize -= chs.data.bytes;
                    sendSize += chs.data.bytes;
                    memcpy(pack.msgBlock, &chs, sizeof(chs));
                    sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
                    printf("已上传%d字节\n", sendSize);
                    usleep(6);
                }
                close(fd);
                printf("\e[32m文件传输完成\e[0m\n");
            }
            sem_post(&sem1);
        }
        break;
```

10.下载文件

​    客户可以输入指点好友名字和文件名来下载好友给他发送的文件,客户从服务器下载该文件后,服务器会删除该文件,并提示发送文件的客户,”你的好友已经接收了你的文件”

客户端代码如下:

```
case 10:
        {
            package pack;
            pack.msgType = CSFILE;
            printf("\e[33m请输入你要下载文件的好友名:\e[0m");
            chuanShou chs;
            scanf("%s", chs.srcname);
            strcpy(chs.desname, loginUserName);
            chs.type = SHOU;
            printf("\e[33m请输入你要接收的文件名:\e[0m");
            scanf("%s", chs.filename); //
            memset(&chs.data, 0, sizeof(chs.data));
            // 告诉服务器你要下载什么文件
            memcpy(pack.msgBlock, &chs, sizeof(chs));
            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
            // 开始下载文件
            sem_post(&sem6);
        }
        break;
        default:
            sem_post(&sem1);
            break;
        }
```

下载文件和传输文件共用一个消息类型,因此他俩的接收线程代码共同如下:

```
else if (pack.msgType == CSFILE)
        {
            chuanShou chs = *(chuanShou *)pack.msgBlock;
            if (chs.type == CHUAN)
            {
                printf("\e[33m你的好友%s给你发送了一个文件:%s\e[0m\n", chs.srcname, chs.filename);
                continue;
            }
            else if (chs.type == SHOU)
            {
                // 发送方
                if (chs.data.isOK == 2)
                {
                    printf("\e[33m你的好友%s接收了你的文件:%s\e[0m\n", chs.desname, chs.filename);
                    continue;
                }
                else
                {
                    // 接收方
                    sem_wait(&sem6);
                    int dir_mode = 0755; // 目录权限
                    char dirname[128];
                    sprintf(dirname, "./customer_file/%s", loginUserName);
                    if (mkdir(dirname, dir_mode) == -1)
                    {
                        if (errno != EEXIST) // 如果创建失败
                        {
                            perror("Failed to create directory A");
                            continue;
                        }
                    }
                    char clientfile[128];
                    sprintf(clientfile, "./customer_file/%s/%s_%s", loginUserName, chs.srcname, chs.filename); // 指定下载目录,这里我直接指定了一个目录
                    int fd = open(clientfile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
                    int rcvsize = 0;
                    while (1)
                    {
                        if (write(fd, chs.data.text, chs.data.bytes) != chs.data.bytes)
                        {
                            perror("write wrong");
                            close(fd);
                            break;
                        }
                        rcvsize += chs.data.bytes;
                        printf("已下载%d个字节\n", rcvsize);
                        if (chs.data.isOK == 1)
                        {
                            printf("下载完毕\n");
                            close(fd);
                            break;
                        }
                        recvfrom(waddr.fd, &pack, sizeof(pack), 0, NULL, NULL);
                        chs = *(chuanShou *)pack.msgBlock;
                    }
                    sem_post(&sem1);
                }
            }
        }
```

服务器端代码如下:

```
 else if (pack.msgType == CSFILE)
        {
            chuanShou chs = *(chuanShou *)pack.msgBlock;
            if (chs.type == CHUAN)
            {
                printf("\e[34m%s给%s发送了一个文件%s\e[0m\n", chs.srcname, chs.desname, chs.filename);
                // 不检测是不是好友了,没有做离线文件传输了
                char sysfile[128];
                sprintf(sysfile, "./server_file/%s/%s_%s", chs.srcname, chs.desname, chs.filename); // 保存到服务器指定文件夹
                // 给文件添加前缀,区分谁发的,发给谁,什么文件
                int fd = open(sysfile, O_CREAT | O_WRONLY | O_TRUNC, 0666); // 一定要加上0666
                // 如果客户端发送的文件名包含路径信息,服务器可能会尝试在错误的目录下创建文件。
                if (fd < 0)
                {
                    perror("open fail");
                    continue;
                }
                int rcvsize = 0;
                while (1)
                {
                    if (write(fd, chs.data.text, chs.data.bytes) != chs.data.bytes)
                    {
                        perror("write wrong");
                        close(fd);
                        break;
                    }
                    rcvsize += chs.data.bytes;
                    printf("已下载%d个字节\n", rcvsize);
                    if (chs.data.isOK == 1)
                    {
                        close(fd);
                        break;
                    }
                    recvfrom(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, &length);
                    chs = *(chuanShou *)pack.msgBlock;
                }
                printf("下载完毕\n");
                // 告诉客户有人给你发了文件
                for (int i = 0; i < users; i++)
                {
                    if (!strcmp(loginUser[i].name, chs.desname))
                    {
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&loginUser[i].addr, sizeof(struct sockaddr_in));
                    }
                }
            }
            else if (chs.type == SHOU)
            {
                printf("\e[34m%s开始接收%s的文件:%s\n\e[0m", chs.desname, chs.srcname, chs.filename);
                char sysfile[256];
                sprintf(sysfile, "./server_file/%s/%s_%s", chs.srcname, chs.desname, chs.filename); // 服务器读取之前保存的文件
                // puts(sysfile);
                int fd = open(sysfile, O_RDONLY);
                if (fd < 0)
                {
                    // 可以发送给用户,报错
                    perror("open fail");
                    close(fd);
                    continue;
                }
                else
                {
                    struct stat info;
                    stat(sysfile, &info);
                    int leftSize = info.st_size; // 取出传输文件大小文件
                    printf("%d\n", leftSize);
                    int sendSize = 0;
                    while (leftSize)
                    {
                        if (leftSize > 800)
                        {
                            chs.data.isOK = 0;
                            chs.data.bytes = 800;
                        }
                        else
                        {
                            chs.data.isOK = 1; // 告诉客户这是最后一个文件块
                            chs.data.bytes = leftSize;
                        }
                        read(fd, chs.data.text, chs.data.bytes);
                        leftSize -= chs.data.bytes;
                        sendSize += chs.data.bytes;
                        memcpy(pack.msgBlock, &chs, sizeof(chs));
                        sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&clientAddr, sizeof(struct sockaddr_in));
                        printf("已发送%d字节\n", sendSize);
                        usleep(6);
                    }
                    close(fd);
                    printf("\e[32m文件传输完成\e[0m\n");
                    // 在服务器上面删除掉这个文件
                    remove(sysfile); // 用户下载了就删除它
                    for (int i = 0; i < users; i++)
                    {
                        if (!strcmp(loginUser[i].name, chs.srcname))
                        {
                            // 改标志位,区分接收方还是发送方
                            chs.data.isOK = 2; // 发送给发送方,提示你的好友已经接收了文件
                            memcpy(pack.msgBlock, &chs, sizeof(chs));
                            sendto(sd, &pack, sizeof(pack), 0, (struct sockaddr *)&loginUser[i].addr, sizeof(struct sockaddr_in));
                        }
                    }
                }
            }
        }
```

不足与改进

 不足

不足如下几点:

1.没有实现离线好友申请,离线用户接收不到好友申请

2.没有注册掉”ctrl+c“,用户强制退出时,他的登录信息仍然保存在服务器,服务器默认客户还在继续登录

3.没有实现同时处理多条好友申请,只能一条一条处理好友申请

4.整体结构有待提高,由于前面构思不够完善,考虑的不周到,随着项目的推进,构思逐渐完善,但前面写的已经占了大部分,不好修改。

改进

针对以上几点不足,提出改进想法:

1.发送好友申请的同时,服务器检测对方是否在线,如果不在线,服务器创建一个子线程,专门用来监测对方用户,一旦用户上限,服务器子线程就将好友申请提示信息发送给对方并结束子线程。

2.用signal函数注册掉ctrl+c信号,为其指定相应的处理退出函数,在登录用户数组删除掉它,并结束线程,防止强制退出的用户依然霸占着这个账号

3.将用户的好友申请消息保存在服务器中,一条一条处理好友申请消息

4.在写项目的前期一定要构思好整体框架,不要”顾头不顾腚“

备注

      小白第一次把自己写的项目上传,希望大家多多指教,完整源代码我放在了gitee仓库,链接如下:   https://gitee.com/wxx2024/program 

​                                                                                                                            2024年6月4日

​                                                     

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值