实现了基于sqlite3数据库使用UDP协议完成登录注册群聊、私聊、上线下线通知、查看聊天记录功能的聊天室
关于UDP协议
UDP 协议(用户数据报协议)是建立在 IP 协议基础之上的,用在传输层的协议。UDP 提供了无连接的数据报服务。UDP 和 IP 协议一样,是不可靠的数据报服务。
1.UDP 提供无连接服务
2.UDP 缺乏可靠性支持,应用程序必须实现:确认、超时、重传、流控等
3.UDP 面向记录服务
UDP编程的一般步骤
1.UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、循环接收数据,用函数recvfrom();
5、关闭网络连接;2.UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置对方的IP地址和端口等属性;
5、发送数据,用函数sendto();
6、关闭网络连接;
流程图
sqlite3数据库
建两个表 usr表用来存储用户名和密码,用来完成注册登录操作,record表用来记录聊天信息。
服务器
/*===============================================
* 文件名称:ser.c
* 创 建 者:
* 创建日期:2023年08月28日
* 描 述:
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include <dirent.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <sqlite3.h>
#define ERRLOG(errmsg) do{\
printf("%s %s %id\n", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
typedef struct wm{
int type;
char name[30];
char destname[30];
char data[100];
}MSG;
typedef struct node{
struct wm user;
struct sockaddr_in addr;
struct node * next;
}*linklist,linknode;
typedef struct {
int fd;
int len;
struct wm his;
struct sockaddr_in hddr;
}call;
linklist list_create(){
linklist H=(linklist)malloc(sizeof(linknode));
if(NULL==H){
printf("create fail\n");
return NULL;
}
H->next=NULL;
return H;
}
void insert_linklist(MSG msg,struct sockaddr_in cliaddr,linklist p){
linklist t=p;
while(t->next!=NULL){
t=t->next;
}
linklist new=(linklist)malloc(sizeof(linknode));
new->addr=cliaddr;
new->user=msg;
new->next=NULL;
t->next=new;
return;
}
void delete_linklist(linklist p,MSG*msg){
linklist l=p;
if(p->next==NULL) {
perror("linklist NULL");
exit(-1);
}
while(l->next!=NULL){
if(strcmp(l->next->user.name,msg->name)==0)
{
if(l->next->next==NULL){
l->next=NULL;
printf("%s从链表中删除\n",msg->name);
break;
}
l->next=l->next->next;
printf("%s从链表中删除\n",msg->name);
break;
}
else
{
l=l->next;
}
}
}
void zhuce(int fd,MSG*msg,sqlite3*db,struct sockaddr_in cliaddr,int len){
char *errmsg;
char sql[200]={0};
sprintf(sql,"INSERT INTO usr VALUES('%s','%s')",msg->name,msg->data);
printf("%s\n",sql);
if( sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK)
{
sprintf(msg->data,"name %s exit",msg->name);
printf("%s\n",msg->data);
}
else
{
sprintf(msg->data,"zhuce success");
printf("%s\n",msg->data);
}
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&cliaddr,len)==-1){
perror("send error\n");
exit(-1);
}
return;
}
int denglu(int fd,MSG*msg,sqlite3*db,struct sockaddr_in cliaddr,int len,linklist p){
char *errmsg;
char sql[200]={0};
char **result;
int row;
int col;
linklist pp=p->next;
printf("%s正在登陆\n",msg->name);
sprintf(sql,"select * from usr where name = '%s' and pass = '%s'",msg->name,msg->data);
if( sqlite3_get_table(db,sql,&result,&row,&col,&errmsg)!=SQLITE_OK)
{
printf("%s",errmsg);
exit(-1);
}
else
{
if(row==0){
sprintf(msg->data,"用户名或密码错误");
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&cliaddr,len)==-1){
perror("send error\n");
exit(-1);
}
return -1;
}
else
{
sprintf(msg->data,"denglu success");
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&cliaddr,len)==-1){
perror("send error\n");
exit(-1);
}
while(pp!=NULL)
{
sprintf(msg->data,"%s上线啦\n",msg->name);
printf("%s\n",msg->data);
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr *)&pp->addr,sizeof(pp->addr)) < 0)
{
perror("sendto\n");
exit(-1);
}
pp=pp->next;
}
return 1;
}
}
}
void chat(int fd,MSG*msg,sqlite3*db,struct sockaddr_in cliaddr,int len,linklist p){
char sql[300];
char *errmsg;
char date[100]={0};
linklist q,m,n;
q=p->next;
m=p->next;
n=p->next;
if(q==NULL)
{
perror("linklist NULL");
exit(-1);
}
time_t tm;
struct tm *tmp_ptr = NULL;
time(&tm);
tmp_ptr = localtime(&tm);
sprintf (date,"%d-%02d-%02d %02d:%02d:%02d", (1900+tmp_ptr->tm_year), (1+tmp_ptr->tm_mon), tmp_ptr->tm_mday, tmp_ptr->tm_hour, tmp_ptr->tm_min, tmp_ptr->tm_sec);
sprintf(sql,"insert into record values('%s','%s','%s')",msg->name,date,msg->data);
printf("%s",date);
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK){
printf("%s\n",errmsg);
exit(-1);
}
while(q!=NULL)
{
if(strcmp(q->user.name,msg->name)==0){
q=q->next;
}
else{
if(strcmp(msg->data,"quit")==0){
sprintf(msg->data,"[%s]已离线",msg->name);
delete_linklist(p,msg);
printf("%s is quit\n",msg->name);
}else{
char temp[100]={0};
strcpy(temp,msg->data);
sprintf(msg->data,"%s\n[%s]:%s\n",date,msg->name,temp);
printf("%s\n",msg->data);
}
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr *)&q->addr,sizeof(q->addr)) < 0)
{
perror("sendto\n");
exit(-1);
}
else{
q= q->next;
}
}
}
return;
}
//2. 列数3.列的结果 4.列的名字
int hiscallback(void *arg,int num,char** value,char ** name){
call s=*(call*)arg;
sprintf(s.his.data,"%s [%s]:%s",value[1],value[0],value[2]);
sendto(s.fd,&s.his,sizeof(MSG),0,(struct sockaddr *)&(s.hddr),s.len);
return 0;
}
int history(int fd,MSG msg,sqlite3*db,struct sockaddr_in cliaddr,int len){
printf("find history\n");
call c;
c.fd=fd;
c.hddr=cliaddr;
c.len=len;
c.his=msg;
char *errmsg;
if( sqlite3_exec(db ,"select * from record", hiscallback , (void*)&c , &errmsg)!=SQLITE_OK)
{
printf("error:%s\n",errmsg);
sqlite3_free(errmsg);
exit(-1);
}
//解决阻塞
strcpy(msg.data, "*");
if(sendto(fd,&msg,sizeof(MSG),0,(struct sockaddr * )&cliaddr,len)==-1)
{
perror("fail to send");
exit(-1);
}
return 1;
}
void siliao(int fd,MSG*msg,sqlite3*db,struct sockaddr_in cliaddr,int len,linklist q){
printf("in siliao\n");
char sql[300];
char *errmsg;
char date[100]={0};
linklist m=q->next;
linklist n=q->next;
if(m==NULL){
printf("kong linklist\n");
perror("linklist");
exit(-1);
}
else{
printf("%s\n",m->user.name);
printf("链表不为空\n");
}
while(m!=NULL)
{
if(strcmp(m->user.name,msg->destname)==0){
char temp[100]={0};
strcpy(temp,msg->data);
sprintf(msg->data,"%s to %s:%s",msg->name,msg->destname,temp);
printf("%s\n",msg->data);
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr *)&m->addr,sizeof(m->addr)) < 0)
{
perror("sendto\n");
exit(-1);
}
else{ printf("send success\n");
time_t tm;
struct tm *tmp_ptr = NULL;
time(&tm);
tmp_ptr = localtime(&tm);
sprintf (date,"%d-%02d-%02d %02d:%02d:%02d", (1900+tmp_ptr->tm_year), (1+tmp_ptr->tm_mon), tmp_ptr->tm_mday, tmp_ptr->tm_hour, tmp_ptr->tm_min, tmp_ptr->tm_sec);
sprintf(sql,"insert into record values('%s','%s','%s')",msg->name,date,msg->data);
printf("%s",sql);
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg)!=SQLITE_OK){
printf("%s\n",errmsg);
exit(-1);
}
break;
}
}
else{
m=m->next;
printf("goto next\n");
}
}
if(m==NULL){
printf("destname not exit\n");
sprintf(msg->data,"...[%s] not online",msg->destname);
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr *)&cliaddr,len) < 0)
{
perror("sendto\n");
exit(-1);
}
}
}
int main(int argc, const char *argv[]){
MSG msg;
sqlite3 *db;
//入参合理性检查
if(3 != argc){
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
if(sqlite3_open("my.db",&db)!=SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(db));
return -1;
}
else{
printf("打开数据库成功\n");
}
//创建用户数据报套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == fd){
ERRLOG("socket error");
}
//填充网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
//3.绑定
if(-1 == bind(fd, (struct sockaddr *)&serveraddr, serveraddr_len)){
ERRLOG("bind error");
}
//定义结构体保存客户端的信息
//如果使用UDP时不保存客户端的网络信息结构体
//可以接受到客户端发来的数据 但是没法回信给客户端 因为sendto的后两个参数需要用到
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
linklist p=list_create();//群聊
linklist q=list_create();//私聊
while(1){
int ret =recvfrom(fd,&msg,sizeof(MSG), 0, (struct sockaddr *)&cliaddr, &len);
if(ret>0)
{
switch(msg.type)
{
case 1:
zhuce(fd,&msg,db,cliaddr,len);
break;
case 2:
if(denglu(fd,&msg,db,cliaddr,len,p)==1){
insert_linklist(msg,cliaddr,p);
insert_linklist(msg,cliaddr,q);
break;
}else{
break;}
case 3:
chat(fd,&msg,db,cliaddr,len,p);
break;
case 4:
history(fd,msg,db,cliaddr,len);
break;
case 5:
siliao(fd,&msg,db,cliaddr,len,q);
break;
defalut:
printf("recv error\n");
}
}
}
close(fd);
return 0;
}
客户端
/*===============================================
* 文件名称:cli.c
* 创 建 者:
* 创建日期:2023年08月28日
* 描 述:
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/wait.h>
#include <dirent.h>
#include <sys/stat.h>
#define Z 1 //注册
#define D 2 //登陆
#define C 3 //聊天
#define H 4 //历史
#define S 5 //悄悄话
#define ERRLOG(errmsg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
typedef struct wm{
int type;
char name[30];
char destname[30];
char data[100];
}MSG;
void zhuce(int fd,MSG*msg,struct sockaddr_in saddr,int len){
msg->type=1;
printf("\033[1;33m输入网名\n");
fgets(msg->name,30,stdin);
msg->name[strlen(msg->name)-1]='\0';
printf("\033[0;35m输入密码\n");
fgets(msg->data,100,stdin);
msg->data[strlen(msg->data)-1]='\0';
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&saddr,len)==-1)
{
perror("send 失败");
exit(-1);
}
if(recvfrom(fd,msg,sizeof(MSG),0,NULL,NULL)==-1)
{
perror("recv 失败");
exit(-1);
}
printf("%s\n",msg->data);
return;
}
int denglu(int fd,MSG*msg,struct sockaddr_in saddr,int len){
msg->type=2;
printf("\033[1;36m输入网名\n");
fgets(msg->name,30,stdin);
msg->name[strlen(msg->name)-1]='\0';
printf("\033[0;33m输入密码\n");
fgets(msg->data,100,stdin);
msg->data[strlen(msg->data)-1]='\0';
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&saddr,len)==-1)
{
perror("send 失败");
exit(-1);
}
if(recvfrom(fd,msg,sizeof(MSG),0,NULL,NULL)==-1)
{
perror("recv 失败");
exit(-1);
}
if(strncmp(msg->data,"denglu success",14)==0){
printf("\033[0;35m%s 登陆成功,欢迎回来\n",msg->name);
return 1;
}else{
printf("\033[1;33m%s\n",msg->data);
return -1;
}
}
void chat(int fd,MSG*msg,struct sockaddr_in saddr,int len){
MSG*temp;
msg->type=3;
pid_t pid= fork();
if(pid<0){
perror("fork");
exit(-1);
}
else if(pid==0)
{
while(1)
{
//接收
// memset(temp->name,0,sizeof(temp->data));
// memset(temp->data,0,sizeof(temp->data));
if(recvfrom(fd,temp,sizeof(MSG),0,NULL,NULL)==-1)
{
perror("recv 失败");
exit(-1);
}
printf("\033[1;33m%s\n",temp->data);
}
}
else{
//发送
while(1){
// memset(msg->data,0,sizeof(msg->data));
fgets(msg->data,100,stdin);
msg->data[strlen(msg->data)-1]='\0';
if(strcmp(msg->data,"quit")==0)
{
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&saddr,len)==-1)
{
perror("send 失败");
exit(-1);
}
kill(pid,SIGKILL);
wait(NULL);
exit(1);
break;
}
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&saddr,len)==-1)
{
perror("send 失败");
exit(-1);
}
}
}
close(fd);
return;
}
int history(int fd,MSG*msg,struct sockaddr_in saddr,int len){
msg->type=4;
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&saddr,len)==-1)
{
perror("历史失败");
exit(-1);
}
while(1){
if(recvfrom(fd,msg,sizeof(MSG),0,(struct sockaddr*)&saddr,&len)==-1)
{
perror("recv 失败");
exit(-1);
}
//解决阻塞
if(0==strcmp(msg->data,"*"))
{
break;
}
printf("%s\n",msg->data);
}
return 1;
}
void siliao(int fd,MSG*msg,struct sockaddr_in saddr,int len){
MSG*temp;
msg->type=5;
printf("请输入你要连接人的名字\n");
fgets(msg->destname,30,stdin);
msg->destname[strlen(msg->destname)-1]='\0';
printf("输入你想要发送的数据\n");
pid_t pid=fork();
if(pid<0){
perror("fork\n");
exit(-1);
}
else if(pid==0)
{
while(1)
{
//接收
// memset(temp->name,0,sizeof(temp->data));
// memset(temp->data,0,sizeof(temp->data));
if(recvfrom(fd,temp,sizeof(MSG),0,NULL,NULL)==-1)
{
perror("recv 失败");
exit(-1);
}
printf("\033[1;33m%s\n",temp->data);
}
}
else{
//发送
while(1){
// memset(msg->data,0,sizeof(msg->data));
fgets(msg->data,100,stdin);
msg->data[strlen(msg->data)-1]='\0';
if(strcmp(msg->data,"quit")==0)
{
kill(pid,SIGKILL);
wait(NULL);
exit(1);
break;
}
if(sendto(fd,msg,sizeof(MSG),0,(struct sockaddr * )&saddr,len)==-1)
{
perror("send 失败");
exit(-1);
}
}
}
close(fd);
return;
}
void chat_menu(int fd,MSG*msg,struct sockaddr_in saddr,int len){
int n;
while(1) {
printf("\033[1;35m🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵\n");
printf("\033[1;31m登陆成功!你来啦! %s !\n",msg->name);
printf("\033[0;35m 使用功能 \n");
printf("\033[1;36m1.加入群聊 \033[1;33m2.查看历史聊天记录\n");
printf("\033[1;34m3.加入悄悄话 \033[1;32m4.退出\n");
printf("\033[1;35m🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵\n");
int sc=scanf("%d",&n);
if(sc==-1){
perror("scanf\n");
exit(-1);
}
getchar();
switch(n)
{
case 1:
chat(fd,msg,saddr,len);
break;
case 2:
history(fd,msg,saddr,len);
break;
case 3:
siliao(fd,msg,saddr,len);
break;
case 4:
printf("\033[1;36m感谢使用,欢迎下次再来\n");
close(fd);
exit(0);
break;
default:
printf("\033[1;31m输入错误\n");
break;
}
}
return;
}
int main(int argc, const char *argv[]){
//入参合理性检查
if(3 != argc){
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
//1.创建用户数据报套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == fd){
ERRLOG("socket error");
}
//2.填充服务器的网络信息结构体
struct sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[2]));
seraddr.sin_addr.s_addr = inet_addr(argv[1]);
int len = sizeof(seraddr);
int choose;
while(1){
MSG msg;
printf("\033[1;35m🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵\n");
printf("\033[1;33m 欢迎使用聊天室系统 \n");
printf("\033[1;36m 选择功能 \n");
printf("\033[1;31m1.注册 \033[1;35m2.登陆 \033[1;32m3.退出\n");
printf("\033[1;35m🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵🌵\n");
int sc=scanf("%d",&choose);
if(sc==-1){
perror("scanf\n");
exit(-1);
}
getchar();
switch(choose)
{
case 1:
zhuce(fd,&msg,seraddr,len);
break;
case 2:
if(denglu(fd,&msg,seraddr,len)==1){
chat_menu(fd,&msg,seraddr,len);
}
break;
case 3:
printf("\033[1;34m感谢使用,欢迎下次再来\n");
close(fd);
exit(0);
default:
printf("\033[1;34m输入错了请重新输入\n");
}
}
return 0;
}
关于编译
服务器:
客户端:
运行结果图
1.注册功能:
2.登录功能,密码匹配
3.群聊功能
4.私聊功能
5.查看聊天记录功能