websocket+c语言(聊天室那些事)

1.前言

近期,大三期末结束学校组织去培训,时间虽然就15天,但是本来合计着就特么放假了,结果来个培训。而且培训内容更操蛋,一天linux,一天C语言,一天进程,一天线程,一天数据库,一天网络编程,一天黄粱一梦,就盼着自己睡醒的那天,其实说不上郁闷,反而觉得挺好的。在大环境中氛围好更容易进入学习状态,虽然黄粱一梦,可黄粱万一存在呢,对吧。想必我算是睡一半饿醒的那个吧,经过10天努力,不负众望,还是勉强写出个差不多的聊天室,学习够用了;

 2.代码简介

前言说到,只是个差不多的聊天室(由于本人之前c技术也算是学校基础课程学完那种,一般用php,所以写起来许多写法等等得从从头开始学,就比如基础的字符串拼接,内存分配等等),加上websocket之前也只是了解过一点,所以功能本就不多的聊天室用了10天时间

废话说完了下面开始介绍

websocket就不说了,想必不了解也不会看到这篇文章(对了,本代码并不涉及线程与进程,只是用了select函数)

先看看具体模型:

其他绑定端口函数啥的就不讲解了,咱们细细说说socket中的select

函数原型如下:

int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

返回值:int型

参数1: maxfdp 这个值怎么说呢,他其实代表的是一个范围,当一个新连接进来后,accept函数接收到连接后会返回一个int类型的fd(fd只是个名字而已)值,由于每个fd值是自增的,所以这个第一个参数就是还在连接中最大的这个fd值加上1就等于maxfdp(官方解释自行百度)

参数2: fd_set *readset 从类型上看,故名思意,是个fd_set 的结构体,这个结构体中应当包含所有的在线的可读的fd值(就是前面说那个),结构体具体啥样自行百度

参数3: fd_set *writeset 与参数3类似,不过参数2是读,参数3是写,故名思意,就是select会阻塞到有这两个结构体中有可读可写时然后进行消息处理。

参数4:同23不多介绍

参数5:也是一个结构体,超时时间,当阻塞时间超过这个时间就会提示错误(可以直接写0)

说完了select 就来看看具体代码吧:
服务端代码分为几个文件夹:

sever.c  服务器相关代码

function.c 握手编码发送消息等相关代码

user_operate.c 用户操作类

json_op.c 生成json消息相关代码

说到websocket,其实他和socket的区别就是多了一条在服务器上握手与消息解码

主要是从http1.1升级到ws的过程

其中设计到sha_1编码和base64编码

服务器开启,浏览器使用websocket连接时,发起的请求中会有一个

Sec-WebSocket-Key:xxxxxxxxxxx 的值段

所谓的sha_1和base64就是对获取到的key进行编码

过程 :获取key->sha_1->base64

 

//sha_1和base64解码函数 我这直接合成一起了,sha_1直接用的库,代码后半段是base64解码
char * get_comm_key(char *key){
    char *mofa="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    char *temp_data=(char *)malloc(strlen(key)+strlen(mofa)+1);
    sprintf(temp_data,"%s%s",key,mofa);
    char *base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    size_t len = strlen(temp_data);
    unsigned char sha1[SHA_DIGEST_LENGTH];
    SHA1(temp_data,len,sha1);
    int sha1_len=strlen(sha1);
    //定义base64编码后的长度
    int de_len=0;
    if(sha1_len%3==0){
        de_len=(sha1_len/3)*4;
    }else{
        de_len=(sha1_len/3+1)*4;
    }
    // 定义字符数组长度
    char *code_value=(char *)malloc(de_len+1);
    code_value[de_len]='\0';
    //三个一组依次写入
    for(int i=0,j=0;i<sha1_len;i+=3,j+=4){
        //这里是3个齐全的
        if(i+3<sha1_len){
            code_value[j]=base64_table[sha1[i]>>2];
            code_value[j+1]=base64_table[(sha1[i] & 0x3)<<4 | sha1[i+1]>>4];
            code_value[j+2]=base64_table[(sha1[i+1] & 0xf)<<2 | sha1[i+2]>>6];
            code_value[j+3]=base64_table[sha1[i+2] & 0x3f];
        }else{
            //当没有3个时
            int c=sha1_len-i;
            if(c==1){
                code_value[j]=base64_table[sha1[i]>>2];
                code_value[j+1]=base64_table[(sha1[i] & 0x3)<<4];
            }else if(c==2){
                code_value[j]=base64_table[sha1[i]>>2];
                code_value[j+1]=base64_table[(sha1[i] & 0x3)<<4 | sha1[i+1]>>4];
                code_value[j+2]=base64_table[(sha1[i+1] & 0xf)<<2];
            }
        }
    }
    //末尾填写=
    switch(sha1_len % 3)  {  
        case 1:  
            code_value[de_len-2] = '=';  
            code_value[de_len-1] = '=';  
            break;  
        case 2:  
            code_value[de_len-1] = '=';  
            break;  
    } 
    free(temp_data);
    return code_value;
}

3.具体实现

额感觉也没啥好说的直接上代码

4.代码

//sever.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <stdbool.h>
// #include "function.c"
#include "user_operate.c"
#define MAX_CONN 100 //定义最大连接数
#define MAX_BUFF_SIZE 1024 //定义最大输入输出字节数
#define IP "0.0.0.0" //ip地址
#define PORT 6001 //端口
#define MAX_LISTEN 100 //最大监听

//初始化一个socket链接
int creat_socket(const char *ip,int port,int listen_num){
    int sever_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sever_sock==-1){
        printf("socket创建失败,请重启机器\n");
        exit(0);
        return -1;
    }
    struct sockaddr_in sever_config;
    sever_config.sin_family = AF_INET;  //使用IPv4地址
    sever_config.sin_addr.s_addr = inet_addr(ip);  //具体的IP地址
    sever_config.sin_port = htons(port);  //端口
    if(bind(sever_sock, (struct sockaddr*)&sever_config, sizeof(sever_config))==-1){
        printf("socket初始化信息失败,请重启机器\n");
        exit(0);
        return -1;
    }
    //进入监听状态,等待用户发起请求
    if(listen(sever_sock, listen_num)==-1){
        printf("监听数设置失败,请重启机器\n");
        exit(0);
        return -1;
    }
    printf("服务器开始监听\n");
    return sever_sock;
}

//发送函数
bool socket_send(int sock,const char *msg){
    if(strlen(msg)>MAX_BUFF_SIZE){
        printf("发送失败,超过最大发送字节数\n");
        return false;
    }
    send(sock,msg,strlen(msg),0);
    return true;
}

//群发函数
bool socket_sends(int sock[],int sock_num,const char *msg){
    if(strlen(msg)>MAX_BUFF_SIZE){
        printf("发送失败,超过最大发送字节数\n");
        return false;
    }
    for(int i=0;i<sock_num;i++){
        send(sock[i],msg,strlen(msg),0);
    }
    return true;
}
//初始化链接池
//开启服务
void sever_run(int sever_socket){
    //握手池
    int wait_pool[MAX_LISTEN]={0};
    wait_pool[0]=sever_socket;
    //当前等待握手数
    int wait_num=1;
    //等待登录池
    int dl_pool[MAX_LISTEN]={0};
    int dl_num=0;
    online_users *conn_pool=init_users();
    // user_data *sever_user=creat_user(0,sever_socket,"sever");
    //定义群组
    groups *group_pool=init_groups();
    // 服务端上线
    // add_users(conn_pool,sever_user);
    //定义maxfdp
    int maxfdp=0;
    //初始化缓冲区
    fd_set read_fd;
    //定义接收数组与发送数组
    char read_msg[MAX_BUFF_SIZE];
    int uid_key=1;
    int gid_key=1;
    while(1){
        //清空读缓冲区 在对缓冲区进行重置
        FD_ZERO(&read_fd);
        maxfdp=set_fds_arr(&read_fd,wait_pool,wait_num,0);
        maxfdp=set_fds_arr(&read_fd,dl_pool,dl_num,maxfdp);
        maxfdp=set_fds(conn_pool,&read_fd,maxfdp);
        int ret=select(maxfdp+1,&read_fd,NULL,NULL,NULL);
        if(ret>0){
            if(FD_ISSET(sever_socket,&read_fd)){
                 struct sockaddr_in new_client;
                socklen_t new_client_len;
                int new_client_conn = accept(sever_socket,(struct sockaddr *)&new_client, &new_client_len);
                if(new_client_conn>0){
                    wait_pool[wait_num]=new_client_conn;
                    wait_num+=1;
                    printf("新链接进来了\n");
                }else{
                    printf("新链接链接失败\n");
                }
            }
            //读取握手消息
            for(int i=0;i<wait_num;i++){
                memset(read_msg, 0,sizeof(read_msg));
                int byte_num=recv(wait_pool[i],read_msg,MAX_BUFF_SIZE,MSG_DONTWAIT);
                if(byte_num>0){
                    // 握手
                    char *temp_data=computeAcceptKey(read_msg);
                    shakeHand(wait_pool[i],temp_data);
                    free(temp_data);
                    dl_pool[dl_num]=wait_pool[i];
                    dl_num+=1;
                    array_splice(wait_pool,wait_num,wait_pool[i]);
                    wait_num-=1;
                    printf("握手成功\n");
                }else if(byte_num==0){
                    //链接断开
                    array_splice(wait_pool,wait_num,wait_pool[i]);
                    printf("链接断开\n");
                    wait_num-=1;
                }
            }
            for(int i=0;i<dl_num;i++){
                int len;
                int ret=0;
                memset(read_msg, 0,sizeof(read_msg));  
                while((len = recv(dl_pool[i],read_msg,sizeof(read_msg),MSG_DONTWAIT))>0){
                    ret=on_ws_recv_data((unsigned char*)read_msg,len,(char*)&read_msg);
                    if(ret){
                        cJSON *msg_json=cJSON_Parse(read_msg);
                        if(msg_json!=NULL){
                            int type=-1;
                            if(cJSON_GetObjectItem(msg_json,"type")==NULL){
                                char *temp_msg=J_error("请输入消息类型");
                                ws_send_data(dl_pool[i],temp_msg,strlen(temp_msg));
                                free(temp_msg);
                                printf("登录信息错误:未输入消息类型\n");
                                // continue;
                            }else{
                                type=cJSON_GetObjectItem(msg_json,"type")->valueint;
                                if(type==1){
                                    if(cJSON_GetObjectItem(msg_json,"name")==NULL){
                                        char *temp_msg=J_error("请输入名字");
                                        ws_send_data(dl_pool[i],temp_msg,strlen(temp_msg));
                                        free(temp_msg);
                                        printf("登录信息错误:未输入名字\n");
                                    }else{
                                        //获取名字
                                        char *name=cJSON_GetObjectItem(msg_json,"name")->valuestring;
                                        //生生新信息并加入表
                                        user_data *new_user=creat_user(uid_key,dl_pool[i],name);
                                        add_users(conn_pool,new_user);
                                        //将cid发送到客户端
                                        char *temp_msg=j_u_init(uid_key);
                                        ws_send_data(dl_pool[i],temp_msg,strlen(temp_msg));
                                        uid_key+=1;
                                        //移除本来在等待登录表中的id
                                        array_splice(dl_pool,dl_num,dl_pool[i]);
                                        dl_num-=1;
                                        printf("%s 登录成功\n",name);
                                    }
                                }
                            }
                            cJSON_Delete(msg_json);
                        }
                    }
                }
                if(len==0){
                    array_splice(dl_pool,dl_num,dl_pool[i]);
                    dl_num-=1;
                    printf("链接断开\n");
                }
            }
            user_data *temp_user_node=conn_pool->head;
            while(temp_user_node!=NULL){
               int len;
                int ret=0;
                memset(read_msg, 0,sizeof(read_msg));  
                while((len = recv(temp_user_node->fd_int,read_msg,sizeof(read_msg),MSG_DONTWAIT))>0){
                    ret=on_ws_recv_data((unsigned char*)read_msg,len,(char*)&read_msg);
                    if(ret){
                        cJSON *msg_json=cJSON_Parse(read_msg);
                        if(msg_json!=NULL){
                            if(cJSON_GetObjectItem(msg_json,"type")!=NULL){
                               int type=cJSON_GetObjectItem(msg_json,"type")->valueint;
                               if(type==2){
                                    //用户对用户的消息
                                    int rid=cJSON_GetObjectItem(msg_json,"r_uid")->valueint;
                                    int sid=cJSON_GetObjectItem(msg_json,"s_uid")->valueint;
                                    char *name=cJSON_GetObjectItem(msg_json,"name")->valuestring;
                                    char *s_msg=cJSON_GetObjectItem(msg_json,"msg")->valuestring;
                                    char *data=j_u_msg(sid,rid,name,s_msg);
                                    //寻址 
                                    user_data *target_user=get_user_local(conn_pool,rid);
                                    if(target_user!=NULL){
                                        ws_send_data(target_user->fd_int,data,strlen(data));
                                    }else{
                                        J_error("用户已下线");
                                    }
                               }else if(type==3){
                                    //创建群聊
                                    char *name=cJSON_GetObjectItem(msg_json,"name")->valuestring;
                                    group_data *new_group=creat_group(gid_key,name,temp_user_node->uid);
                                    gid_key+=1;
                                    add_groups(group_pool,new_group);
                                    // print_group(group_pool,gid_key-1);
                               }else if(type==4){
                                    //加入群聊
                                    // user_join_group(groups *ghead,online_users  *uhead,int uid,int gid)
                                    int gid=cJSON_GetObjectItem(msg_json,"gid")->valueint;
                                    user_join_group(group_pool,conn_pool,temp_user_node->uid,gid);
                                    print_group(group_pool,gid);
                               }else if(type==5){
                                    //退出群聊
                               }else if(type==6){
                                    //群发消息
                                    int gid=cJSON_GetObjectItem(msg_json,"gid")->valueint;
                                    char *msg=cJSON_GetObjectItem(msg_json,"msg")->valuestring;
                                    char *group_msg_j=j_g_msg(temp_user_node->uid,temp_user_node->uname,gid,msg);
                                    int group_msg_len=strlen(group_msg_j);

                                    //消息转发
                                    //群组寻址
                                    group_data *temp_group=get_group_local(group_pool,gid);
                                    if(temp_group!=NULL){
                                        for(int i=0;i<temp_group->join_num;i++){
                                            int uid=temp_group->join_uid[i];
                                            // 用户寻址
                                            user_data *send_temp_user=get_user_local(conn_pool,uid);
                                            if(send_temp_user!=NULL){
                                                ws_send_data(send_temp_user->fd_int,group_msg_j,group_msg_len);
                                            }
                                        }
                                    }
                               }else if(type==8){
                                    char *online_user=j_users(conn_pool);
                                    ws_send_data(temp_user_node->fd_int,online_user,strlen(online_user));
                               }else if(type==9){
                                    char *online_group=j_groups(group_pool);
                                    ws_send_data(temp_user_node->fd_int,online_group,strlen(online_group));
                               }
                            }
                        }
                        
                    }
                }
                if(len==0){
                    printf("登录用户下线了\n");
                    users_del(conn_pool,group_pool,temp_user_node->uid);
                }
                temp_user_node=temp_user_node->next_user;
            }
        }
    }
}
int main(){
    int sever_sock=creat_socket(IP,PORT,MAX_LISTEN);
    // SetNonBlock(sever_sock);
    sever_run(sever_sock);
    close(sever_sock);
    return 0;
}
//function.c
#include <stdbool.h>
#include <stdlib.h>
#include <openssl/sha.h>
#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#define RESPONSE_HEADER_LEN_MAX 1024
//u char 转  char
void uctoc(char* str, unsigned char* UnChar, int ucLen){  
    int i = 0;  
    for(i = 0; i < ucLen; i++)  {  
        //格式化输str,每unsigned char 转换字符占两位置%x写输%X写输  
        sprintf(str + i * 2, "%02x", UnChar[i]);  
    }  
}  
//sha_1 返回sha1后的码
char *sha_1(char *str,int len){
    //实例化一个sha
    unsigned char *sSHA=(unsigned char *)malloc(20);
    char *data=(char *)malloc(40);;
    SHA1((const unsigned char*)str, len, sSHA);
    uctoc(data,sSHA,20);
    return data;
}
//删除数组某数
bool array_splice(int arr[],int arr_len,int data){
    int flag=0;
    for(int i=0;i<arr_len;i++){
        if(arr[i]==data){
            flag=1;
        }
        if(flag==1){
            if(i==arr_len-1){
                arr[i]=0;
                return true;
            }else{
                arr[i]=arr[i+1];
            }
        }
    }
    return false;
}
//检查数组中某数据是否存在
bool array_exists(int arr[],int arr_len,int data){
    for(int i=0;i<arr_len;i++){
        if(arr[i]==data){
            return true;
        }
    }
    return false;
}
//获取Sec-WebSocket-Key的值
char * fetchSecKey(const char * buf){
    char *key;
    char *keyBegin;
    char *flag="Sec-WebSocket-Key: ";
    int i=0, bufLen=0;
    key=(char *)malloc(256);
    memset(key,0, 256);
    if(!buf){
      return NULL;
    }
    keyBegin=strstr(buf,flag);
    if(!keyBegin){
      return NULL;
    }
    //读取Sec-WebSocket-Key后面的
    keyBegin+=strlen(flag);
    bufLen=strlen(buf);
    for(i=0;i<bufLen;i++){
        if(keyBegin[i]==0x0A||keyBegin[i]==0x0D){
            break;
        }
        key[i]=keyBegin[i];
    }
  return key;
}
char * get_comm_key(char *key){
    char *mofa="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    char *temp_data=(char *)malloc(strlen(key)+strlen(mofa)+1);
    sprintf(temp_data,"%s%s",key,mofa);
    char *base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    size_t len = strlen(temp_data);
    unsigned char sha1[SHA_DIGEST_LENGTH];
    SHA1(temp_data,len,sha1);
    int sha1_len=strlen(sha1);
    //定义base64编码后的长度
    int de_len=0;
    if(sha1_len%3==0){
        de_len=(sha1_len/3)*4;
    }else{
        de_len=(sha1_len/3+1)*4;
    }
    // 定义字符数组长度
    char *code_value=(char *)malloc(de_len+1);
    code_value[de_len]='\0';
    //三个一组依次写入
    for(int i=0,j=0;i<sha1_len;i+=3,j+=4){
        //这里是3个齐全的
        if(i+3<sha1_len){
            code_value[j]=base64_table[sha1[i]>>2];
            code_value[j+1]=base64_table[(sha1[i] & 0x3)<<4 | sha1[i+1]>>4];
            code_value[j+2]=base64_table[(sha1[i+1] & 0xf)<<2 | sha1[i+2]>>6];
            code_value[j+3]=base64_table[sha1[i+2] & 0x3f];
        }else{
            //当没有3个时
            int c=sha1_len-i;
            if(c==1){
                code_value[j]=base64_table[sha1[i]>>2];
                code_value[j+1]=base64_table[(sha1[i] & 0x3)<<4];
            }else if(c==2){
                code_value[j]=base64_table[sha1[i]>>2];
                code_value[j+1]=base64_table[(sha1[i] & 0x3)<<4 | sha1[i+1]>>4];
                code_value[j+2]=base64_table[(sha1[i+1] & 0xf)<<2];
            }
        }
    }
    //末尾填写=
    switch(sha1_len % 3)  {  
        case 1:  
            code_value[de_len-2] = '=';  
            code_value[de_len-1] = '=';  
            break;  
        case 2:  
            code_value[de_len-1] = '=';  
            break;  
    } 
    free(temp_data);
    return code_value;
}
//加密获得电脑段key
char * computeAcceptKey(const char * buf){
    char * clientKey=fetchSecKey(buf);
    // char * serverKey=(char *)malloc(100); 
    char * serverKey=get_comm_key(clientKey);
    free(clientKey);
    printf("sever_key=%s\n",serverKey);
    return serverKey;
}
void shakeHand(int connfd,const char *serverKey){
    char *responseHeader=(char *)malloc(sizeof("a")*200);
    if(!connfd){
        return;
    }
    if(!serverKey){
        return;
    }
    memset(responseHeader,'\0',200);
    sprintf(responseHeader, "HTTP/1.1 101 Switching Protocols\r\n");
    sprintf(responseHeader, "%sUpgrade: websocket\r\n", responseHeader);
    sprintf(responseHeader, "%sConnection: Upgrade\r\n", responseHeader);
    sprintf(responseHeader, "%sSec-WebSocket-Accept: %s\r\n\r\n", responseHeader, serverKey);
    write(connfd,responseHeader,strlen(responseHeader));
    free(responseHeader);
}


void ws_send_data(int fid, unsigned char* pkg_data, unsigned int pkg_len) {
    static unsigned char send_buffer[8196];
    unsigned int send_len;
    // 固定的头
    send_buffer[0] = 0x81;
    if (pkg_len <= 125) {
        send_buffer[1] = pkg_len; // 最高bit为0,
        send_len = 2;
    }
    else if (pkg_len <= 0xffff) {                                                               
        send_buffer[1] = 126;
        send_buffer[2] = (pkg_len & 0x000000ff);
        send_buffer[3] = ((pkg_len & 0x0000ff00) >> 8);
        send_len = 4;
    }
    else { 
        send_buffer[1] = 127;
        send_buffer[2] = (pkg_len & 0x000000ff);
        send_buffer[3] = ((pkg_len & 0x0000ff00) >> 8);
        send_buffer[4] = ((pkg_len & 0x00ff0000) >> 16);
        send_buffer[5] = ((pkg_len & 0xff000000) >> 24);
 
        send_buffer[6] = 0;
        send_buffer[7] = 0;
        send_buffer[8] = 0;
        send_buffer[9] = 0;
        send_len = 10;
    }
    memcpy(send_buffer + send_len, pkg_data, pkg_len);
    send_len += pkg_len;
    send(fid, send_buffer, send_len, 0);
}

//数据解码
int on_ws_recv_data(unsigned char*pkg_data, int pkg_len, char* msg) {
    int ret =0;
    // 第一个字节是头,已经判断,跳过;
    unsigned char* mask = NULL;
    unsigned char* raw_data = NULL;
    unsigned int len = pkg_data[1];
    // 最高的一个bit始终为1,我们要把最高的这个bit,变为0;
    len = (len & 0x0000007f);
    if (len <= 125) {
        mask = pkg_data + 2; // 头字节,长度字节
    }
    else if (len == 126) { // 后面两个字节表示长度;
        len = ((pkg_data[2]) | (pkg_data[3] << 8));
        mask = pkg_data + 2 + 2;
    }
    else if (len == 127){ // 这种情况不用考虑,考虑前4个字节的大小,后面不管;
        unsigned int low = ((pkg_data[2]) | (pkg_data[3] << 8) | (pkg_data[4] << 16) | (pkg_data[5] << 24));
        unsigned int hight = ((pkg_data[6]) | (pkg_data[7] << 8) | (pkg_data[8] << 16) | (pkg_data[9] << 24));
        if (hight != 0) { // 表示后四个字节有数据int存放不了,太大了,我们不要了。
            return ret;
        }
        len = low;
        mask = pkg_data + 2 + 8;
    }
    // mask 固定4个字节,所以后面的数据部分
    raw_data = mask + 4;
    static unsigned char recv_buf[8096];
    unsigned int i;
    for (i = 0; i < len; i++) {
        recv_buf[i] = raw_data[i] ^ mask[i % 4]; // mask只有4个字节的长度,所以,要循环使用,如果超出,取余就可以了。
    }
    recv_buf[len] = 0;
    int head = pkg_data[0];
    if(head==129 || head==130){
        memcpy(msg, recv_buf, len);
        ret = 1;
    }else{
        return -1;
    }
    return ret;
}
//json_op.c
#include "./JSON/cJSON.c"
#include <stdio.h>
//错误消息
char *J_error(const char *msg){
    cJSON *json_msg=cJSON_CreateObject();    
    cJSON_AddStringToObject(json_msg,"data",msg);        //添加字符串 
    cJSON_AddNumberToObject(json_msg,"type",-1);
    char *json_data = cJSON_Print(json_msg);
    cJSON_Delete(json_msg);
    return json_data;
}
//用户信息
char *j_u_msg(int s_uid,int r_uid,char *name,char *msg){
    cJSON *json_msg=cJSON_CreateObject();    
    cJSON_AddNumberToObject(json_msg,"s_uid",s_uid);
    cJSON_AddNumberToObject(json_msg,"r_uid",r_uid);
    cJSON_AddStringToObject(json_msg,"name",name);
    cJSON_AddStringToObject(json_msg,"msg",msg);
    cJSON_AddNumberToObject(json_msg,"type",2);
    char *json_data = cJSON_PrintUnformatted(json_msg);
    cJSON_Delete(json_msg);
    return json_data;
}
//群消息
char *j_g_msg(int s_uid,char *s_name,int r_gid,char *msg){
    cJSON *json_msg=cJSON_CreateObject();    
    cJSON_AddNumberToObject(json_msg,"s_uid",s_uid);
    cJSON_AddStringToObject(json_msg,"name",s_name);
    cJSON_AddNumberToObject(json_msg,"gid",r_gid);
    cJSON_AddStringToObject(json_msg,"msg",msg);
    cJSON_AddNumberToObject(json_msg,"type",6);
    char *json_data = cJSON_Print(json_msg);
    cJSON_Delete(json_msg);
    return json_data;
}

//生成上线信息
char *j_u_init(int cid){
    cJSON *json_msg=cJSON_CreateObject();    
    cJSON_AddNumberToObject(json_msg,"cid",cid);
    cJSON_AddNumberToObject(json_msg,"type",1);
    char *json_data = cJSON_Print(json_msg);
    cJSON_Delete(json_msg);
    return json_data;
}




/*
    type协议
    -1 error 提示错误消息
    1 user_conn 用户链接
    2 用户对用户发消息 必须字段 s_uid r_uid name msg type
    3 创建群聊         必须字段 s_uid g_name type
    4 退出群聊         必须字段 s_uid gid    type
    5 加入群聊         必须字段 s_uid gid    type
    6 群发消息         必须字段 s_uid gid    type msg
*/
//user_operate.c
#include <stdio.h>
#include <stdbool.h>
#include <sys/select.h>
#include <stdlib.h>
#include <pthread.h>
#include "function.c"#include "./json_op.c"
#define JOIN_MAX 100
#define GROUP_MAX 100
//用户信息
typedef struct USER_DATA{
    int uid;
    char *uname;
    int fd_int;
    int add_group_id[JOIN_MAX];
    int join_num;
    struct USER_DATA *next_user;
} user_data;

//在线用户表
typedef struct ONLINE_USERS{
    user_data *head; 
    int online_num;
} online_users;

//群组信息
typedef struct GROUP_DATA{
    int gid;
    char *gname;
    int creat_uid;
    int join_uid[JOIN_MAX];
    int join_num;
    struct GROUP_DATA *next;
} group_data;

//所有群组
typedef struct GROUPS{
    group_data *head;
    int creat_group_mun;
} groups;

//创建一个初始化用户
user_data *creat_user(int uid,int fd_int,char *uname){
    char *name=(char *)malloc(strlen(name)+1);
    strcpy(name,uname);
    user_data *user=(user_data *)malloc(sizeof(user_data));
    user->uid=uid;
    user->fd_int=fd_int;
    user->uname=name;
    user->next_user=NULL;
    user->join_num=0;
    return user;
}

//初始化一个用户组
online_users *init_users(){
    online_users *data=(online_users *)malloc(sizeof(online_users));
    data->head=NULL;
    data->online_num=0;
    return data;
}

//初始化一个群组
group_data *creat_group(int gid,char *gname,int uid){
    group_data *data=(group_data *)malloc(sizeof(group_data));
    char *name=(char *)malloc(strlen(gname)+1);
    strcpy(name,gname);
    data->gid=gid;
    data->creat_uid=uid;
    data->gname=gname;
    data->join_num=0;
    data->next=NULL;
    return data;
}

//初始化群组组
groups *init_groups(){
    groups *data=(groups *)malloc(sizeof(groups));
    data->head=NULL;
    data->creat_group_mun=0;
}
//群组寻址
group_data *get_group_local(groups *headNode,int gid){
    group_data *temp=headNode->head;
    if(headNode->head==NULL){
        return NULL;
    }
    for(int i=0;i<headNode->creat_group_mun;i++){
        if(temp->gid==gid){
            return temp;
        }else{
            temp=temp->next;
        }
    }
    return NULL;
}

//用户寻址
user_data *get_user_local(online_users *headNode,int uid){
    user_data *temp=headNode->head;
    if(headNode->head==NULL){
        return NULL;
    }
    for(int i=0;i<headNode->online_num;i++){
        if(temp->uid==uid){
            return temp;
        }else{
            temp=temp->next_user;
        }
    }
    return NULL;
}

//用户加入在线组
bool add_users(online_users *headNode,user_data *new_node){
    user_data *temp=get_user_local(headNode,new_node->uid);
    if(temp==NULL){
        new_node->next_user=headNode->head;
        headNode->head=new_node;
        headNode->online_num+=1;
        return true;
    }else{
        return false;
    }
    return true;
}
// 将用户信息加入在线表
bool users_add(online_users *users,user_data *user){
    user_data *temp=get_user_local(users,user->uid);
    if(temp==NULL){
        user->next_user=users->head;
        users->head=user->next_user;
        users->online_num+=1;
        return true;
    }else{
        return false;
    }
}

//将群组加入群组组
bool add_groups(groups *headNode,group_data *new_group){
    group_data *temp=get_group_local(headNode,new_group->gid);
    if(temp==NULL){
        new_group->next=headNode->head;
        headNode->head=new_group;
        headNode->creat_group_mun+=1;
        return true;
    }else{
        return false;
    }

}

//加入群组
bool user_join_group(groups *ghead,online_users  *uhead,int uid,int gid){
    //找到两个地址
    group_data *gtemp=get_group_local(ghead,gid);
    user_data *utemp=get_user_local(uhead,uid);
    if(gtemp!=NULL && utemp!=NULL){
        //查看用户加入群组中是否有该群组
        //查看群组用户群组中是否有该用户
        bool exit_g=array_exists(gtemp->join_uid,gtemp->join_num,uid);
        bool exit_u=array_exists(utemp->add_group_id,utemp->join_num,gid);
        if(!exit_g && !exit_u){
            gtemp->join_uid[gtemp->join_num]=uid;
            utemp->add_group_id[utemp->join_num]=gid;
            gtemp->join_num+=1;
            utemp->join_num+=1;
            return true;
        }
    }
    return false;
}

//退出群组
bool user_rm_group(groups *ghead,online_users *uhead,int uid,int gid){
    //找到两个地址
    group_data *gtemp=get_group_local(ghead,gid);
    user_data *utemp=get_user_local(uhead,uid);
    if(gtemp!=NULL && utemp!=NULL){
        //查看用户加入群组中是否有该群组
        //查看群组用户群组中是否有该用户
        bool exit_g=array_exists(gtemp->join_uid,gtemp->join_num,uid);
        bool exit_u=array_exists(utemp->add_group_id,utemp->join_num,gid);
        if(exit_g && exit_u){
            array_splice(gtemp->join_uid,gtemp->join_num,uid);
            array_splice(utemp->add_group_id,utemp->join_num,gid);
            gtemp->join_num-=1;
            utemp->join_num-=1;
            return true;
        }
    }
    return false;
}

//用户下线 将用户移出在线表
bool users_del(online_users *uhead,groups *ghead,int uid){
    if(uhead->head->uid==uid){
        if(uhead->head->join_num!=0){
            while (uhead->head->join_num!=0){
                user_rm_group(ghead,uhead,uid,uhead->head->add_group_id[0]);
            }
        }
        uhead->head=uhead->head->next_user;
        uhead->online_num-=1;
        return true;
    }else{
        user_data *temp=uhead->head;
        while(temp->next_user!=NULL){
            if(temp->next_user->uid==uid){
                while(temp->next_user->join_num!=0){
                    user_rm_group(ghead,uhead,uid,temp->next_user->add_group_id[0]);
                }
                temp->next_user=temp->next_user->next_user;
                uhead->online_num-=1;
                return true;
            }
            temp=temp->next_user;
        }
        return false;
    }
    return false;
}
//循环在线表
void printall(online_users *head){
    user_data *temp=head->head;
    while (temp!=NULL){
        printf("用户name为:%s\n",temp->uname);
        printf("用户uid为:%d\n",temp->uid);
        printf("用户加入群聊数为:%d\n",temp->join_num);
        for(int i=0;i<temp->join_num;i++){
            printf("%d\t",temp->add_group_id[i]);
        }
        printf("\n");
        temp=temp->next_user;
    }
}

//打印群组信息
void print_group(groups *headNode,int gid){
    group_data *temp=get_group_local(headNode,gid);
    if(temp!=NULL){
        printf("群名:%s\n",temp->gname);
        printf("群组成员数为:%d \n",temp->join_num);
        printf("群组成员为\n");
        for(int i=0;i<temp->join_num;i++){
            printf("%d\t",temp->join_uid[i]);
        }
        printf("\n");
    }else{
        printf("gid不存在\n");
    }
}

//打印用户信息
void print_user(online_users *headNode,int uid){
    user_data *temp=get_user_local(headNode,uid);
    if(temp!=NULL){
        printf("用户name为:%s\n",temp->uname);
        printf("用户uid为:%d\n",temp->uid);
        printf("用户加入群聊数为:%d\n",temp->join_num);
        for(int i=0;i<temp->join_num;i++){
            printf("%d\t",temp->add_group_id[i]);
        }
        printf("\n");
    }else{
        printf("uid不存在\n");
    }
}

//将所有链接上的fd放进一个fd中 并设置最大maxfd
int set_fds(online_users *head,fd_set *fds,int maxfdp){
    int temp_int=maxfdp;
    user_data *temp=head->head;
    while(temp!=NULL){
        FD_SET(temp->fd_int,fds);
        if(temp->fd_int>temp_int){
            temp_int=temp->fd_int;
        }
        temp=temp->next_user;
    }
    return temp_int;
}
//将数组中的值加入fds中
int set_fds_arr(fd_set *fds,int *arr,int arr_len,int maxfdp){
    int temp_int=maxfdp;
    for(int i=0;i<arr_len;i++){
        FD_SET(arr[i],fds);
        if(arr[i]>temp_int){
            temp_int=arr[i];
        }
    }
    return temp_int;
}
//生成群聊信息
char *j_groups(groups *headNode){
    group_data *temp=headNode->head;
    cJSON *json_msg=cJSON_CreateArray();
    cJSON *json_msg1=cJSON_CreateObject();
    while(temp!=NULL){
        cJSON *temp_j  = cJSON_CreateObject();
        cJSON_AddStringToObject(temp_j,"name",temp->gname);
        cJSON_AddNumberToObject(temp_j,"gid",temp->gid);
        cJSON_AddItemToArray(json_msg,temp_j);
        temp=temp->next;
    }
    cJSON_AddItemToObject(json_msg1,"group",json_msg);
    cJSON_AddNumberToObject(json_msg1,"type",9);
    char *data2=cJSON_PrintUnformatted(json_msg1);
    cJSON_free(json_msg);
    cJSON_free(json_msg1);
    return data2;
}
//生成用户信息
char *j_users(online_users *headNode){
    user_data *temp=headNode->head;
    cJSON *json_msg=cJSON_CreateArray();
    cJSON *json_msg1=cJSON_CreateObject();
    // cJSON *json_num=cJSON_CreateObject();
    while(temp!=NULL){
        cJSON *temp_j  = cJSON_CreateObject();
        cJSON_AddStringToObject(temp_j,"name",temp->uname);
        cJSON_AddNumberToObject(temp_j,"uid",temp->uid);
        cJSON_AddItemToArray(json_msg,temp_j);
        // cJSON_free(temp_j);
        temp=temp->next_user;
    }
    cJSON_AddItemToObject(json_msg1,"users",json_msg);
    cJSON_AddNumberToObject(json_msg1,"type",8);
    char *data2=cJSON_PrintUnformatted(json_msg1);
    cJSON_free(json_msg);
    cJSON_free(json_msg1);
    return data2;
}
5.前端测试代码
//html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>聊天测试</title>
    <link rel="stylesheet" href="./css/main.css">
</head>
<body>
    <header class="header">
        <!-- 最顶部标题部分 -->
        <div class="menu">
            <button id="btn3">刷新列表</button>
            <button id="btn1">在线人员</button>
            <button id="btn2">在线群聊</button>
            <button id="btn4">创建群聊</button>
        </div>
    </header>
    <div class="content_body">
        <!-- 内容显示区域 -->
        <!-- 左边菜单 -->
        <div class="left_menu">
            <ul class="left_menu_ul">
                <!-- <li s_uid="2" msg_type="1" id="msg_card_1_1"><img src="./img/纸飞机.png" alt=""><span class="li_type">私聊:xxxx</span><span class="msg_flag"></span></li> -->
            </ul>
        </div>
        <!-- 右边内容区 -->
        <div class="content_left">
            <!-- 消息区 -->
            <div class="msg_region"></div>
            <div class="online_user"></div>
            <div class="groups"></div>
            <!-- 消息输入区 -->
            <div class="input_text">
                <textarea id="msg_input"></textarea>
                <button id="send_msg">发送</button>
            </div>
        </div>
    </div>
    <script src="./js/main.js"></script>
</body>

</html>

  

// css/main.css
*{
    padding: 0px;
    margin: 0px;
}
html{
    width: 100vw;
    height: 100vh;
}
body{
    width: 100%;
    height: 100%;
}
.header{
    width: 100%;
    height: 50px;
    background-color: rgb(230, 230, 230);
}
.content_body{
    width: 100%;
    height: calc(100% - 50px);
    background-color: rgb(179, 180, 181);
    display: flex;
}
.input_area{
    width: 100%;
    height: 20%;
    background-color: rgb(69, 84, 97);
}
.menu{
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    /* text-align: center; */
    /* inline-size: 50px; */
}
.menu button{
    width: 100px;
    height: 40px;
    border: 0px;
    font-size: 16px;
    background-color: rgb(193, 193, 193);
    border-radius: 5px;
    /* margin-left: 20px; */
}
#btn1{
    margin-left: 10px;
}
#btn2{
    margin-left: 10px;
}

#btn4{
    margin-left: 10px;
}
#btn3{
    margin-left: 20px;
}
.left_menu{
    width: 15%;
    height: 100%;
    background-color: antiquewhite;
}
.left_menu_ul{
    width: 100%;
    height: 100%;
    background-color: rgb(195, 195, 195);
    overflow: scroll;
}
.left_menu_ul::-webkit-scrollbar {
    display: none;
}
.left_menu_ul li{
    width: 100%;
    height: 50px;
    border-bottom:1px black solid ;
    display: flex;
    align-items: center;
    position: relative;
    user-select: none;
    /* color: aliceblue; */
}
.left_menu_ul li img{
    width: 50px;
    height: 50px;
}
.msg_flag{
    position: absolute;
    right: 10px;
    display: block;
    width: 20px;
    height: 20px;
    /* background-color: rgb(250, 67, 67); */
    border-radius: 50%;
    display: none;
}
.content_left{
    width: 85%;
    height: 100%;
}
.msg_region{
    width: 100%;
    height: 90%;
    background-color: rgb(204, 204, 204);
    overflow: scroll;
    display: none;
}
.msg_region::-webkit-scrollbar {
    display: none;
}
.msg_card{
    width: 80%;
    /* height: 70px; */
    background-color: rgb(235, 236, 236);
    padding: 10px 5px;
    border-radius: 5px;
    margin-top: 5px;
    
}
.msg_card .msg_card_name{
    height: 30px;
    width: 50px;
    line-height: 30px;
    text-align: center;
    background-color: rgb(198, 202, 202);
    margin-top: 5px;
}
.msg_card_content{
    margin-top: 10px;
}
.card_right{
    margin-left: 20%;
}
.input_text{
    width: 100%;
    height: 9%;
    display: flex;
}
#msg_input{
    display: inline-block;
    width: 92%;
    height: 100%;
    outline: none;
    font-size: 20px;
    /* line-height: 100%; */
    resize:none;
}
#send_msg{
    border: 0px;
    display: inline-block;
    width: 8%;
    height: 100%;
    font-size: 30px;
}
.online_user{
    width: 100%;
    height: 90%;
    background-color: rgb(204, 204, 204);
    overflow: scroll;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-content: flex-start;
    /* display: none; */
}
.online_user::-webkit-scrollbar {
    display: none;
}
.online_user_card{
    width: 80%;
    height: 50px;
    background-color: rgb(196, 196, 196);
    margin-top: 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-radius: 10px;
}
.online_name{
    margin-left: 10px;
}
.online_user_card button{
    margin-right: 10px;
    width: 80px;
    height: 35px;
    border: 0px;
    border-radius: 7px;
    /* background-color: rgb(116, 212, 248); */
}
.online_user_card:nth-child(1){
    margin-top: 20px;
}
.groups{
    width: 100%;
    height: 90%;
    background-color: rgb(204, 204, 204);
    overflow: scroll;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-content: flex-start;
    display: none;
}
.groups::-webkit-scrollbar {
    display: none;
}
.group_card{
    width: 80%;
    height: 50px;
    background-color: rgb(196, 196, 196);
    margin-top: 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-radius: 10px;
}
.online_name{
    margin-left: 10px;
}
.group_card button{
    margin-right: 10px;
    width: 80px;
    height: 35px;
    border: 0px;
    border-radius: 7px;
    /* background-color: rgb(116, 212, 248); */
}
.group_card:nth-child(1){
    margin-top: 20px;
}
// js/main.js
//定义左边菜单
// var left_menu=Array
//2是群聊 1是私聊

var user_groups=new ALLUSERMSGS();
var myid=1;
var my_name=null;
//定义在线人数
var online_user=new Array();
//定义群聊
var online_groups=new Array();

var win_statu={
    type:0,
    uid:0,
}

function MENU_CARD(type,name,id){
    this.type=type;
    this.name=name;
    this.id=id;
}
//私聊信息
function Private(s_uid,name,msg){
    this.s_uid=s_uid
    this.msg=msg;
    this.name=name;
}
//私聊结构体
function Private_Array(uid_gentle,msg_type,s_name){
    this.uid_gentle=uid_gentle;
    this.msgs=new Array();
    this.msgs_num=0;
    this.msg_type=msg_type;
    this.s_name=s_name;
}
//所有私聊合集
function ALLUSERMSGS(){
    this.user_num=0;
    this.user_array=new Array();
}
function user_msg_add(user_groups,s_uid,uid_gentle,s_name,msg,msg_type){
    for(var i=0;i<user_groups.user_num;i++){
        if(user_groups.user_array[i].uid_gentle==uid_gentle && user_groups.user_array[i].msg_type==msg_type){
            let data=new Private(s_uid,s_name,msg);
            user_groups.user_array[i].msgs.push(data)
            user_groups.user_array[i].msgs_num+=1;
            return true;
        }
    }
    return false;
}

function user_add(user_groups,uid_gentle,msg_type,t_name){
    for(var i=0;i<user_groups.user_num;i++){
        if(user_groups.user_array[i].uid_gentle==uid_gentle && user_groups.user_array[i].msg_type==msg_type){
            return false;
        }
    }
    var new_groups=new Private_Array(uid_gentle,msg_type,t_name);
    user_groups.user_array.push(new_groups);
    user_groups.user_num+=1;
    return true;
}
//查询消息
function require_msg(user_groups,uid_gentle,msg_type){
    for(var i=0;i<user_groups.user_num;i++){
        if(user_groups.user_array[i].uid_gentle==uid_gentle&&user_groups.user_array[i].msg_type==msg_type){
            return user_groups.user_array[i];
        }
    }
    return null;
}
//删除与某用户的消息组
function del_user_msg(user_groups,uid_gentle,msg_type){
    for(var i=0;i<user_groups.user_num;i++){
        if(user_groups.user_array[i].uid_gentle==uid_gentle&&user_groups.user_array[i].msg_type==msg_type){
            user_groups.user_array.splice(i, 1);
            user_groups.user_num-=1;
            return true;
        }
    }
    return false;
}

function print_menu(user_groups){
    var left_menu_ul=document.getElementsByClassName("left_menu_ul")[0];
    left_menu_ul.innerHTML="";
    for(var i=0;i<user_groups.user_num;i++){
        let s_uid=user_groups.user_array[i].uid_gentle;
        let msg_type=user_groups.user_array[i].msg_type;
        let id="msg_card_"+s_uid+"_"+msg_type;
        let name=user_groups.user_array[i].s_name;
        left_menu_ul.innerHTML+="<li data-s_uid=\""+s_uid+"\" data-msg_type=\""+msg_type+"\" id=\""+id+"\">"+
                                "<img src=\"./img/纸飞机.png\">"+
                                "<span class=\"li_type\">"+name+"</span>"+
                                "<span class=\"msg_flag\"></span>"+
                                "</li>";
    }
}

function print_online_user(data){
    let online_user=document.getElementsByClassName("online_user")[0];
    online_user.innerHTML="";
    for(var i=0;i<data.length;i++){
        let uid=data[i].uid;
        let name=data[i].name;
        online_user.innerHTML+="<div class=\"online_user_card\" data-uid=\""+uid+"\" data-name=\""+name+"\">"+
                            "<div class=\"online_name\">"+name+"</div>"+
                            "<button id=\"build_conn\">和他聊天</button>"+
                            "</div>";
    }
}
function print_groups(data){
    let groups=document.getElementsByClassName("groups")[0];
    groups.innerHTML="";
    for(var i=0;i<data.length;i++){
        let gid=data[i].gid;
        let name=data[i].name;
        groups.innerHTML+="<div class=\"group_card\" data-uid=\""+gid+"\" data-name=\""+name+"\">"+
                            "<div class=\"online_name\">"+name+"</div>"+
                            "<button id=\"build_conn\">加入群聊</button>"+
                            "</div>";
    }
}
function print_content_msg(user_groups,uid_gentle,msg_type,myid){
    let msg_region=document.getElementsByClassName("msg_region")[0];
    msg_region.innerHTML="";
    var temp_msgs=null;
    for(var i=0;i<user_groups.user_num;i++){
        if(user_groups.user_array[i].uid_gentle==uid_gentle&&user_groups.user_array[i].msg_type==msg_type){
            temp_msgs=user_groups.user_array[i];
            for(var j=0;j<temp_msgs.msgs_num;j++){
                if(temp_msgs.msgs[j].s_uid==myid){
                    let name=temp_msgs.msgs[j].name;
                    let msg=temp_msgs.msgs[j].msg;
                    msg_region.innerHTML+="<div class=\"msg_card card_right\">"+
                                            "<div class=\"msg_card_name\">"+name+"</div>"+
                                            "<div class=\"msg_card_content\">"+msg+"</div>"+
                                            "</div>";
                }else{
                    let name=temp_msgs.msgs[j].name;
                    let msg=temp_msgs.msgs[j].msg;
                    msg_region.innerHTML+="<div class=\"msg_card\">"+
                                            "<div class=\"msg_card_name\">"+name+"</div>"+
                                            "<div class=\"msg_card_content\">"+msg+"</div>"+
                                            "</div>";
                }
            }
        }
    }
}
//左边菜单
var left_menu_ul=document.getElementsByClassName("left_menu_ul")[0];
//打印左侧菜单栏
print_menu(user_groups);
//绑定左侧菜单对应事件
left_menu_ul.addEventListener("click",function(e){
    var target=e.target;
    if(target.tagName=="LI"){
        document.getElementsByClassName("online_user")[0].style.display="none";
        document.getElementsByClassName("groups")[0].style.display="none";
        document.getElementsByClassName("msg_region")[0].style.display="block";
        // left_menu_ul.style.display="block"
        let s_uid=target.dataset["s_uid"];
        let msg_type=target.dataset["msg_type"];
        if(myid==-1){
            alert("请刷新页面");
        }else{
            if(msg_type==2){
                win_statu.type=2;
                win_statu.uid=s_uid;
                print_content_msg(user_groups,s_uid,msg_type,myid);
            }else{
                win_statu.type=1;
                win_statu.uid=Number(s_uid)-myid;
                print_content_msg(user_groups,s_uid,msg_type,myid);
            }
        }
    }
})
/*
    type协议
    -1 error 提示错误消息
    1 user_conn 用户链接
    2 用户对用户发消息 必须字段 s_uid r_uid name msg type
    3 创建群聊         必须字段 s_uid g_name type
    4 退出群聊         必须字段 s_uid gid    type
    5 加入群聊         必须字段 s_uid gid    type
    6 群发消息         必须字段 s_uid gid    type msg
*/
// type 2
function user_tu_user(s_uid,r_uid,name,msg){
    var data={
        "s_uid":Number(s_uid),
        "r_uid":Number(r_uid),
        "name":name,
        "msg":msg,
        type:2
    }
    return JSON.stringify(data);
}
// type 3
function u_c_group(s_uid,g_name,type){
    var data={
        "s_uid":Number(s_uid),
        "g_name":g_name,
        "type":3
    }
    return JSON.stringify(data);
}
// type 4
function u_j_group(s_uid,gid){
    var data={
        "s_uid":Number(s_uid),
        "gid":Number(gid),
        "type":4
    }
    return JSON.stringify(data);
}

// type 5
function u_r_group(s_uid,gid){
    var data={
        "s_uid":Number(s_uid),
        "gid":Number(gid),
        "type":5
    }
    return JSON.stringify(data);
}

function user_to_group(s_uid,gid,msg){
    var data={
        "s_uid":Number(s_uid),
        "gid":Number(gid),
        "msg":msg,
        "type":6,
    }
    return JSON.stringify(data);
}
function user_dl(name){
    var data={
        "name":name,
        "type":1
    }
    return JSON.stringify(data);
}

var ws = new WebSocket("ws://192.168.43.64:6001")
ws.onopen = function(event){
    my_name=prompt("请输入你的名字");
    while(my_name==undefined){
        my_name=prompt("请输入你的名字");
    }
    this.send(user_dl(my_name));
}

ws.onmessage = function(event){
    var data=JSON.parse(event.data);
    let type=data.type;
    console.log(data);
    // console.log(data);
    if(data.type==-1){
        alert(data.msg);
    }else if(type==1){
        //用户登陆
        myid=Number(data.cid);
        alert("登陆成功");
    }else if(type==2){
        //用户对用户
        let s_uid=Number(data.s_uid);
        let r_uid=Number(data.r_uid);
        let name=data.name;
        let msg=data.msg;
        let join_flag=false
        for(var i=0;i<user_groups.user_num;i++){
            if(user_groups.user_array[i].uid_gentle==(s_uid+r_uid)&&user_groups.user_array[i].msg_type==1){
                user_groups.user_array[i].msgs.push(new Private(s_uid,name,msg));
                user_groups.user_array[i].msgs_num+=1;
                join_flag==true;
                if(win_statu.uid==s_uid){
                    print_content_msg(user_groups,Number(s_uid)+myid,1,myid);
                }
                return 0;
            }
        }
        if(join_flag==false){
            user_add(user_groups,s_uid+r_uid,1,name);
            user_msg_add(user_groups,s_uid,s_uid+r_uid,name,msg,1);
        }
        print_menu(user_groups);
        print_content_msg(user_groups,s_uid+r_uid,type,myid);
    }else if(type==6){
        console.log("群聊消息为:",data);
        let s_uid=Number(data.s_uid);
        let gid=Number(data.gid);
        let name=data.name;
        let msg=data.msg;
        user_msg_add(user_groups,s_uid,gid,name,msg,2);
        print_content_msg(user_groups,gid,2,myid);
    }else if(type==7){
        //群聊消息
        online_groups=data.groups;
    }else if(type==8){
        //用户信息
        online_user=data.users;
        print_online_user(online_user);
    }else if(type==9){
        //群聊信息
        online_groups=data.group;
        console.log(data);
        print_groups(online_groups);
    }
}
ws.onclose = function(event){

}
//在线人员
document.getElementsByClassName("online_user")[0].addEventListener("click",function(e){
    var target=e.target;
    if(target.id=="build_conn"){
        let father_node=target.parentNode;
        let uid=father_node.dataset["uid"];
        let name=father_node.dataset["name"];
        let statu=user_add(user_groups,Number(uid)+myid,1,name);
        if(statu){
            alert("添加成功");
            print_menu(user_groups);
        }else{
            alert("添加失败");
        }
    }
});
//加入群聊
document.getElementsByClassName("groups")[0].addEventListener("click",function(e){
    var target=e.target;
    if(target.id=="build_conn"){
        let father_node=target.parentNode;
        let uid=father_node.dataset["uid"];
        let name=father_node.dataset["name"];
        let statu=user_add(user_groups,uid,2,name);
        if(statu){
            alert("添加成功");
            print_menu(user_groups);
            ws.send(u_j_group(myid,uid));
        }else{
            alert("添加失败");
        }
    }
});
//发送消息
document.getElementById("send_msg").onclick=function(){
    let msg_input=document.getElementById("msg_input");
    if(msg_input.value==undefined){

    }else{
        // console.log(msg_input.value);
        if(win_statu.type==1){
            //同理
            user_msg_add(user_groups,myid,Number(win_statu.uid)+myid,my_name,msg_input.value,1);
            print_content_msg(user_groups,win_statu.uid+myid,1,myid);
            ws.send(user_tu_user(myid,win_statu.uid,my_name,msg_input.value));
        }else if(win_statu.type==2){
            ws.send(user_to_group(myid,win_statu.uid,msg_input.value));
        }else{
            alert("发送失败,请选择发送对象");
        }
    }
}
document.getElementById("btn1").onclick=function(){
    document.getElementsByClassName("online_user")[0].style.display="flex";
    document.getElementsByClassName("msg_region")[0].style.display="none";
    document.getElementsByClassName("groups")[0].style.display="none";
    let a={"type":8}
    ws.send(JSON.stringify(a));
}

document.getElementById("btn2").onclick=function(){
    document.getElementsByClassName("online_user")[0].style.display="none";
    document.getElementsByClassName("msg_region")[0].style.display="none";
    document.getElementsByClassName("groups")[0].style.display="flex";
    let a={"type":9 }
    ws.send(JSON.stringify(a));
}
document.getElementById("btn3").onclick=function(){
    print_menu(user_groups);
}
document.getElementById("btn4").onclick=function(){
    my_name=prompt("请输入群聊的名字");
    if(my_name==undefined){
        alert("创建失败");
    }else{
        let a={"type":3,"name":my_name}
        ws.send(JSON.stringify(a));
        alert("成功");
    }
    // user_msg_add(user_groups,myid,Number(win_statu.uid)+myid,my_name,msg_input.value,2);
}

7.测试截图

先打开编译好的C语言程序,编译代码如下


运行程序

ps:如果出现socket初始化失败啥的请检查端口或者IP地址什么的(关闭后得等上2分钟左右才能继续开,除非换个端口)

 打开前端页面 输入名字

 后台提示登陆成功

按照步骤多来几个 

 然后创建群聊

 加入群聊

在线群聊->加入群聊

 

 

将三个用户都加入群聊

 后端显示

 

 点击群聊开始聊天

用户:小白

其他用户

 

 ps:到这就结束了,私聊同理(已经实现)有需要得自己下下来测试;还有就是,测试时请将前端连接ip换成自己的,毕竟每个人不一样,ip获得办法

连接上一个ipv4网

然后ip addr 

那个192.168.43.64一看就像是ipv4网填写进去就完事了

ps:虚拟机跑的话记得sever中的地址写0.0.0.0 非要问为什么的自行百度

到着就完结了,有什么需要问的还请 企鹅:2691467667

 再附加百度盘连接(前端+服务端)

 链接: https://pan.baidu.com/s/1DaVhZct2MhDDdG6SRtPCcA?pwd=pajx 提取码: pajx

文中没发出来的都是可以从网上下到的库,前端代码回头再发出来

posted @ 2023-07-02 20:19  鬼灰也  阅读(380)  评论(1)    收藏  举报