一、项目要求
系统能实现注册登录功能
然后能实现用户间相互通讯
二、项目思路
注册的账号保存在数据库中,这里需要搭建数据库
登录账号后选择需要聊天的对象并与其聊天,这里客户端需要用到线程,能同时读写;在线客户需要引用一个链表保存起来,每当有新客户登录时将客户与其文件描述符存储在链表中,而退出时将其从链表里剔除
三、代码
1. .c文件
1)server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/epoll.h>
#include <string.h>
#include "mysql.h"
#include "list.h"
#include "pack.h"
struct word
{
int fd; //客户端的文件描述符
int objfd; //客户端选择的对象客户的文件描述符
struct word *next;
};
//成功返回监听套接字, 失败返回NULL
int sock_init()
{
int sockfd;
int ret;
sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建监听套接字
if(sockfd<0)
{
perror("socket");
return -1;
}
//设置套接字端口复用
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//设置地址族
struct sockaddr_in seraddr;
int addrlen = sizeof(struct sockaddr_in);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8001);
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
ret = bind(sockfd, (struct sockaddr*)&seraddr, addrlen); //绑定地址和端口号
if(ret<0)
{
perror("bind");
return -1;
}
ret = listen(sockfd, 10); //通知内核监听套接字
if(ret<0)
{
perror("bind");
return -1;
}
return sockfd;
}
//处理显示在线人数请求
int deal_show_request(int cfd,struct node *head)
{
int ret;
int num=0;
struct node *p=head->next;
struct pack pk;
pk.ver=1;
pk.type=SHOW;//设置type
pk.len=len_list(head);//求出在线人数
ret=write(cfd,&pk,sizeof(struct pack));
if(ret<0)
{
close(cfd);
return -1;
}
while(p!=NULL)
{
ret=write(cfd,p,sizeof(struct node));//发送在线人数的链表给客户端
if(ret<0)
{
close(cfd);
return -1;
}
p=p->next;
}
return 0;
}
//处理通信请求
int deal_communicate_request(int sockfd,int cfd,int efd,struct node *head)
{
int ret;
char buff[1024];
//读客户端发来的信息
ret = read(sockfd,buff,1024);
if(ret<0)
{
perror("read");
//1、关闭文件描述符
close(sockfd);
//2、从集合中移除
epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
return -1;
}
else if(ret == 0)
{
//1、关闭文件描述符
close(sockfd);
//2、从集合中移除
epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
del_list(head,sockfd);
return -1;
}
buff[ret] = '\0';
//将信息写给目标客户端
ret = write(cfd,buff,strlen(buff));
if(ret<0)
{
close(cfd);
return -1;
}
}
//处理注册请求
//成功:0
//失败:-1
int deal_reg_request(int cfd, int efd,struct node *head)
{
int ret;
struct reg r1;
ret = read(cfd, &r1, sizeof(struct reg));
if(ret<0)
{
perror("read");
//1、关闭文件描述符
close(cfd);
//2、从集合中移除
epoll_ctl(efd, EPOLL_CTL_DEL, cfd, NULL);
return -1;
}
else if(ret == 0)
{
//1、关闭文件描述符
close(cfd);
//2、从集合中移除
epoll_ctl(efd, EPOLL_CTL_DEL, cfd, NULL);
del_list(head,cfd);
return -1;
}
//去数据库中查询该用户是否存在
// select * from user_info where name = ''
char sql[1024];
int rows = 0;
MYSQL_RES *result = NULL;
sprintf(sql, "select * from user_info where name='%s'", r1.name);
my_go(sql, &result);
rows = mysql_num_rows(result);
struct pack pk;
pk.ver = 1;
pk.type = REG_FAIL;
pk.len = 0;
if(rows == 0)//用户不存在
{
//在数据库中注册
sprintf(sql, "insert into user_info values ('%s', '%s')", r1.name, r1.passwd);
my_go(sql, NULL);
//修改包头类型中的数据
pk.type = REG_OK;
}
ret = write(cfd, &pk, sizeof(struct pack));
if(ret<0)
{
//1、关闭文件描述符
close(cfd);
//2、从集合中移除
epoll_ctl(efd, EPOLL_CTL_DEL, cfd, NULL);
}
return 0;
}
//处理登录请求
//成功返回0
//失败返回-1
int deal_login_request(int cfd, int efd,struct node *head)
{
int ret;
struct reg r2;
struct node *p=head->next;
ret=read(cfd,&r2,sizeof(struct reg));
if(ret<0)
{
perror("read");
close(cfd);
epoll_ctl(efd,EPOLL_CTL_DEL,cfd,NULL);
return -1;
}
else if(ret == 0)
{
close(cfd);
epoll_ctl(efd,EPOLL_CTL_DEL,cfd,NULL);
del_list(head,cfd);
return -1;
}
struct pack pk;
pk.ver = 1;
pk.type = LOG_OK;
pk.len = 0;
char sql[1024];
int rows = 0;
MYSQL_RES *result = NULL;
sprintf(sql,"select * from user_info where name='%s'",r2.name);
my_go(sql,&result);
rows = mysql_num_rows(result);
if(rows == 0)
{
pk.type=LOG_FAIL_NONAME;
}
else
{
MYSQL_ROW row = NULL;
for(int i=1;i<=rows;i++)
{
row = mysql_fetch_row(result);
if(strcmp(r2.name,row[0]) == 0)
{
if(strcmp(r2.passwd,row[1]) == 0)
{
pk.type = LOG_OK;
struct node *pnew = NULL;
//每当有客户端登录成功,添加进在线客户端链表中
pnew = create_node();
pnew->fd = cfd;
strcpy(pnew->name,r2.name);
pnew->next = head->next;
head->next = pnew;
}
else
{
pk.type = LOG_FAIL_WRONGPSWD;
}
}
}
}
ret = write(cfd,&pk,sizeof(struct pack));
if(ret<0)
{
close(cfd);
epoll_ctl(efd,EPOLL_CTL_DEL,cfd,NULL);
}
return 0;
}
int main()
{
struct node *head=NULL;
head=create_node();
struct word *head1=NULL;
char buff[1024];
int sockfd;
int ret, count;
int cfd, efd;
struct sockaddr_in cliaddr;
int addrlen=sizeof(struct sockaddr_in);
int port;
char ip[64];
// 连接数据库
ret = my_init();
if(ret<0)
{
return -1;
}
efd = epoll_create(100); //创建集合空间
if(efd<0)
{
perror("epoll_create");
return -1;
}
sockfd = sock_init();
if(sockfd < 0)
{
return -1;
}
struct epoll_event ev;
struct epoll_event evs[10];
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &ev); //管理集合空间中的描述符
while(1)
{
printf("服务器等待响应...\n");
count = epoll_wait(efd, evs, 10, -1); //监听集合中的文件描述符
if(count < 0)
{
perror("epoll_wait");
break;
}
for(int i=0; i<count; i++)
{
int temp = evs[i].data.fd;
if(temp == sockfd)// 有客户端请求连接
{
//1、接收客户端
cfd = accept(sockfd, (struct sockaddr*)&cliaddr, &addrlen);
port=ntohs(cliaddr.sin_port);
inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,64);
printf("客户端[%s:%d]连接成功!\n",ip,port);
if(cfd<0)
{
perror("accept");
continue;
}
//2、cfd 加入efd关联的集合中
ev.data.fd = cfd;
epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &ev);
}
else //已经连接过来的客户端发来数据
{
struct pack pk;
ret = read(temp, &pk, sizeof(struct pack));
if(ret<0)
{
perror("read");
//1、关闭文件描述符
close(temp);
//2、从集合中移除
epoll_ctl(efd, EPOLL_CTL_DEL, temp, NULL);
continue;
}
else if(ret == 0)
{
printf("客户端断开连接!\n");
//1、关闭文件描述符
close(temp);
//2、从集合中移除
epoll_ctl(efd, EPOLL_CTL_DEL, temp, NULL);
//3、从在线链表中移除退出登录的客户端
if(head->next==NULL)
{
break;
}
else
{
del_list(head,temp);
break;
}
}
if(pk.type == REG)//注册请求
{
deal_reg_request(temp, efd,head);
}
else if (pk.type == LOG) //登录请求
{
deal_login_request(temp, efd,head);
}
else if (pk.type == SHOW) //显示在线客户端的请求
{
deal_show_request(temp,head);
}
else if (pk.type == COM) //读取目标客户
{
char objuser[32];
int cfd;
ret=read(temp,objuser,32); //读取目标客户的用户名
objuser[ret]='\0';
//printf("%s\n",objuser);
if(ret<0)
{
perror("read");
close(temp);
epoll_ctl(efd, EPOLL_CTL_DEL, temp, NULL);
continue;
}
struct node *t=head->next;
struct word *pnew=NULL;
while(t!=NULL)
{
if(strcmp(t->name,objuser)==0) //判断是否有目标客户
{
cfd=t->fd;
//将请求方和目标客户存储在word链表中
pnew=create_node();
pnew->fd=temp;
pnew->objfd=cfd;
pnew->next=head1;
head1=pnew;
break;
}
t=t->next;
}
}
else if (pk.type == CATE) //发送消息请求
{
struct word *d=head1;
while(d!=NULL)
{
if(d->fd == temp)
{
deal_communicate_request(temp,d->objfd,efd,head);
break;
}
d=d->next;
}
}
}
}
}
//数据库断开连接
my_close();
close(cfd);
close(sockfd);
head=free_list(head);
return 0;
}
2)client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#include "pack.h"
#include "list.h"
struct pk
{
int sockfd;
char objuser[32];
};
void hangle(int sig)
{
printf("rev sig: %d\n", sig);
}
//注册请求
void reg_request(int sockfd)
{
int ret;
struct reg r1;
printf("请输入用户名:");
scanf("%s", r1.name);
while(getchar()!='\n');
printf("请输入密码:");
scanf("%s", r1.passwd);
while(getchar()!='\n');
struct pack *p = NULL;
p = (struct pack*)malloc(sizeof(struct pack)+sizeof(struct reg));
assert(p!=NULL);
p->ver = 1;
p->type = REG; //这只type
p->len = sizeof(struct reg);
memcpy(p->data, &r1, sizeof(struct reg));
ret = write(sockfd, p, sizeof(struct pack)+sizeof(struct reg)); //将包发送给服务端
if(ret<0)
{
perror("write");
close(sockfd);
exit(-1);
}
ret = read(sockfd, p, sizeof(struct pack)); //读取服务端发来的type
if(ret<0)
{
perror("read");
close(sockfd);
exit(-1);
}
else if(ret == 0)
{
printf("服务器断开连接!\n");
close(sockfd);
exit(-1);
}
if(p->type == REG_OK)
{
printf("注册成功!\n");
}
else if(p->type == REG_FAIL)
{
printf("注册失败,用户名已经存在!\n");
}
else
{
printf("数据异常!\n");
}
free(p);
return ;
}
//登录请求
int login_request(int sockfd)
{
char name[32];
char passwd[32];
struct reg r2;
int ret;
printf("用户名:\n");
scanf("%s",r2.name);
while(getchar()!='\n');
printf("密 码:\n");
scanf("%s",r2.passwd);
while(getchar()!='\n');
struct pack *p=NULL;
p=(struct pack *)malloc(sizeof(struct pack)+sizeof(struct reg));
assert(p!=NULL);
p->ver=1;
p->type=LOG; //设置type
p->len=sizeof(struct reg);
memcpy(p->data,&r2,sizeof(struct reg));
ret = write(sockfd, p, sizeof(struct pack)+sizeof(struct reg));
if(ret<0)
{
perror("write");
close(sockfd);
exit(-1);
}
ret = read(sockfd, p, sizeof(struct pack));
if(ret<0)
{
perror("read");
close(sockfd);
exit(-1);
}
else if(ret == 0)
{
printf("服务器断开连接!\n");
close(sockfd);
exit(-1);
}
if(p->type == LOG_OK)
{
printf("登录成功!\n");
return 1;
}
else if(p->type == LOG_FAIL_NONAME)
{
printf("登录失败,用户名不存在!\n");
}
else if(p->type == LOG_FAIL_WRONGPSWD)
{
printf("登录失败,密码错误!\n");
}
else
{
printf("数据异常!\n");
}
free(p);
return 0;
}
//显示在线客户请求
void show_request(int sockfd)
{
int ret;
int num;
struct node *head = NULL;
struct pack pk,pk1;
pk.ver = 1;
pk.type = SHOW;
pk.len = 0;
ret = write(sockfd,&pk,sizeof(struct pack));
if(ret<0)
{
exit(-1);
}
ret = read(sockfd,&pk1,sizeof(struct pack)); //读取服务器发送的含有在线人数的包
if(ret<0)
{
close(sockfd);
exit(-1);
}
for(int i=0;i<pk1.len;i++)
{
struct node *pnew = NULL;
pnew=create_node();
ret = read(sockfd,pnew,sizeof(struct node)); //读取在线客户链表
if(ret<0)
{
perror("read");
close(sockfd);
exit(-1);
}
else if(ret == 0)
{
printf("服务器断开连接!\n");
close(sockfd);
exit(-1);
}
pnew->next = head;
head = pnew;
}
struct node *p = head;
printf("当前在线用户:\n");
while(p!=NULL)
{
printf("%s\n",p->name);
p=p->next;
}
return;
}
//线程1:写信息
void *pthread1(void *argv)
{
int sockfd = *((int *)argv);
char buff[1024];
int ret;
while(1)
{
int ret;
struct pack pk;
scanf("%s",buff);
pk.ver = 1;
pk.type = CATE;
pk.len = 0;
ret = write(sockfd,&pk,sizeof(struct pack));
if(ret<0)
{
exit(-1);
}
while(getchar()!='\n');
ret = write(sockfd,buff,strlen(buff));
if(ret<0)
{
perror("write");
exit(-1);
}
}
}
//线程2:读信息
void *pthread2(void *argv)
{
int ret;
struct pk temp = *((struct pk *)argv);
char buff[1024];
while(1)
{
int ret;
ret = read(temp.sockfd,buff,1024);
if(ret<0)
{
perror("read");
close(temp.sockfd);
exit(-1);
}
else if(ret == 0)
{
printf("服务器断开连接!\n");
close(temp.sockfd);
exit(-1);
}
buff[ret] = '\0';
printf("[%s]:%s\n",temp.objuser,buff);
}
}
//将选择的目标客户发送给服务器
void communicate_request(int sockfd,char objuser[])
{
int ret;
int cfd;
struct pack pk;
pk.ver = 1;
pk.type = COM;
pk.len = 0;
ret = write(sockfd,&pk,sizeof(struct pack));
if(ret<0)
{
exit(-1);
}
ret = write(sockfd,objuser,strlen(objuser));
if(ret<0)
{
exit(-1);
}
sleep(1);
return;
}
//将聊天信息发送给服务器
void communicate(int sockfd,char objuser[])
{
struct pk pp;
pp.sockfd=sockfd;
strcpy(pp.objuser,objuser);
pthread_t pth1,pth2;
printf("输入聊天信息:\n");
//加入线程,能够边读边写
if(0!=pthread_create(&pth1,NULL,pthread1,&sockfd))
{
perror("pthread_create");
exit(-1);
}
if(0!=pthread_create(&pth2,NULL,pthread2,&pp))
{
perror("pthread_create");
exit(-1);
}
pthread_join(pth1,NULL);
pthread_join(pth2,NULL);
}
int main()
{
signal(SIGPIPE, hangle); //遇到SIGPIPE信号执行hangle函数
int sockfd;
int ret;
char objuser[32];
sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建监听套接字
if(sockfd<0)
{
perror("socket");
return -1;
}
//设置地址族
struct sockaddr_in seraddr;
int addrlen = sizeof(struct sockaddr_in);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8001);
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
ret = connect(sockfd, (struct sockaddr*)&seraddr, addrlen); //请求连接
if(ret<0)
{
perror("connect");
return -1;
}
int flag=1,sel;
int flag1=1,sel1;
while(flag)
{
printf("1、注册\n");
printf("2、登录\n");
printf("0、退出\n");
printf("请输入你的选择:");
scanf("%d", &sel);
while(getchar()!='\n');
switch(sel)
{
case 1://注册
reg_request(sockfd);
break;
case 2://登录
ret=login_request(sockfd);
if(ret == 1)
{
while(flag1)
{
printf("1、显示在线用户\n");
printf("2、选择在线用户聊天\n");
printf("0、返回上一层\n");
printf("请输入你的选择:");
scanf("%d",&sel1);
while(getchar()!='\n');
switch(sel1)
{
case 1: //显示在线用户
show_request(sockfd);
break;
case 2: //选择在线用户连天
printf("请输入选择聊天的对象名称:");
scanf("%s",objuser);
while(getchar()!='\n');
communicate_request(sockfd,objuser);
sleep(2);
communicate(sockfd,objuser);
break;
case 0:
flag1=0;
break;
default:
printf("输入有误,请重新选择!\n");
break;
}
}
}
break;
case 0:
flag = 0;
break;
default:
printf("输入有误,请重新选择!\n");
break;
}
}
close(sockfd);
return 0;
}
3)list.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "list.h"
//创建结点
struct node *create_node()
{
struct node *pnew = NULL;
pnew=(struct node *)malloc(sizeof(struct node));
assert(pnew!=NULL);
pnew->next = NULL;
return pnew;
}
static struct node *search_list(struct node *head,int sockfd)
{
struct node *p = head->next;
while(p!=NULL)
{
if(p->fd == sockfd)
{
return p;
}
p = p->next;
}
}
//求链表长度
int len_list(struct node *head)
{
int len=0;
struct node *p=head->next;
while(p!=NULL)
{
len++;
p=p->next;
}
return len;
}
//删除结点
void del_list(struct node *head,int sockfd)
{
struct node *pdel = search_list(head,sockfd);
struct node *p = head;
while(p->next!=pdel)
{
p=p->next;
}
p->next=pdel->next;
free(pdel);
}
//释放链表
struct node *free_list(struct node *head)
{
struct node *pdel=NULL;
while(head!=NULL)
{
pdel=head;
head=head->next;
free(pdel);
}
return head;
}
4)mysql.c
#include "./mysql.h"
#include <stdio.h>
#define HOST "localhost"
#define USER "halfgod"
#define PASSWD "1"
#define DB "userinfo"
static MYSQL mysql;
//成功返回0, 失败返回-1
int my_init()
{
//初始化句柄
if(NULL == mysql_init(&mysql))
{
printf("%s\n", mysql_error(&mysql));
return -1;
}
//连接数据库
if(NULL == mysql_real_connect(&mysql, HOST, USER, PASSWD, DB, 0, NULL, 0))
{
printf("%s\n", mysql_error(&mysql));
return -1;
}
mysql_set_character_set(&mysql, "utf8");
return 0;
}
int my_go(char *sql, MYSQL_RES **pres)
{
if(0!= mysql_query(&mysql, sql))
{
printf("%s\n", mysql_error(&mysql));
return -1;
}
MYSQL_RES *result = NULL;
result = mysql_store_result(&mysql);
if(result != NULL)
{
if(pres != NULL)
{
*pres = result;
}
}
return 0;
}
void my_close()
{
mysql_close(&mysql);
}
2. .h文件
1)pack.h
#pragma once
//表示包头信息
struct pack
{
unsigned char ver;
unsigned char type;
unsigned int len;
char data[0];
};
//表示用户注册基本信息
struct reg
{
char name[32];
char passwd[32];
};
#define REG 11
#define REG_OK 12
#define REG_FAIL 13
#define LOG 1
#define LOG_OK 2
#define LOG_FAIL_NONAME 3
#define LOG_FAIL_WRONGPSWD 4
#define SHOW 5
#define COM 6
#define CATE 7
2)其他 .h文件
3.Makefile文件
target: ser cli
serobj = mysql.o server.o list.o
cliobj = client.o list.o
ser:$(serobj)
gcc $^ -o $@ -lmysqlclient
cli:$(cliobj)
gcc $^ -o $@ -lpthread
%.o : %.c
gcc $^ -o $@ -c
clean:
rm -rf *.o cli ser