即时通讯系统————基于TCP协议的C/S架构(Server)

本文详细介绍了如何构建一个基于TCP协议的客户端/服务器(C/S)架构即时通讯系统,重点聚焦在服务器端的实现。内容涵盖服务器的设计、连接管理、消息收发以及可能遇到的网络通信问题和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务器端

#ifndef SERVER_H
#define SERVER_H

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <pthread.h>
#include <signal.h>

#define SIZ   20
#define SIZE  10
#define SIZE1  20 //线程数
#define FAILURE  10000
#define SUCCESS  10001
#define FALSE    10002
#define TRUE     10003

struct sockaddr_in server_addr;//保存服务器信息

enum Enum
{
    REG,
    LOGIN,
    PRIVATE,
    GROUP
};
typedef enum Enum ENUM;
/* 定义一个结构体,含有用户的所有信息类型*/
struct user            
{
    char petname[32];    //昵称
    char age[32];
    char sex[32];
    char key[32];
    int cmd;            //用户指令
    int member;         //会员状态
    char buf[32];        //文件内容
};
typedef struct user User;

struct judge
{
    int cmd;            //用户命令
    char buf[32];        //文件名
    char petname[32];    //昵称
    char t2[128];        //发送消息的时间
};
typedef struct judge Judge;
struct info        
{
    int ToFd;            //要发送消息的套接字
    char petname[32];    //用户昵称
    char ptr[64];        //聊天内容
};
typedef struct info InFo;


int sockfdInit(int sockfd);
int show2(void *para, int columnCount, char **columnValue, char **columnName);
void send0(int fd);
void send1(int fd);
void sendlogin(int fd);
void sendLogin(int fd);
void sendlogin1(int fd);
int count(sqlite3 *pdb);

#endif
/**************************************************************
  > File Name: Server.c
  > Author: yangxuan
  > Mail: 2503327051@qq.com 
  > Created Time: 2018年08月23日 星期四 23时23分18秒
 **************************************************************/

#include <stdio.h>
#include "Server.h"
#include <time.h>
#include <unistd.h>

//全局变量
char sql[128] = {0};  //存储数据库要用的命令字符串
sqlite3 *pdb;   //定义一个sqlite3指针, 指向数据库
int ret;              //接受函数调用后的返回值
int sockfd;           //定义一个文件描述符,基于ipv4,采用TCP协议的套接字
//char *data = NULL;
//int k = 0;
pthread_mutex_t mutex; //定义pthread_mutex_t 类型的锁,实现线程同步
pthread_cond_t con;    //当某些线程在工作时, 有必要让一些线程等待
char buf[32] = {0};    //定义一个buf数组,即时方便的向客户端传递信息
User RecvInfo;        //定义一个结构体变量
User T;
int Tofd;             //记下被邀请私聊好友的文件描述符
int fdon;             //记下私聊发起人的fd    
fd_set ReadFd, tmpfd;   //定义一个可读集合,中间变量集合
char ptr[32] = {0};   //记下私聊发起人的昵称
int j;                //用来计数连了多少客户端 j + 1
int fd[SIZE] = {0};
char reg[32] = {0};     //记下注册好的用户名
int grpfd = 0;          //记群聊发起人的fd
int S = 0;              //记群聊邀请了几个好友
int groupfd[SIZ];          //群聊最大人数
int receivefd;
int fdcancel;
int fdcheck;        //记下私聊记录查询用户的fd
int ID;         //用来记录聊天记录中的数据条数
int group;
int a[2];
int I;
char namegroup[32];
int FD;

int count1(void *para, int columnCount, char **columnValue, char **columnName)
{
    ID = atoi(*columnValue);
    printf("IDID %d\n", ID);
    return 0;
}

/*
	作者:杨宣
	函数原型: 回调函数:int show(void *para, int columnCount, char **columnValue, char **columnName)
	函数说明:参1:为调用函数传过来的参数, 参2: 为查询到的信息有几条  参3:查询到的结果  参4:查询结构的名称即类型
    函数功能:登录时查询该用户是否存在,不存在返回0, 存在返回-1
*/
int select1(void *para, int columnCount, char **columnValue, char **columnName)
{
    memset(buf, 0, sizeof(buf));
    char buf[32] = {0};
    strcpy(buf, columnValue[0]);

    if(strcmp(buf, RecvInfo.petname) == 0)  //将读取的用户名与数据库查询到的结果比较,若存在返回-1
    {
        return -1;
    }

    return 0;
}


/*
	作者:杨宣
	函数原型:回调函数:int show(void *para, int columnCount, char **columnValue, char **columnName)
	函数说明:参1:为调用函数传过来的参数, 参2: 为查询到的信息有几条  参3:查询到的结果  参4:查询结构的名称即类型
	函数功能:登录时查询该用户密码,正确返回-1, 不正确返回0
*/
int select2(void *para, int columnCount, char **columnValue, char **columnName)
{
    char buf[32] = {0};

    strcpy(buf, columnValue[0]);
    if(strcmp(buf, RecvInfo.key) == 0) //与数据库中查到的密码比较,相同则说明密码正确,返回-1
    {
        return -1;
    }

    return 0;
}

/*
	作者:杨宣
	函数原型: 回调函数:int show(void *para, int columnCount, char **columnValue, char **columnName)
	函数说明: 参1:为调用函数传过来的参数, 参2: 为查询到的信息有几条  参3:查询到的结果  参4:查询结构的名称即类型
    函数功能:登录时查询该用户是否在线,不在线返回-1, 在线返回0,
*/
int loginfd(void *para, int columnCount, char **columnValue, char **columnName)
{
    int a = *columnValue[0] - '0';       //char型数据转换成int型    取值减去0的ASCII码值

    if(a == 1)                      	//标志位为 1说明不在线,返回-1
    {
        return -1;
    }

    return 0;
}

int filecmd(void *para, int columnCount, char **columnValue, char **columnName)
{
    Judge J;
    J.cmd = 18;
    FILE *fp;
    int fd = *(int *)para;
    if(columnCount >= 0)
    {
        strcpy(J.petname, columnValue[0]);
        strcpy(J.buf, columnValue[2]);

        ret = send(fd, &J, sizeof(J), 0);
        if(ret == -1)
        {
            perror("send");
        }

        fp = fopen(J.buf, "r");
        if(fp == NULL)
        {
            printf("failure");
        }
        while(1)
        {
            memset(J.buf, 0, sizeof(J.buf));
            ret = fread(J.buf, 1, sizeof(J.buf) - 1, fp);
            if(ret == -1)
            {
                perror("fread failure");
            }
            if(ret == 0)
            {
                strcpy(J.buf, "SUCCESS");
                ret = send(fd, &J, sizeof(J), 0);
                if(ret == -1)
                {
                    perror("send");
                }
                break;
            }
            else
            {
                J.cmd = ret;
                ret = send(fd, &J, sizeof(J), 0);
                if(ret == -1)
                {
                    perror("send");
                }
            }
        }
        fclose(fp);
    }

    sprintf(sql, "update file set cmd = 2 where namerecv = '%s';", RecvInfo.petname);
    ret = sqlite3_exec(pdb, sql, NULL, NULL, NULL);
    if(ret != SQLITE_OK)
    {
        perror("update cmdfile");
    }
    return 0;
}

/*  
	作者:杨宣
	函数原型:void *pthread(void *arg)
	函数说明:带有一个void *型的参数,返回值为void *
	函数功能:处理用户登录请求  
*/
void *pthreadlogin(void *arg)
{
    pthread_detach(pthread_self()); //线程分离,线程运行完自动释放线程

    int fd = *(int *)arg;      		//取出文件描述符
    int ret;
    User U;
    U = RecvInfo;

    while(1)
    {
       memset(sql, 0, sizeof(sql)); 
        sprintf(sql, "select petname  from User where petname = '%s';", U.petname);    //查找数据库中是否存在该用户
        ret = sqlite3_exec(pdb, sql, select1, NULL, NULL);
        if(ret == SQLITE_OK)														//若不存在发送FALSE 给客户端
        {
            Judge J;
            strcpy(J.petname, U.petname);
            printf("12%s\n", U.petname);
            J.cmd = 2;
            strcpy(J.buf, "FALSE");
            ret = send(fd, &J, sizeof(J), 0);
            if(ret == -1)
            {
                perror("sendlogin1");
            }
            break;
        }
	
        else														
        {
            sprintf(sql, "select key from User where petname = '%s';", U.petname);  //若存在,再查找密码是否正确
        
        ret = sqlite3_exec(pdb, sql, select2, NULL, NULL);                       //若不正确,则发送FAILURE 给客户端
        if(ret == SQLITE_OK)
        {
            Judge J;
            strcpy(J.petname, U.petname);
            J.cmd = 2;
            strcpy(J.buf, "FAILURE");
            ret = send(fd, &J, sizeof(J), 0);
            if(ret == -1)
            {
                perror("sendlogin1");
            }
            break;
        }
	
        else
        {
            sprintf(sql, "select cmd from User where petname = '%s';", U.petname);  //若正确, 再判断该用户是否已在线
            ret = sqlite3_exec(pdb, sql, loginfd, NULL, NULL);
            if(ret == SQLITE_OK)									//若在线,发送TRUE给客户端
            {
                Judge J;
                strcpy(J.petname, U.petname);
                J.cmd = 2;
                strcpy(J.buf, "TRUE");
            
			    ret = send(fd, &J, sizeof(J), 0);
                if(ret == -1)
                {
                    perror("send");
                }
            }
    
            else
            {
            Judge J;
            strcpy(J.petname, U.petname);
            J.cmd = 2;
            strcpy(J.buf, "SUCCESS");						//各种情况排除完毕, 发送SUCCESS给客户端
        
		    ret = send(fd, &J, sizeof(J), 0);
            if(ret == -1)
            {
                perror("sendlogin1");
            }
            T = U;

            sprintf(sql, "update User set cmd = '2' where petname = '%s';", U.petname);   //更新数据库用户在线状态
            ret = sqlite3_exec(pdb, sql, NULL, NULL, NULL);
            if(ret != SQLITE_OK)
            {
                perror("update");
            }
		
            sprintf(sql, "update User set fd = '%d' where petname = '%s';", fd,  U.petname);   //更新用户登录的客户端fd
            ret = sqlite3_exec(pdb, sql, NULL, NULL, NULL);
            if(ret != SQLITE_OK)
            {
                perror("update");
            }

            sprintf(sql, "select * from file where cmd = 1 and  namerecv = '%s';", U.petname);
            ret = sqlite3_exec(pdb, sql, filecmd, &fd, NULL);
            if(ret != SQLITE_OK)
            {
                perror("filecmd");
            }

            }
		
            break;
        }
     }
    }
	
    return NULL;
}


/*
	作者:杨宣
	函数原型: 回调函数:int show(void *para, int columnCount, char **columnValue, char **columnName)
	函数说明:参1:为调用函数传过来的参数, 参2: 为查询到的信息有几条  参3:查询到的结果  参4:查询结构的名称即类型
	函数功能:用户注册时查找数据库中是否有同名用户,存在返回-1, 不存在返回0
*/
int show(void *para, int columnCount, char **columnValue, char **columnName)
{
    Judge J;
    J.cmd = 1;
    int fd = *(int *)para;
	
    if(columnCount >= 1)
    {
        strcpy(J.buf, "FAILURE");        //若用户名已存在, 发送FAILURE给客户端
        send(fd, &J, sizeof(J), 0);
        if(ret == -1)
        {
            perror("用户名已存在!\n");
        }
        return -1;
    }
    return 0;
}


/* 
	作者:杨宣
	指针函数, 函数名pthreadHandle, 含有一个void *的参数, 返回指为void * 
	函数说明:循环中不断接受文件描述符的信息, 并将其存到结构体中,接收一个将其写入数据库的表中
	函数功能:处理用户注册请求
 */
void *pthreadHandle(void *arg)
{
    int fd = *(int *)arg;                     //将主函数的传过来的文件描述符赋给变量

    pthread_detach(pthread_self());           //线程分离
	
    sprintf(sql, "select petname  from User where petname = '%s';", RecvInfo.petname);   //查询数据库中是否存在该用户 
    ret = sqlite3_exec(pdb, sql, show, &fd, NULL);
    if(ret != SQLITE_OK)
    {
         pthread_mutex_unlock(&mutex);
        //printf("select %s\n", sqlite3_errmsg(pdb));
    }

   else			//若不存在,将用户信息插入数据库
   {
       strcpy(reg, RecvInfo.petname);
	   sprintf(sql, "insert into User (petname, age, sex, key, cmd, fd, member) values ('%s', '%s', '%s', '%s', '%d', '%d', '%d');", RecvInfo.petname, RecvInfo.age, RecvInfo.sex, RecvInfo.key,RecvInfo.cmd, fd, 0);

        ret = sqlite3_exec(pdb, sql, NULL, NULL, NULL);
        if(ret != SQLITE_OK)
        {
            perror("sqlite_exec");
        }
      
        send0(fd);   //并调用send0()函数发送 SUCCESS给客户端
        memset(&RecvInfo, 0, sizeof(RecvInfo));  //读完后需对结构体进行清空,以便下次再读 
   }
          
    return NULL;    
}


/*
	作者:杨宣
	函数原型: 回调函数:int show(void *para, int columnCount, char **columnValue, char **columnName)
	函数说明:参1:为调用函数传过来的参数, 参2: 为查询到的信息有几条  参3:查询到的结果  参4:查询结构的名称即类型  
	函数功能:登录时显示在线好友,无好友在线时返回0, 有一个或多个好友在线时返回好友名
  
*/
int exhibit(void *para, int columnCount, char **columnValue, char **columnName)
{
    int fd = *(int *)para;
    int i;
    Judge J;
  
    for(i = 0; i < columnCount; i++)  //将查到的用户名发送给客户端
    {
        memset(&J, 0, sizeof(J));
        J.cmd = 3;
        strcpy(J.buf, columnValue[i]);
        if(strcmp(J.buf, RecvInfo.petname) != 0)
        {
        printf("buf %s\n", J.buf);
        printf("Recv %s\n", RecvInfo.petname);
        ret = send(fd, &J, sizeof(J), 0);
        /*if(ret == -1)    //else与最近的未配对的if相匹配,得注掉
        {
            perror("sendp");
        }*/
        }
        else
        {
            continue;
        }
    }

    return 0;                         //回调函数return 0不可以少
}


/*
	作
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值