客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
//打印错误的宏函数
#define ERR_MSG(msg) \
do \
{ \
fprintf(stderr,"__%d__",__LINE__); \
perror(msg); \
}while(0)
#define PROT 8848 //服务器端口号
#define IP "192.168.31.213" //服务器IP地址
//保存信息结构体
typedef struct
{
char type; //协议:L登录;M普通;Q退出
char name[30]; //用户名
char msg[256]; //消息内容
}INFO;
//消息写入
INFO creat_msg(INFO info,char*name)
{
strcpy(info.name,name); //用户名存入结构体
fgets(info.msg,sizeof(info.msg),stdin);
info.msg[strlen(info.msg)-1]=0;
if(strcasecmp(info.msg,"quit")==0) //判断是否与退出消息一致
info.type='Q'; //如一致,将协议改为退出消息
else
info.type='M'; //否则,将协议改为普通消息
return info; //返回结构体
}
//接收线程
void* rcv(void*arg)
{
//存储接收到的数据包来源
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
char buf[300]="";
int sfd=*(int*)arg;
//接收
while(1)
{
bzero(buf,sizeof(buf));
if(recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen)<0)
{
ERR_MSG("recvfrom");
return NULL;
}
if(strcmp(buf,"repeat")==0) //用户名重复,则退出进程
{
printf("用户名重复\n");
exit(0);
}
printf("%s\n",buf);
}
}
//发送线程
void* sed(void*arg)
{
//填充服务器IP地址和端口号
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(PROT); //端口号转网络字节序
sin.sin_addr.s_addr =inet_addr(IP); //IP地址转网络字节序
char name[30];
int sfd=*(int*)arg;
INFO info;
//第一次输入为登录信息
bzero(&info,sizeof(info));
info.type='L';
//结构体内只填用户名信息
printf("输入用户名登录:");
fgets(name,sizeof(name),stdin);
name[strlen(name)-1]=0;
strcpy(info.name,name);
if(sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return NULL;
}
while(1)
{
//输入消息
info=creat_msg(info,name);
if(sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return NULL;
}
if(info.type=='Q') //协议为退出,则退出进程
exit(0);
}
}
int main(int argc, const char *argv[])
{
system("clear");
printf("***************聊天室****************\n\n");
//创建报式套接字
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
pthread_t tidrcv,tidsed;
//创建接收线程
if(pthread_create(&tidrcv,NULL,rcv,&sfd)!=0)
{
ERR_MSG("pthread_create");
return -1;
}
//创建发送线程
if(pthread_create(&tidsed,NULL,sed,&sfd)!=0)
{
ERR_MSG("pthread_create");
return -1;
}
pthread_join(tidrcv,NULL);
pthread_join(tidsed,NULL);
close(sfd);
return 0;
}
服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
//打印错误的宏函数
#define ERR_MSG(msg) \
do \
{ \
fprintf(stderr,"__%d__",__LINE__); \
perror(msg); \
}while(0)
#define PORT 8848 //服务器端口号
#define IP "192.168.31.213" //服务器IP地址
//保存信息结构体
typedef struct
{
char type; //协议:L登录;M普通;Q退出
char name[30]; //用户名
char msg[256]; //消息内容
}INFO;
//创建用户信息链表
typedef struct Node
{
char name[30]; //用户名
struct sockaddr_in user; //用户地址信息
struct Node* next;
}Link;
Link* list; //创建全局链表
//链表
//初始化链表
Link* list_create(Link*list)
{
list=(Link*)malloc(sizeof(Link));
if(NULL==list)
{
printf("链表创建失败\n");
exit(0);
}
list->next=NULL;
bzero(&list->name,sizeof(list->name));
bzero(&list->user,sizeof(list->user));
return list;
}
//尾插
int lstk_insert(Link*list,struct sockaddr_in cin,char* name)
{
Link*p=(Link*)malloc(sizeof(Link));
p->next=NULL;
p->user=cin;
strcpy(p->name,name);
Link* q=list;
while(q->next!=NULL)
{
q=q->next;
}
q->next=p;
return 0;
}
//删除指定链表数据
int list_del(Link*list,struct sockaddr_in cin)
{
Link*q=list;
Link*p=q->next;
while(q->next!=NULL)
{
if(p->user.sin_port==cin.sin_port) //对比端口号
{
q->next=p->next;
free(p);
p->next=NULL;
break;
}
q=q->next;
p=q->next;
}
return 0;
}
//转发消息
void transpond(int sfd,char* buf,struct sockaddr_in cin,int len)
{
Link*p=list->next;
while(p!=NULL)
{
//遍历链表成员发送,除了发送者
if(p->user.sin_port!=cin.sin_port) //对比端口号
sendto(sfd,buf,len,0,(struct sockaddr*)&(p->user),sizeof(p->user));
p=p->next;
}
}
//全体发送
void send_all(int sfd,char* buf,int len)
{
Link*p=list->next;
while(p!=NULL)
{
//遍历链表成员发送
sendto(sfd,buf,len,0,(struct sockaddr*)&(p->user),sizeof(p->user));
p=p->next;
}
}
//判断用户名是否重复
int repeat(char* name,Link*list)
{
if(list->next==NULL) //表空
return 0;
Link*q=list->next;
while(q!=NULL)
{
if(strcmp(name,q->name)==0) //判断用户名是否相同
return -1;
q=q->next;
}
return 1;
}
//登录消息
void send_log(int sfd,INFO info,struct sockaddr_in cin)
{
char buf[50]="";
int len; //字符串长度
bzero(buf,sizeof(buf));
if(repeat(info.name,list)<0) //判断用户名是否重复
{
strcpy(buf,"repeat");
//如重复发送"repeat"给发送者
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,sizeof(cin));
printf("用户名%s重复\n",info.name); //服务器终端输出重复信息
return;
}
lstk_insert(list,cin,info.name); //将用户信息存入链表
//需要转发的内容
strcat(buf,"******");
strcat(buf,info.name);
strcat(buf,"已上线******");
len=strlen(buf);
//转发消息
transpond(sfd,buf,cin,len);
}
//普通消息
void send_msg(int sfd,INFO info,struct sockaddr_in cin)
{
char buf[300];
int len;
bzero(buf,sizeof(buf));
//需要转发的内容
strcat(buf,info.name);
strcat(buf,":");
strcat(buf,info.msg);
len=strlen(buf);
//转发消息
transpond(sfd,buf,cin,len);
}
//退出消息
void send_quit(int sfd,INFO info,struct sockaddr_in cin)
{
char buf[50]="";
int len;
bzero(buf,sizeof(buf));
//需要转发的内容
strcat(buf,"******");
strcat(buf,info.name);
strcat(buf,"已下线******");
len=strlen(buf);
//转发消息
transpond(sfd,buf,cin,len);
//删除下线用户链表内信息
list_del(list,cin);
}
//服务器发送全体消息线程
void* ser_sed(void*arg)
{
int sfd=*(int*)arg;
char buf[300];
char arr[255];
int len;
bzero(buf,sizeof(buf));
scanf("%s",arr);
getchar();
strcat(buf,"server:");
strcat(buf,arr);
len=strlen(buf);
//全体发送
send_all(sfd,buf,len);
}
int main(int argc, const char *argv[])
{
system("clear");
printf("***************聊天室****************\n\n");
list=list_create(list); //初始化链表
//创建报式套接字
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;
sin.sin_family =AF_INET;
sin.sin_port =htons(PORT); //端口号转网络字节序
sin.sin_addr.s_addr =inet_addr(IP); //IP地址转网络字节序
//绑定服务器地址信息结构体
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("绑定成功\n");
//存储接收到的数据包来源
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
INFO info;
pthread_t tid;
//创建发送线程
if(pthread_create(&tid,NULL,ser_sed,&sfd)!=0)
{
ERR_MSG("pthread_create");
return -1;
}
//接收并转发消息
while(1)
{
if(recvfrom(sfd,&info,sizeof(info),0,(struct sockaddr*)&cin,&addrlen)<0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s:%d]%s:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),info.name,info.msg);
switch(info.type)
{
case 'L':
send_log(sfd,info,cin); //登录消息
break;
case 'M':
send_msg(sfd,info,cin); //普通消息
break;
case 'Q':
send_quit(sfd,info,cin); //退出消息
break;
}
}
close(sfd);
return 0;
}