linux 下网络编程 聊天室项目

服务端使用框架:非阻塞轮询;

客户端使用框架:多线程;

使用数据库:mysql;

实现主要功能:

1.注册;

2.登录;

3.群聊;

4.私聊;

5.查看聊天记录;

6.查看在线人数;

7.修改个性签名;

8.修改密码;

9.发送文件;

10.退出登录;

11.注销账号;

进入时界面:左边服务器,右边客户端;

 登录成功时界面:

客户端:

client.h

#ifndef _select_h_
#define _select_h_
#include <stdio.h>
#include <time.h>
#define SIZE 1024
#define NOW_MAX 20 
typedef struct msg          //表示聊天时的信息
{
	char msg[SIZE];   		//消息内容
	char localname[20];		//消息目的名称
    char account[20];            //账户
	char fromname[20];      //消息来源名称
	char password[20];      //用户密码   
	char signname[40];      //个性签名   
	int cmd;                //工作模式
	int num;                //用于保存文件复制的字节数
}Msg;
	 	 //最大允许并行客户端数量
         void regisc(int client_socket);
         void main_menu(void);
         int Ask_server(int client_socket);
         int entry(int client_socket);
		 void user_menu(void);
		 void User_used(int client_socket);
		 void * readMsg (void *v);
		 void entry_out(int client_socket);
		 void chat_all(int client_socket);
		 void chat_one(int client_socket);
		 void client_logout(int client_socket);
		 void save(Msg*msg);
		 void look_chat();
		 void see_now_time(int client_socket);
		 void revise_sign(int client_socket);
		 void revise_password(int client_socket);
		 void client_guanli(int client_socket);
		 void transfer_file(int client_socket);//传输文件
			void transfer_file_y(int client_socket);//同意接受文件
			void transfer_file_n(int client_socket);//拒绝接受文件
			void start_transfer_file(int client_socket);//开始传输文件
			void save_transfer_file(Msg * buf);//接受文件

			
 #endif

client.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include "client.h"

#define SER_IP       "127.0.0.1"
#define SER_PORT     6789

#define LISTEN_MAX   100
char myname[20]={0};
char signname[100]={0};
char account_c[100]={0};
char mylocalname[20]; //用于保存传输文件来源的名字
char fsignname[40]; //用于保存文件传输名字
int root=0;
int flag = 1;
void *recvdata_do_handler(void *a);
/********************************************
 * TCP通信客户端流程
 * 1,创建套接字
 * 2,请求连接
 * 3,收发数据
 * 4,关闭套接字
 * *************************************************/

int main(int argc, char  *argv[])
{
    //1.创建套接字   IPV4  流式套接字  TCP协议
    int clifd = socket(AF_INET,SOCK_STREAM,0);
    if(clifd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("clifd = %d\n",clifd);


    struct sockaddr_in seraddr;
    memset(&seraddr,0,sizeof(struct sockaddr_in));
    seraddr.sin_family = AF_INET;//ipv4协议类型
    seraddr.sin_addr.s_addr = inet_addr(SER_IP);
    seraddr.sin_port = htons(SER_PORT);//端口    网络字节序
    //2.请求连接
    if(connect(clifd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr_in)) == -1)
    {
        perror("connect");
        return -1;
    }
   
    printf("连接成功\n");
   

    
    Ask_server(clifd);
    close(clifd);
    return 0;
}



void main_menu(void)
{
	system("clear");
	printf("\n\n\n\n\n\n\n\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
	printf("+++\t    欢迎使用101聊天室      +++\n");
	printf("+++\t\t1,注册                +++\n");
	printf("+++\t\t2,登录                +++\n");
	printf("+++\t\t3,退出                +++\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");

}

int Ask_server(int client_socket)
{
	char ch;
	int ret;
	while(1)
	{
		main_menu();//引用菜单栏
		printf("请输入您要做操作\n");
		scanf("%c",&ch);
		while(getchar() != '\n');
		switch(ch)
		{
			case '1':    //注册
				regisc(client_socket);
				sleep(1);
				break;
			case '2':    //登录
				ret = entry(client_socket);
				if (ret == 1)
				{
					User_used(client_socket);    //调用函数表示用户界面
				}
				sleep(1);
				break;
			case '3':    //退出
				exit(0);
				break;
		}
	}
}
//注册函数
void regisc(int client_socket)
{
    Msg msg;
	memset(&msg,0,sizeof(msg));
    msg.cmd=1;
    printf("请输入用户名\n");
    scanf("%s",msg.fromname);
    while(getchar() != '\n');
    printf("请输入账号\n");
    scanf("%s",msg.account);
    while(getchar() != '\n');
    printf("请输入密码\n");
    scanf("%s",msg.password);
	 while(getchar() != '\n');
    printf("请输入个性签名\n");
    scanf("%s",msg.signname);
	 while(getchar() != '\n');
    send(client_socket,&msg,sizeof(msg),0);
    recv(client_socket,&msg,sizeof(msg),0);
	printf("%d\n",msg.cmd);
   if(msg.cmd == 1001)
	{
		printf("注册成功\n");
	}
	else if (msg.cmd == -1)
	{
		printf("用户名以存在,注册失败\n");
	}
	else
	{
		printf("系统繁忙,注册失败\n");
	}
	
	sleep(2);


}


//登录账号
int entry(int client_socket)
{
	Msg msg;
	memset(&msg,0,sizeof(msg));
	msg.cmd = 2;
	
	printf("登录,请输入账号名: ");
	scanf("%s",msg.account);
	while(getchar() != '\n');
	
	printf("登录,请输入密码: ");

	

	scanf("%s",msg.password);
	while(getchar() != '\n');

	printf("\n");

	write(client_socket, &msg, sizeof(msg));
	memset(&msg,0,sizeof(msg));
	read(client_socket, &msg, sizeof(msg));
	printf("%d\n",msg.cmd);
	if(msg.cmd == -1)    //表示用户不存在
	{
		printf("登录失败,系统繁忙.\n");
		sleep(2);
		return -1;
	}
	if(msg.cmd == -2)    //表示用户不存在
	{
		printf("登录失败,用户不存在.\n");
		sleep(2);
		return -2;
	}
	if(msg.cmd == -3)
	{
		printf("登录失败,用户已登录.\n");
		sleep(2);
		return -3;
	}	
	if(msg.cmd == -4)
	{
		printf("登录失败,密码错误.\n");
		sleep(2);
		return -4;
	}	
	if(msg.cmd == 1002)
	{
		printf("登录成功,登录中...\n");
		printf("%s %s \n",msg.fromname,msg.signname);
		 strcpy(myname,msg.fromname);      //保存在线名字
		 strcpy(signname, msg.signname);   //保存个性签名
		 strcpy(account_c,msg.account);    //保存账号
		 root=msg.num; //获取权限


		sleep(2);
		return 1;
	}
}
 
void user_menu(void)
{
	system("clear");
	printf("%s: %s\n",myname,signname);
	//printf("\n\n\n\n\n\n\n");
	printf("++++++++++++++++++++++++++++++++++++++++\n");
	printf("+++                                  +++\n");
	printf("+++\t   欢迎使用101聊天室      +++\n");
	printf("+++                                  +++\n");
	printf("++++++++++++++++++++++++++++++++++++++++\n");
	printf("+++\t\t1,群聊               +++\n");
	printf("+++\t\t2,私聊               +++\n");
	printf("+++\t\t3,退出登录           +++\n");
	printf("+++\t\t4,查看聊天记录       +++\n");
	printf("+++\t\t5,显示当前在线人员   +++\n");
	printf("+++\t\t6,修改个性签名       +++\n");
	printf("+++\t\t7,修改密码           +++\n");
	printf("+++\t\t8,传输文件           +++\n");
	printf("+++\t\t9,注销账号           +++\n");
	printf("+++\t\tA,管理           +++\n");
	printf("++++++++++++++++++++++++++++++++++++++++\n");
}

//用户界面
void User_used(int client_socket)
{
	//要进行读写分离
	
	pthread_t read_id;
	pthread_create(&read_id, NULL, readMsg, (void *)&client_socket);
	pthread_detach(read_id);   //等待线程分离
	
	char ch;
	flag=1;
	while(flag)
	{
		user_menu();//调用第二个用户界面
		printf("请输入您要做操作\n");
		scanf("%c",&ch);
		while(getchar() != '\n');
		switch(ch)
		{
			case '1':        //群聊
				chat_all(client_socket);
				break;
			case '2':        //私聊
				chat_one(client_socket);
				break;
			case '3':        //退出登录	
				entry_out(client_socket);
				flag = 0;
				break;
			 case '4':        //查看聊天记录
			 	look_chat();  
			 	break;
			 case '5':        //显示当前在线人数 
			 	see_now_time(client_socket);
			 	break;
			 case '6':        //修改个性签名
			 	revise_sign(client_socket);
			 	break;
			 case '7':        //修改密码
			 	revise_password(client_socket);
				sleep(1);	
			 	break;
			 case '8':        //传输文件
			 	transfer_file(client_socket);
			 	break;
			 case 'y':        //表示愿意接受文件
			 	transfer_file_y(client_socket);
				printf("接收中\n");
				getchar();
			 	break;
			 case 'n':        //表示不愿意接受文件
			 	transfer_file_n(client_socket);sleep(1);
			 	break;		
			case '9':client_logout(client_socket);//表示注销账号
			flag=0;break;
			case 'a':if(root==1)//判断是不是管理员
			client_guanli(client_socket);
			else
			printf("管理失败没有权限\n");
			sleep(1);
			break;
		}
	}

	 //pthread_cancel(read_id);
}
 
//读写分离专门收消息
void * readMsg (void *v)
{
	int client_socket = *((int*)v);
	Msg buf;
	time_t t;
	struct tm *pt;
	int i = 0;
	while(1)
	{
		memset(&buf,0,sizeof(Msg));
		int ret = read(client_socket, &buf, sizeof(Msg));
		if(ret == -1)
		{
			perror("read");
			break;
		}
		switch(buf.cmd)
		{
			case 3:  
				printf(" %s\n",buf.msg);
				save(&buf);    //保存聊天记录
				break;
			 case 4:   //私聊
			
			 	printf(" %s\n",buf.msg);
			 	save(&buf);    //保存聊天记录
			 	break;
			 case -3:  //私聊失败
			 	printf("私聊失败,用户不存在或下线\n");
			 	break;
			 case 5 :  //退出登录
			 	printf("%s 退出登录\n",buf.fromname);
			 	sleep(1);
			 	pthread_exit(NULL);    //线程退出
			 	break;
			 case 6 :   //显示当前在线人数
			 	printf("当前在线人员:\n");
			 	printf("%s\n",buf.msg);
			 	break;
			 case 7 :  //修改个性签名成功
			 	strcpy(signname,buf.signname);
			 	printf("修改个性签名成功\n");
			 	break;
			 case -7 :  //修改个性签名失败
			 	printf("修改个性签名失败\n");
			 	break;
			 case 8 	:   //修改密码成功
			 	printf("修改密码成功\n");
				entry_out(client_socket);
				flag=0;
			 	break;
			 case -8 : 	//修改密码失败
			 	printf("修改密码失败\n");
			 	break;
			 case 9  :   //调用函数确认是否接受文件
			 	system("clear");
			 	printf("请问你是否接受来自 %s 的文件 %s(y/n)\n",buf.fromname,buf.signname);
			 	strcpy(mylocalname,buf.fromname);   //保存传输文件来源名字
		 		sleep(1);
			 	break;
			 case -9 :   //表示传输文件失败,没有找到该人
			 	printf("发送文件失败,好友不在线或不存在\n");sleep(1);
			 	break;
			case 10 :   //表示愿意接受文件,开始传输
				start_transfer_file(client_socket);
				sleep(1);
				break;
			case -10 :  //表示不愿意接受文件
				printf("发送文件失败,好友拒绝接受文件\n");sleep(1);
				break;
			case 11 :   //接受文件
				save_transfer_file(&buf);
				
				break;
				//printf("%d\n",i);
			case 22:printf("%s\n",buf.msg);

			break;
			case 87:printf("禁言成功\n");
			break;
			case 88:printf("踢人成功\n");
			break;
			case 99:printf("注销成功\n");
			sleep(1);
			 	pthread_exit(NULL);    //线程退出
			 	break;
			default:break;
		}
 
	}
}

void entry_out(int client_socket)//退出登录
{
	Msg msg;
	memset(&msg,0,sizeof(msg));
	msg.cmd=5;
	strcpy(msg.account,account_c);
	strcpy(msg.fromname,myname);
	send(client_socket,&msg,sizeof(msg),0);
	sleep(1);
	
}

void chat_all(int client_socket)//群聊
{
	system("clear");
	Msg msg;
	time_t t;
	char senr[150];
	memset(&msg,0,sizeof(msg));
	msg.cmd = 3;
	strcpy(msg.fromname,myname);
	strcpy(msg.localname,"All");
	printf("输入quit退出\n");
	printf("请输入你要群发送的信息\n");
	while(1)
	{
	
	memset(msg.msg,0,sizeof(msg.msg));
	memset(senr,0,sizeof(senr));
	// getchar();
	time(&t);
	struct tm *pt = localtime(&t);
	getchar();
	printf("%d年%d月%d日 %d:%d:%d\n",pt->tm_year+1900,pt->tm_mon+1,pt->tm_mday,
	pt->tm_hour,pt->tm_min,pt->tm_sec);

	sprintf(msg.msg,"\t\t\t%d年%d月%d日 %d:%d:%d\n\t\t\t",pt->tm_year+1900,pt->tm_mon+1,pt->tm_mday,
	pt->tm_hour,pt->tm_min,pt->tm_sec);
	 
	printf("%s:",msg.fromname);
	scanf("%s",senr);
	strcat(msg.msg,msg.fromname);
	strcat(msg.msg,":");
	strcat(msg.msg,senr);
	while(getchar() != '\n');
	if(strcmp("quit",senr)==0)
	break;
	write(client_socket, &msg, sizeof(Msg));
	}
	save(&msg);
 
	sleep (2);
}

void chat_one(int client_socket)//私聊
{
	system("clear");
	Msg msg;
	char senr[150];
	time_t t;
	memset(&msg,0,sizeof(msg));
	msg.cmd = 4;
	printf("请输入你要聊天的对象:\n");
	scanf ("%s",msg.localname);
	while(getchar() != '\n');
	strcpy (msg.fromname,myname);
	while(1)
	{
		getchar();
	time(&t);
	struct tm *pt = localtime(&t);
	printf("%d年%d月%d日 %d:%d:%d\n",pt->tm_year+1900,pt->tm_mon+1,pt->tm_mday,
	pt->tm_hour,pt->tm_min,pt->tm_sec);
	sprintf(msg.msg,"\t\t\t%d年%d月%d日 %d:%d:%d\n\t\t\t",pt->tm_year+1900,pt->tm_mon+1,pt->tm_mday,
	pt->tm_hour,pt->tm_min,pt->tm_sec);
	
	
	
	printf("%s:",msg.fromname);
	scanf("%s",senr);
	while(getchar() != '\n');
	if(strcmp("quit",senr)==0)
	{
		break;
	}

	strcat(msg.msg,msg.fromname);
	strcat(msg.msg,":");
	strcat(msg.msg,senr);
	
	
	
	write(client_socket, &msg, sizeof(Msg));
 
	save(&msg);    //保存聊天记录
	}
	sleep(2);
}


void client_logout(int client_socket)//注销
{
	Msg msg;
	memset(&msg,0,sizeof(msg));
	msg.cmd=99;
	strcpy (msg.account,account_c);
	send(client_socket,&msg,sizeof(msg),0);
}

void save(Msg*msg)//保存
{
	char buf2[120];
	char ai[1050];
	memset(buf2,0,sizeof(buf2));
	memset(ai,0,sizeof(ai));
	if(strcmp(myname,msg->fromname)==0)
	{
	sprintf(buf2,"../liaotianjilu/%s & %s.txt",msg->fromname,msg->localname);
	}
	else
	{
	sprintf(buf2,"../liaotianjilu/%s & %s.txt",msg->localname,msg->fromname);
	}	
	
	int ps=open(buf2,O_WRONLY|O_CREAT|O_APPEND,0644);
	sprintf(ai,"%s\n",msg->msg);
	write(ps,ai,strlen(ai));
	close (ps);

}

void look_chat()//查看聊天记录
{
	char buf2[120];
	char ai[10500];
	memset(ai,0,sizeof(ai));
	memset(buf2,0,sizeof(buf2));
	printf("查看与谁的聊天记录:\n");
	scanf("%s",buf2);
	while(getchar()!='\n');
	sprintf(ai,"../liaotianjilu/%s & %s.txt",myname,buf2);
	
	int ps=open(ai,O_RDONLY);
	if(ps==-1)
	{
		printf("查看失败\n");
		sleep(1);
		return;
	}
	memset(ai,0,sizeof(ai));
	read(ps,ai,sizeof(ai));
	printf("%s\n",ai);
	getchar();
	close(ps);

	
}

void see_now_time(int client_socket)//查看在线人数
{
	Msg msg;
	memset(&msg,0,sizeof(msg));
	msg.cmd = 6;
	strcpy (msg.fromname,myname);
	write(client_socket, &msg, sizeof(Msg));
	
	sleep(2);
}

void client_guanli(int client_socket)//管理
{
	Msg msg;
	memset(&msg,0,sizeof(msg));
	printf("请选择:1禁言,2踢人\n");
	int i;
	scanf("%d",&i);
	while(getchar()!='\n');
	if(i==1)
	{
	msg.cmd=87;
	printf("请选择要禁言的人:\n");
	see_now_time(client_socket);
	scanf("%s",msg.localname);
	
	

	}
	else if(i==2)
	{
		msg.cmd=88;
		printf("请选择要踢的人:\n");
	see_now_time(client_socket);
	scanf("%s",msg.localname);
	
	
	}
	else
	{
		printf("管理失败\n");
		return;
	}
	strcpy (msg.fromname,myname);
	write(client_socket, &msg, sizeof(Msg));
	
	sleep(1);
}
 
//修改个性签名
void revise_sign(int client_socket)
{
	Msg msg;
	memset(&msg,0,sizeof(msg));
	msg.cmd = 7;
	printf("请输入新的个性签名:");
	scanf("%s",msg.signname);
	while(getchar() != '\n');
	
	strcpy(msg.fromname,myname);   //需要保存名字
	strcpy(msg.account,account_c);
	write(client_socket, &msg, sizeof(Msg));
 
	sleep(2);
}
 
//修改密码
void revise_password(int client_socket)
{
	Msg msg;
	memset(&msg,0,sizeof(msg));
	msg.cmd = 8;
	printf("请输入新的密码: ");
	scanf("%s",msg.password);
	while(getchar() != '\n');
	
	strcpy(msg.fromname,myname);   //需要保存名字
	strcpy(msg.account,account_c);
	write(client_socket, &msg, sizeof(Msg));
	
	sleep(2);
}	

void transfer_file(int client_socket)//传输文件
{
	Msg msg;
	msg.cmd = 9;
	printf("请输入你要传输文件的对象:");
	scanf ("%s",msg.localname);
	while(getchar() != '\n');
	
	printf("请输入你要传输的本地文件名:");
	scanf ("%s",msg.signname);
	while(getchar() != '\n');
	
	strcpy(msg.fromname,myname);   //需要保存名字
	write(client_socket, &msg, sizeof(Msg));
	
	printf("等待验收中...\n");
	
	strcpy(fsignname,msg.signname);
	strcpy(mylocalname,msg.localname);
	getchar();
	
}
 

//表示愿意接受文件
void transfer_file_y(int client_socket)
{
	Msg msg;
	msg.cmd = 10;
	strcpy(msg.fromname,myname); 
	strcpy(msg.localname,mylocalname); 
	strcpy(mylocalname,"\0");    //用完后置零
	write(client_socket, &msg, sizeof(Msg));
}
 
//表示不愿意接受文件
void transfer_file_n(int client_socket)
{
	Msg msg;
	msg.cmd = -10;
	strcpy(msg.fromname,myname); 
	strcpy(msg.localname,mylocalname); 
	strcpy(mylocalname,"\0");    //用完后置零
	write(client_socket, &msg, sizeof(Msg));
}
 
//传出文件来源开始传输文件
void start_transfer_file(int client_socket)
{
	Msg msg;
	char ai[120];
	msg.cmd = 11; 
	strcpy(msg.fromname,myname); 
	strcpy(msg.signname,fsignname);
	strcpy(msg.localname,mylocalname);
	sprintf(ai,"../send/%s",msg.signname);
	int fd = open(ai,O_RDONLY); 
	if(fd == -1)
	{
		perror("open");
		printf("传输失败\n");
		return ;
	}
	
	int ret = 0;
	int i = 0;
	while(ret = read(fd,msg.msg,SIZE))
	{
		if(ret == -1)
		{
			perror("read");
			break;
		}
		if (ret == 0)
		{
			break;
		}
		msg.num = ret;
		write(client_socket, &msg, sizeof(Msg));
		usleep(10000);     //这个睡眠时间减缓传输速度,降低不可预祝错误出现
	}
	printf("文件复制完成传输\n");
	strcpy(fsignname,"\0");
	strcpy(mylocalname,"\0");
	
	close(fd);
}
 
//接受文件
void save_transfer_file(Msg * buf)
{
	char ai[120];
	sprintf(ai,"../recv/%s",buf->signname);
	int fd = open(ai,O_WRONLY|O_CREAT|O_APPEND,0777);
	write(fd,buf->msg,buf->num);
	if(buf->num != SIZE)
	{
		printf ("文件接受完成\n");
	}
	close (fd);
	
}
 

服务端:

select.h

#ifndef _select_h_
#define _select_h_
#include <stdio.h>
#define SIZE 1024
#define NOW_MAX 20 
typedef struct msg          //表示聊天时的信息
{
	char msg[SIZE];   		//消息内容
	char localname[20];		//消息目的名称
    char account[20];            //账户
	char fromname[20];      //消息来源名称
	char password[20];      //用户密码   
	char signname[40];      //个性签名   
	int cmd;                //工作模式
	int num;                //用于保存文件复制的字节数
}Msg;
	 	 //最大允许并行客户端数量
 
typedef struct now_client   //在线人数
{
	char name[20];
	int socket;
	int jin;
	int ti;
}NowClient;
int regiss(int client_socket, Msg *msg);
int entry(int client_socket, Msg *msg);
void server_entryout(int client_socket,Msg *msg);
void server_chatall(int client_socket, Msg * msg);
void server_chatone(int client_socket, Msg * msg);
void server_logout(int client_socket,Msg *msg);
void server_save(Msg*msg);
void see_nowuser(int client_socket, Msg * msg);
void sremove(int client_socket, Msg * msg);
void shutup(int client_socket, Msg * msg);
void server_transfer_file(int client_socket, Msg * msg);//收到开始传输文件命令
 
void server_transfer_file_y(Msg * msg);//接受文件
 
void server_transfer_file_n(Msg * msg);//拒绝文件
 
void server_start_transfer_file(Msg * msg);//开始文件传输
void server_revise_sign(int client_socket, Msg * msg);
void server_revise_password(int client_socket, Msg * msg);
#endif

server.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include "select.h"
#include "1mysql.h"

#define SER_IP       "127.0.0.1"
#define SER_PORT     6789

#define LISTEN_MAX   100
NowClient user[NOW_MAX];    //表示当前在线人的资料


/********************************************
 * TCP通信服务器端流程
 * 1,创建套接字
 * 2,绑定
 * 3,监听
 * 4,等待连接
 * 5,收发数据
 * 6,关闭套接字
 * *************************************************/

int all_clifd[256] = {0};//存放所有的客户端描述符
int count = 0;//计数
MYSQL *my_sql;//数据库描述符

void accept_do_handler(int sockfd);
void recv_do_handler(int sockfd);
void send_to_all_cli(char *buf);

int sockfd;
void guan()//关闭函数
{
    close(sockfd);
    mysql_close(my_sql);
    exit(-1);
}
int main(int argc, char  *argv[])
{
   pid_t pid= fork();
   if (pid==0){
    //1.创建套接字   IPV4  流式套接字  TCP协议
     sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("sockfd = %d\n",sockfd);
    signal(10,guan);
    //2.绑定
    struct sockaddr_in seraddr;
    memset(&seraddr,0,sizeof(struct sockaddr_in));
    seraddr.sin_family = AF_INET;//ipv4协议类型
    // seraddr.sin_addr;//ip地址  网络字节序
    // inet_aton(SER_IP,&seraddr.sin_addr);
    seraddr.sin_addr.s_addr = inet_addr(SER_IP);
    seraddr.sin_port = htons(SER_PORT);//端口    网络字节序
    int opt=1;
    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    if(bind(sockfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr_in)) == -1)
    {
        perror("bind");
        return -1;
    }
    printf("绑定成功\n");

    //3.监听
    if(listen(sockfd,LISTEN_MAX) == -1)
    {
        perror("listen");
        return -1;
    }
    printf("监听成功\n");

    //非阻塞轮询
    int maxfd = sockfd;
    //处理连接请求--》sockfd发生可读变化
    //接收数据--》clifd发生可读变化
    fd_set readfds;//检测可读变化
     my_sql=Sconnect();//连接数据库
    int ret = 0;
    // struct timeval select_time;
    int i = 0;
    while(1)
    {
        //
        //先去清空集合
        FD_ZERO(&readfds);
        //再去添加
        FD_SET(sockfd,&readfds);
        
        for(i = 0;i<count;i++)
        {
            //添加描sockfd述符
            FD_SET(all_clifd[i],&readfds);
            //求出来最大的
            if(all_clifd[i]>maxfd)
            {
                maxfd = all_clifd[i];
            }
           
        }
        printf("\n");
        
        //永久阻塞
        ret = select(maxfd+1,&readfds,NULL,NULL,NULL);
        if(ret == -1)
        {
            perror("select");
            return -1;
        }
        else if(ret == 0)
        {
            printf("超时\n");
            continue;
        }
        //有变化,会打断阻塞,将集合里边没有变化的删除掉
        else
        {
            // printf("有文件描述符发生变化\n");
            //判断是谁发生的变化,根据变化去作处理
            //轮询从0到maxfd所有的文件描述符
            for(i = 0;i<=maxfd;i++)
            {
                //检查发生变化的是否是readfds集合里边的
                if(FD_ISSET(i,&readfds)==1)
                {
                    //服务器套接字描述符发生变化
                    //有客户端请求连接
                    if(i == sockfd)
                    {
                        accept_do_handler(sockfd);
                    }
                    //有客户端发消息
                    else
                    {
                        
                        recv_do_handler(i);
                    }
                }
            } 
        }
        
    }
    
 
   }
   else
   {
    char a;
        while(1)
        {
            printf("y/n\n");
            a=getchar();
            if(a=='y')
            {
                printf("1");
                kill(pid,10);
                exit(0);
            }
        }
   }
}

void accept_do_handler(int sockfd)
{
    //4.等待连接
    //存放连接成功之后,客户端的IP地址和端口
    struct sockaddr_in cliaddr;
    memset(&cliaddr,0,sizeof(struct sockaddr_in));
    socklen_t len = sizeof(struct sockaddr_in);
    int clifd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
    if(clifd == -1)
    {
        perror("accept");
        return;
    }
   

    //存储所有的客户端描述符
    all_clifd[count] = clifd;
    count++;
    
}

void recv_do_handler(int client_socket)
{
    // 5.数据收发
    
    int i = 0;
    int ret = 0;
    int pos = 0;//记录下表
    Msg msg;
    memset(&msg,0,sizeof(msg));
    
    //阻塞
    ret = recv(client_socket,&msg,sizeof(msg),0);
    char shi[1024];
    sprintf(shi,"%s %s %s %s",msg.fromname,msg.account,msg.signname,msg.password);
    printf("%s\n",shi);
    if(ret==-1)
    {
        perror("recv");
        return ;
    }
    else if(ret == 0)
    {
        
        for(i = 0;i<count;i++)
        {
            if(all_clifd[i] == client_socket)
            {
                all_clifd[i] = 0;
                pos = i;
                break;
            }
        }
        for(i = pos;i<count-1;i++)
        {
            all_clifd[i] = all_clifd[i+1];
        }
        all_clifd[count-1]=0;
        count--;
        
    }
    else
    {
        
        switch (msg.cmd)
		{
			case 1 :    // 注册
				regiss(client_socket, &msg);
				break;
			case 2 :    //登录
				ret = entry(client_socket, &msg);
				send(client_socket , &msg ,sizeof(Msg),0);
				if (ret == 1)
				{
					//在线人数加1
					int i;
					for (i=0; i<NOW_MAX; i++)
					{
						if(user[i].socket == 0)
						{
							strcpy(user[i].name, msg.fromname);
							user[i].socket = client_socket;
                            user[i].jin=0;
                            user[i].ti=0;
							printf("客户端在线人数加一\n");
							break;
						}
					}
                    char guanbuf[1000];
                    sprintf(msg.msg,"%s已登录",msg.fromname);
                    msg.cmd=22;
					//用户界面
					
                    for(i=0;i<NOW_MAX;i++)
                    {
                        if(user[i].socket == 0)
                        continue;
                        if(user[i].socket==client_socket)
                        continue;
                        send(user[i].socket,&msg,sizeof(Msg),0);
                    }  
				}	
				break;
                case 3://群聊
                server_chatall(client_socket, &msg);
                server_save(&msg);
				break;
                case 4 :    //私聊
				server_chatone(client_socket, &msg);
                server_save(&msg);	
				break;
                case 5://退出登录
                server_entryout(client_socket,&msg);
                sprintf(msg.msg,"%s已下线",msg.fromname);
                    msg.cmd=22;
                    for(i=0;i<NOW_MAX;i++)
                    {
                        if(user[i].socket == 0)
                        continue;
                        if(user[i].socket==client_socket)
                        continue;
                        send(user[i].socket,&msg,sizeof(Msg),0);
                    }  break;
                case 6://在线人数
                see_nowuser(client_socket,&msg);break;
                case 7://改签名
                server_revise_sign(client_socket, &msg);
				break;
                case 8://改密码
                server_revise_password(client_socket, &msg);
				break;
                case 9://传输文件
                server_transfer_file(client_socket, &msg);
                    break;
                case 10:   //接受传输文件
                    server_transfer_file_y(&msg);
                    break;
                case -10 : //拒绝传输文件
                    server_transfer_file_n(&msg);
                    break;
                case 11:   //一切条件都已成立,直接开始传输
				server_start_transfer_file(&msg);break;
                case 87://禁言
                shutup(client_socket,&msg);break;
                case 88://移除
                sremove(client_socket,&msg);break;
                case 99://注销
                server_logout(client_socket,&msg);break;
            
		}
    }
    
}



int regiss(int client_socket, Msg *msg)//注册
{
    //printf("nihaos%d\n",__LINE__);
    
    int flag;
    int i=-1,j=-1,a=0;
    i=search(my_sql,msg);
    j=search1(my_sql);
    if(j==0)
    {
        a=1;
    }
    printf("%d\n",i);
    if(i==0)
    { //printf("nihaos%d\n",__LINE__);
        flag= data_insert(my_sql,msg,client_socket,a);
        msg->cmd+=1000;
    }
    else if(i==1)
    msg->cmd=-1;
    printf("%d\n",msg->cmd);
   // printf("nihaos%d\n",__LINE__);
    send(client_socket,msg,sizeof(Msg),0);
    
}

int entry(int client_socket, Msg *msg)
{
	printf(" %s进行登录\n",msg->fromname);
	
	//用户登录
	
	
	int flag = Entry_User(my_sql,msg,client_socket);
    if (flag == -1)
	{
		printf("登录失败,系统繁忙\n");
		msg->cmd = -1;
		return -1;
	}
	
	if (flag == -2)
	{
		printf("登录失败,用户名不存在\n");
		msg->cmd = -2;
		return -2;
	}
	if (flag == -3)
	{
		printf("登录失败,用户已登录\n");
		msg->cmd = -3;
		return -3;
	}
    if (flag == -4)
	{
		printf("登录失败,密码错误\n");
		msg->cmd = -4;
		return -4;
	}
	else 
	{
		printf("登录成功\n");
		msg->cmd += 1000;
		return 1;
	}
}

void server_entryout(int client_socket,Msg *msg)//退出登录
{
   int ret= change(my_sql,"type","0",msg->account);
   //printf("%d\n",client_socket);
   if(ret==0)
   {
    int i ;
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket == client_socket)
		{
			user[i].socket = 0;
			printf("客户端在线人数减一\n");
			break;
		}
	}
   msg->cmd=5;
   sprintf(msg->msg,"退出登录成功");
   }
   else
   {
   sprintf(msg->msg,"退出登录失败");
   msg->cmd=-5;
   }
   //printf("%d\n",client_socket);
   char shi [1024];
   sprintf(shi,"%s %s %s %s %d",msg->fromname,msg->account,msg->signname,msg->password,msg->cmd);
    printf("%s\n",shi);
   send(client_socket,msg,sizeof(Msg),0);
   //printf("fasongcg\n");
}

void server_chatall(int client_socket, Msg * msg)//群聊
{
	printf (" %s 进行群发.\n",msg->fromname);
	
    int j;
        for (j=0; j<NOW_MAX; j++)
        {
            if(user[j].socket==client_socket)
            {
                if(user[j].ti==1)
                {
                    memset(msg->msg,0,1000);
                    sprintf(msg->msg,"发送失败已被移除群聊");
                    write(client_socket, msg , sizeof(Msg));
                    return ;
                }
                if(user[j].jin==1)
                {
                    memset(msg->msg,0,1000);
                    sprintf(msg->msg,"发送失败已被禁言");
                    write(client_socket, msg , sizeof(Msg));
                    return ;
                }
                
                break;
            }
		}
	int i;
	for(i=0; i<NOW_MAX; i++)
	{
		if (user[i].socket != 0&&user[i].socket!=client_socket&&user[i].ti!=1)
		{
			write(user[i].socket, msg , sizeof(Msg));
		}
	}
}

void server_chatone(int client_socket, Msg * msg)//私聊
{
	printf ("私聊 %s发送信息给%s\n",msg->fromname,msg->localname);
	
	int i;
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket != 0 && strcmp(user[i].name, msg->localname) == 0)
		{
			write(user[i].socket, msg , sizeof(Msg));
			printf("私聊成功\n");
			break;
		}
	}
	if (i == NOW_MAX)
	{
		msg->cmd = -3;    //表示私聊失败
		write(client_socket, msg , sizeof(Msg));
		printf("私聊失败\n");
	}
}

void server_logout(int client_socket,Msg *msg)//注销
{
    int ret=mysql_delete(my_sql,msg->account);
    if(ret==0)
    {
        printf("注销成功\n");
        int i ;
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket == client_socket)
		{
			user[i].socket = 0;
			printf("客户端在线人数减一\n");
			break;
		}
	}
        msg->cmd=99;
    }
    else
    msg->cmd=-99;
    send(client_socket,msg,sizeof(Msg),0);
}

void server_save(Msg*msg)//保存聊天记录
{
	char buf2[120];
	char ai[1250];
	memset(buf2,0,sizeof(buf2));
	memset(ai,0,sizeof(ai));
	
	sprintf(buf2,"../serverliaotianjilu/all.txt");
	
	
	int ps=open(buf2,O_WRONLY|O_CREAT|O_APPEND,0644);
	sprintf(ai,"%s->%s:%s\n",msg->fromname,msg->localname,msg->msg);
	write(ps,ai,strlen(ai));
	close (ps);

}

void see_nowuser(int client_socket, Msg * msg)//在线人员
{
	printf("%s 查看当前在线人员\n",msg->fromname);
	
	int i;
	int len;
	char buf[1024] = {0};
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket != 0)
		{
			strcat(buf,user[i].name);
			len = strlen(buf);
			buf[len] = ' ';
		}
	}
	strcpy(msg->msg,buf);
	
	write(client_socket, msg, sizeof(Msg));
	printf("查看成功\n");
}


void server_revise_sign(int client_socket, Msg * msg)//修改个性签名
{
	printf("%s 修改个性签名\n",msg->fromname);
	int ret = change(my_sql,"signname",msg->signname,msg->account);    //修改数据库
	if (ret == -1)
	{
		msg->cmd = -7;
		printf("%s 修改个性签名失败\n",msg->fromname);
		write(client_socket, msg, sizeof(Msg));
	}
	printf("%s 修改个性签名成功\n",msg->fromname);
	write(client_socket, msg, sizeof(Msg));
}

//修改密码
void server_revise_password(int client_socket, Msg * msg)
{
	printf("%s 修改密码\n",msg->fromname);
	int ret = change(my_sql,"passwd",msg->password,msg->account);
	if (ret == -1)
	{
		msg->cmd = -8;
		printf("%s 修改密码失败\n",msg->fromname);
		write(client_socket, msg, sizeof(Msg));
	}
	printf("%s 修改密码成功\n",msg->fromname);
	write(client_socket, msg, sizeof(Msg));
}

void shutup(int client_socket, Msg * msg)//禁言
{
        int i;
        for (i=0; i<NOW_MAX; i++)
        {
            if(strcmp(user[i].name ,msg->localname)==0)
            {
                
                user[i].jin=1;
                
                
                break;
            }
		}
        write(client_socket, msg, sizeof(Msg));
}

void sremove(int client_socket, Msg * msg)//移除
{
        int i;
        for (i=0; i<NOW_MAX; i++)
        {
            if(strcmp(user[i].name ,msg->localname)==0)
            {
                
                user[i].ti=1;
                
                
                break;
            }
		}
        write(client_socket, msg, sizeof(Msg));
}

//传输文件
void server_transfer_file(int client_socket, Msg * msg)
{
	printf ("%s 请求发送%s文件给 %s\n",msg->fromname,msg->signname,msg->localname);
	int i;
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket != 0 && strcmp(user[i].name, msg->localname) == 0)
		{
			write(user[i].socket, msg , sizeof(Msg));
			printf("发送给%s信息进行判断是否接受\n",msg->localname);
			break;
		}
	}
	if (i == NOW_MAX)
	{
		msg->cmd == -9;    //表示传输文件失败
		write(client_socket, msg , sizeof(Msg));
		printf("发送文件失败,好友不在线或不存在\n");
	}
}

void server_transfer_file_y(Msg * msg)//接收文件
{
	printf("%s 接受文件传输.\n",msg->fromname);
	
	int i;
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket != 0 && strcmp(user[i].name, msg->localname) == 0)
		{
			write(user[i].socket, msg , sizeof(Msg));
			break;
		}
	}
}
 
//拒绝文件
void server_transfer_file_n(Msg * msg)
{
	printf("%s 不愿意接受文件传输.\n",msg->fromname);
	
	int i;
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket != 0 && strcmp(user[i].name, msg->localname) == 0)
		{
			write(user[i].socket, msg , sizeof(Msg));
			break;
		}
	}
}

void server_start_transfer_file(Msg * msg)
{
	//printf("文件传输中\n");
	int i;
	for(i=0; i<NOW_MAX; i++)
	{
		if(user[i].socket != 0 && strcmp(user[i].name, msg->localname) == 0)
		{
			write(user[i].socket, msg , sizeof(Msg));    //写文件数据
			break;
		}
	}
	if(msg->num != 1024)
	{
		printf("文件传输完成\n");
	}
}

数据库端:

1mysql.h

#ifndef _mysql_h_
#define _mysql_h_
#include <stdio.h>
#include <mysql/mysql.h>
#include <string.h>
#include <stdlib.h>
#include "select.h"
#define HOSTNAME        "localhost"
#define USERNAME        "root"
#define PASSWD          "1"
#define DATABASENAME    "chatroom"
#define TABLENAME       "infor"
void print_all_data(MYSQL *mysql,Msg *msg);
int search(MYSQL *mysql,Msg *msg);

MYSQL * Sconnect();
int data_insert(MYSQL *mysql,Msg *msg,int sockfd,int root);
int  Entry_User(MYSQL *con,Msg *msg,int my_sockfd);
int change(MYSQL *my_sql,char *field,char * changenaem,char *naem);
int mysql_delete(MYSQL *my_sql,char *deletename);
int search1(MYSQL *mysql);
#endif

mysql.c

#include "1mysql.h"
#include "select.h"
MYSQL * Sconnect()//连接数据库
{
   static MYSQL my_sql;
    //0.初始化核心结构体
    mysql_init(&my_sql);
    //1.连接数据库
    //指定使用"hello"数据库
    MYSQL*p =  mysql_real_connect(&my_sql,HOSTNAME,USERNAME,PASSWD,DATABASENAME,0,NULL,0);
     if(p == NULL)
    {
        // perror("mysql_real_connect");
        printf("mysql_error:%s\n",mysql_error(&my_sql));
        return NULL;
    }
    printf("数据库连接成功\n");
    return &my_sql;
}

void print_all_data(MYSQL *mysql,Msg *msg)
{
    char buf[256];
    int ret = 0;
    memset(buf,0,sizeof(buf));
    sprintf(buf,"SELECT * FROM %s",TABLENAME);
    // printf("buf:%s\n",buf);
    ret = mysql_real_query(mysql,buf,strlen(buf));
    if(ret != 0)
    {
        printf("mysql_error:%s\n",mysql_error(mysql));
        return;
    }
    // printf("查询数据成功\n");
    //获取结果集
    MYSQL_RES *pres = mysql_store_result(mysql);
    if(pres == NULL)
    {
        printf("mysql_error:%s\n",mysql_error(mysql));
        return;
    }    
    // printf("获取结果集成功\n");
    
    MYSQL_ROW row;
    int i,j;
    for(i = 0;i<mysql_num_rows(pres);i++)
    {
        //读取结果集里边的一行数据
        row = mysql_fetch_row(pres);
        if(row ==  NULL)
        {
            printf("mysql_error:%s\n",mysql_error(mysql));
            return;
        }
 
        for(j=0;j<mysql_num_fields(pres);j++)
        {
            printf("%s\t",row[j]?row[j]:NULL);
        }
        printf("\n");

    }


    //释放结果集
    mysql_free_result(pres);
}

int search(MYSQL *mysql,Msg *msg)//查询数据表某一项
{
    int ret;
    char buf[256];
printf("nihaos\n");
    sprintf(buf,"SELECT * FROM %s WHERE account='%s'",TABLENAME,msg->account);
    ret = mysql_real_query(mysql,buf,strlen(buf));
    MYSQL_RES *pres = mysql_store_result(mysql);
    uint64_t rownum =  mysql_num_rows(pres);
    return rownum; 
}


int search1(MYSQL *mysql)//查询数据表
{
    int ret;
    char buf[256];
printf("nihaos\n");
    sprintf(buf,"SELECT * FROM %s ",TABLENAME);
    ret = mysql_real_query(mysql,buf,strlen(buf));
    MYSQL_RES *pres = mysql_store_result(mysql);
    uint64_t rownum =  mysql_num_rows(pres);
    return rownum; 
}


int data_insert(MYSQL *mysql,Msg *msg,int sockfd,int root)//插入数据表
{
    char query[1024];
    sprintf(query,"insert into %s values('%s','%s','%s','%d','%d','%d','%s')",
		TABLENAME ,msg->fromname,msg->account,msg->password,0,sockfd,root,msg->signname);
        printf("%s\n",query);
        if((mysql_real_query(mysql,query,(unsigned int)strlen(query))) != 0){
		
		printf("insert failed.\n");
		return 0;
	}
    return 1;
}


int  Entry_User(MYSQL *con,Msg *msg,int my_socket)//登录
{
    char query[1024],shi[1024];
    
    
    sprintf(query,"SELECT * FROM %s WHERE account = '%s'",TABLENAME,msg->account);
  
    if((mysql_real_query(con,query,(unsigned int)strlen(query))) != 0)
    {
		
		printf("insert failed.\n");
		return -1;
	}
    MYSQL_RES *pres = mysql_store_result(con);
    uint64_t rownum =  mysql_num_rows(pres);
    if(rownum==0)
    {
        return -2;
    }
    MYSQL_ROW row;

    int num_fields=mysql_num_fields(pres);
    row=mysql_fetch_row(pres);
    
    if(strcmp(row[3],"1")==0)
    {
        return -3;
    }
    
    if(strcmp(msg->password,row[2])!=0)
    {
        return -4;
    }
    char di[10];
    sprintf(di,"%d",my_socket);
    change(con,"socket",di,msg->account);
    change(con,"type","1",msg->account);
    
    strcat(msg->signname,row[6]);
    strcat(msg->fromname,row[0]);
    if(strcmp(row[5],"1")==0)
    msg->num=1;
    else
    msg->num=0;
    return 0;
    

}

int change(MYSQL *my_sql,char *field,char * changenaem,char *naem)//修改数据表
{
    char query[1024];
    sprintf(query,"update  %s set %s = '%s' where account='%s'",TABLENAME,field,changenaem,naem);
    if((mysql_real_query(my_sql,query,(unsigned int) strlen(query)))!=0)
    {
        printf("insert failed.\n");
		return -1;
    }
return 0;

}


int mysql_delete(MYSQL *my_sql,char *deletename)//删除数据表
{
    char query[1024];
    sprintf(query,"delete from %s  where account='%s'",TABLENAME,deletename);
    if((mysql_real_query(my_sql,query,(unsigned int) strlen(query)))!=0)
    {
        printf("insert failed.\n");
		return -1;
    }
return 0;

}

编译客户端:gcc client.c -lmysqlclient -lpthread -o c

编译服务端:gcc server.c mysql.c -lmysqlclient -lpthread -o s

注意:使用mysql要安装相应的库;

安装服务器端

sudo apt-get install mysql-server

安装客户端

sudo apt-get install mysql-client

安装C语言API库

sudo apt-get install libmysqlclient-dev

如果想直接运行代码的话还要在mysql里面添加chatroom数据库,在chatroom库里面添加infor数据表;

步骤1:创建chatroom数据库,代码:create database chatroom;

步骤2:进入chatroom数据库,代码:use chatroom;

 步骤3:创建infor数据表,代码:CREATE TABLE infor(name varchar(20),account varchar(20),passwd varchar(20),type int,socket int,root int,signname varchar(100));

  • 24
    点赞
  • 198
    收藏
    觉得还不错? 一键收藏
  • 25
    评论
这段代码是一个简单的Linux网络编程聊天的TCP实现。它包括了服务器端和客户端的代码。 服务器端(service function)通过socket函数创建一个套接字,然后绑定到指定的IP地址和端口上。接下来进入一个循环中,等待客户端的连接请求。当有客户端连接时,服务器会记录客户端的socket,并启动一个线程为该客户端提供服务。同时,服务器会提示其他客户端有新用户上线,并发送相应的信息给已连接的其他客户端。 客户端(main function)也通过socket函数创建一个套接字,然后与服务器建立连接。客户端需要输入一个用户名,并将其发送给服务器。之后,客户端进入一个循环中,等待用户输入消息。当用户输入"bye"时,客户端会发送该消息给服务器,并关闭套接字,结束程序。 整个程序还存在一些不足之处。首先,发送数据时无法识别空格。其次,查询聊天记录的功能还需完善,目前只能显示前100个字符。如果还有其他不足之处,可以继续完善。希望这段代码对您有所帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [linux网络编程TCP多人聊天](https://blog.csdn.net/qq_44891751/article/details/95067267)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值