目录
需求
项目需求:
-
如果有用户登录,其他用户可以收到这个人的登录信息
-
如果有人发送信息,其他用户可以收到这个人的群聊信息
-
如果有人下线,其他用户可以收到这个人的下线信息
-
服务器可以发送系统信息
流程图
代码
服务器
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<netinet/in.h>
#define ERR_MSG(msg) do{\
fprintf(stderr," __%d__",__LINE__);\
perror(msg);\
}while(0);
struct sockaddr_in sin1,cin;
socklen_t addrlen=sizeof(struct sockaddr_in);
//用户信息结构体
typedef struct{
char name[20];
char msg[128];
int flag;
}INFO;
int len_info=sizeof(INFO);
//客户端信息链表结构体
typedef struct usr_list{
struct sockaddr_in usr_addr;
struct usr_list *next;
}List;
List *list;
//链表初始化
void init()
{
list=(List *)malloc(sizeof(List));
list->next=NULL;
}
//链表插入(头插)
void insert_list(struct sockaddr_in usr_addr)
{
List *p=list;
List *node;
node=(List*)malloc(sizeof(List));
node->usr_addr=usr_addr;
node->next=p->next;
p->next=node;
}
//删除
void del_list(struct sockaddr_in usr_addr)
{
List *q=list;//前驱节点
List *p=list->next;//要删除的节点
while (p && p->next!=NULL)
{
if (p->usr_addr.sin_port==usr_addr.sin_port)
{
break;
}
p=p->next;
q=q->next;
}
q->next=p->next;
free(p);
}
//发送登录信息
void send_login(int sfd,INFO info,struct sockaddr_in cin)
{
if(list == NULL)
{
printf("用户列表为空\n");
}
List *p=list->next;
char buf[30]="";
strcat(buf,info.name);
strcat(buf,"<<<已登录");
printf(">>>%s\n",buf);
while (p)
{
//sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),sizeof(cin));
p=p->next;
}
insert_list(cin);
}
//发送聊天信息
void send_msg(int sfd,INFO info,struct sockaddr_in cin)
{
List *p=list->next;
char buf[128]="";
strcat(buf,info.name);
strcat(buf,":");
strcat(buf,info.msg);
buf[strlen(buf)-1]=0;
while (p)
{
if(p->usr_addr.sin_port != cin.sin_port || (p->usr_addr.sin_addr.s_addr != cin.sin_addr.s_addr))
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),sizeof(cin));
p=p->next;
}
}
//退出
void send_off(int sfd,INFO info,struct sockaddr_in cin){
List *p = list->next;
char buf[128]= {0};
strcat(buf,info.name);
strcat(buf,"退出聊天室"); //将用户昵称+Offline保存到buf缓冲区进行群发
//buf[strlen(buf)] = '\0';
printf("%s退出聊天室\n",info.name);
while(p){
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
p = p->next;
}
del_list(cin); //删除退出用户节点
}
//服务器向客户端发送信息
void serv_send(int sfd,char *buf)
{
List *p=list->next;
printf("向客服端发送消息\n");
printf("函数测试%s\n",buf);
while(p)
{
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
//sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
p=p->next;
}
}
//线程收发客户端来的消息
void *func(void *arg)
{
pthread_detach(pthread_self());
int sfd=*(int *)arg;
ssize_t res;
while(1)
{
//接受信息保存在info中
INFO info;
if ((res=recvfrom(sfd,&info,sizeof(info),0,(struct sockaddr*)&cin,&addrlen))<0)
if(res<0)
{
ERR_MSG("recvfrom");
break;
}
printf("接收来自 %s 的信息\n",info.name);
switch(info.flag)
{
case 1:
{
send_login(sfd,info,cin);
break;
}
case 2:
{
send_msg(sfd,info,cin);
break;
}
case 3:
{
send_off(sfd,info,cin);
break;
}
}
}
}
int main(int argc, const char *argv[])
{
if (argc<3)
{
fprintf(stderr,"请输入IP port\n");
return -1;
}
init();//初始化链表
//将获取到的端口字符串转换成整形
int port =atoi(argv[2]);
if (port<1024 || port >49151)
{
fprintf(stderr,"port %d input error!! 1024~49151\n",port);
return -1;
}
//创建套接字
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if (sfd<0)
{
ERR_MSG("socket");
return -1;
}
//允许端口快速复用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充服务器的IP地址以及端口号
//struct sockaddr_in sin;
sin1.sin_family =AF_INET;
sin1.sin_port =htons(atoi(argv[2]));
sin1.sin_addr.s_addr =inet_addr(argv[1]);
//绑定服务器的地址信息结构体
if(bind(sfd,(struct sockaddr*)&sin1,sizeof(sin1))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//创建子线程
pthread_t tid;
pthread_create(&tid,NULL,func,&sfd);
//struct sockaddr_in cin; //存储接收到的数据包来自哪里
//socklen_t addrlen=sizeof(cin);
//char buf[128]="";
while (1)
{
char buf[64]="";
char msg[48]="";
//strcat(buf,"system:");
strcpy(buf,"system msg:");
fgets(msg,48,stdin);
msg[strlen(msg)-1]=0;
strcat(buf,msg);
printf("test %s\n",buf);
//serv_send(sfd,buf);
List *p=list->next;
printf("向客服端发送消息\n");
//printf("函数测试%s\n",buf);
while(p)
{
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
p=p->next;
}
if(strcmp(msg,"exit")==0)
exit(0);
//memset(msg,0,48); //清空
//memset(buf,0,64); //清空
}
//关闭套接字
close(sfd);
return 0;
}
客户端
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#define ERR_MSG(msg) do{\
fprintf(stderr," __%d__",__LINE__);\
perror(msg);\
}while(0);
typedef struct{
char name[20];
char msg[128];
int flag;
}INFO;
INFO create_msg(INFO info,char *name)
{
char msg[64];
//printf("请输入>>>");
fgets(msg,sizeof(msg),stdin);
strcpy(info.name,name);
strcpy(info.msg,msg);
if (strcmp(msg,"exit\n")==0)
{
info.flag=3;//退出协议
}else
info.flag =2;
return info;
}
void *rcv_func(void *arg)//接受服务器信息线程
{
pthread_detach(pthread_self());
int sfd=*(int *)arg;
while (1)
{
char buf[256]="";
//接收服务器发送过来的包
//if(recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen)<0)
if(recvfrom(sfd,buf,sizeof(buf),0,NULL,NULL)<0)
{
ERR_MSG("recvfrom");
return NULL;
}
if (strcmp(buf,"system msg:exit")==0)
{
printf("服务器下线,聊天室关闭\n");
exit(0);
}
printf("%s\n",buf);
}
}
int main(int argc, const char *argv[])
{
INFO info;
ssize_t res;
if (argc<3)
{
fprintf(stderr,"请输入IP port\n");
return -1;
}
//将获取到的端口字符串转换成整形
int port =atoi(argv[2]);
if (port<1024 || port >49151)
{
fprintf(stderr,"port %d input error!! 1024~49151\n",port);
return -1;
}
//填充服务器的IP地址以及端口号
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(port);
sin.sin_addr.s_addr =inet_addr(argv[1]);
int len_s=sizeof(sin);
//创建套接字
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if (sfd<0)
{
ERR_MSG("socket");
return -1;
}
printf("socket success\n");
pthread_t tid;//接收线程
pthread_create(&tid,NULL,rcv_func,&sfd);
pthread_t tid2;//发送线程
pthread_create(&tid2,NULL,rcv_func,&sfd);
char name[20];
printf("请输入用户名>>>");
fgets(name,sizeof(name),stdin);
name[strlen(name)-1]=0;
strcpy(info.name,name);
info.flag=1;//登录协议
sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,len_s);
printf("***************************************************\n");
//struct sockaddr_in rcv_addrmsg; //存储接收到的数据包来自哪里
//socklen_t addrlen=sizeof(rcv_addrmsg);
char buf[64]="";
while (1)
{
//bzero(buf,sizeof(buf));
//发送
//info =create_msg(info,name);
INFO info;
char msg[64];
//printf("请输入>>>");
fgets(msg,sizeof(msg),stdin);
strcpy(info.name,name);
strcpy(info.msg,msg);
//msg[strlen(msg)-1]=0;
if (strcmp(msg,"exit\n")==0)
{
info.flag=3;//退出协议
}else
info.flag =2;
//return info;
//info.flag=2;
// printf("发送信息:%s\n",info.msg);
res=sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin));
if (res<0)
{
ERR_MSG("sendto");
return -1;
}
//printf("sendto success\n");
if (info.flag==3)
{
break;
}
memset(&info,0,sizeof(info));
//printf("[%s:%d]:%s\n",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),buf);
}
//关闭套接字
close(sfd);
return 0;
}
运行截图