.h文件
#ifndef __liaotian_h__
#define __liaotian_h__
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
//打印
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
//定义节点类型
typedef struct node
{
union
{
int len;
struct sockaddr_in cin;
};
struct node *next;
}linklist;
//定义消息包结构体
typedef struct msg
{
char type;
char name[20];
char text[128];
}LCQ_msg;
int insert(linklist *l,struct sockaddr_in cin,LCQ_msg msg);
int zhuanfa(linklist *l,struct sockaddr_in cin,LCQ_msg msg,int sfd);
int notice(linklist *l,LCQ_msg,int sfd);
int cliquit(linklist* l,struct sockaddr_in cin,LCQ_msg msg,int sfd);
#endif
服务器功能函数
#include "liaotian.h"
//服务器将客户端节点信息插入链表
int insert(linklist *l,struct sockaddr_in cin,LCQ_msg msg)
{
if(NULL==l)
{
printf("链表不合法\n");
return -1;
}
linklist *p=(linklist*)malloc(sizeof(linklist));
if(p==NULL)
{
printf("节点申请失败\n");
return -1;
}
//把数据放入节点
p->cin=cin;
p->next=NULL;
//头插
p->next=l->next;
l->next=p;
printf("[%s %d] %s加入聊天室\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),msg.name);
}
//服务器向客户端转发信息
int zhuanfa(linklist *l,struct sockaddr_in cin,LCQ_msg msg,int sfd)
{
linklist* q=l->next;
while(q!=NULL)
{
if(memcmp(&(q->cin),&cin,sizeof(q->cin))==0)
{
q=q->next;
continue;
}
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&q->cin,sizeof(q->cin))<0)
{
ERR_MSG("sendto");
return -1;
}
q=q->next;
}
printf("[%s %d]%s:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),msg.name,msg.text);
printf("_%s_的消息转发成功\n",msg.name);
}
//服务器向客户端发送登录信息
int notice(linklist *l,LCQ_msg msg,int sfd)
{
linklist *q=l->next;
strcpy(msg.text,"加入群聊");
while(q!=NULL)
{
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&q->cin,sizeof(q->cin))<0)
{
ERR_MSG("sendto");
return -1;
}
q=q->next;
}
}
//服务器向客户端发送某客户端退出消息,并从链表删除
int cliquit(linklist* l,struct sockaddr_in cin,LCQ_msg msg,int sfd)
{
linklist *q = l;
strcpy(msg.text,"退出群聊");
while(q->next != NULL )
{
if(memcmp(&(q->next->cin),&cin,sizeof(cin)))
{
if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(q->next->cin), sizeof(q->next->cin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
q = q->next;
}
else
{
//删除逻辑
linklist *p = q->next;
q->next = p->next;
free(p);
p = NULL;
}
}
printf("[%s %d] %s 退出聊天室\n",inet_ntoa(cin.sin_addr),cin.sin_port,msg.name);
}
服务器
#include "liaotian.h" int port,sfd; LCQ_msg msg; struct sockaddr_in cin; //存储接收到的数据包来自与哪里 socklen_t addrlen = sizeof(cin); linklist *l=NULL; //打印 #define ERR_MSG(msg) do{\ fprintf(stderr,"__%d__",__LINE__);\ perror(msg);\ }while(0) //#define PORT 8888 //1024-49151 //#define IP "192.168.31.197" //本机ip,ifconfig void* torcv(void* arg) //用来处理客户端发来的数据包 { while(1) { bzero(&msg,sizeof(msg)); //接收 if(recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,&addrlen)<0) { ERR_MSG("recvfrom"); return NULL; } //判断发过来的包的类型 if(msg.type=='L') { //调用服务器发送登录消息的函数 notice(l,msg,sfd); //调用服务器将客户端节点信息插入链表函数 insert(l,cin,msg); } else if(msg.type=='C') //调用服务器转发信息函数 zhuanfa(l,cin,msg,sfd); else if(msg.type=='Q') //调用服务器告知客户端退出信息函数 cliquit(l,cin,msg,sfd); } } void* tosnd(void* arg) //用来发送服务器系统消息 { while(1) { //发送 // printf("请输入>>>>"); fgets(msg.text,sizeof(msg.text),stdin); msg.text[strlen(msg.text)-1]=0; msg.type='C'; strcpy(msg.name,"系统消息"); linklist *q =l; while(q->next!=NULL) { q=q->next; if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&q->cin,sizeof(q->cin))<0) { ERR_MSG("sendto"); return NULL; } } } } int main(int argc, const char *argv[]) { if(argc<3) { fprintf(stderr,"请输入ip,port\n"); return -1; } //创建客户端链表 l = (linklist*)malloc(sizeof(linklist)); if(NULL==l) { printf("创建失败\n"); return -1; } //给节点初始化 l->len = 0; //头结点数据域为0 l->next = NULL;//指针域为空 //printf("创建客户端链表成功\n"); //将获取的字符串强转成整形 port = atoi(argv[2]); if(port<1024||port>49151) { fprintf(stderr,"port error\n"); return -1; } //创建报式套接字 sfd = socket(AF_INET,SOCK_DGRAM,0); if(sfd < 0 ) { ERR_MSG("socket"); return -1; } printf("socket create success\n"); //填充地址信息结构体,真实的地址信息结构体与协议族相关 //AF_INET,所以详情在man 7 ip struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(port);//网络字节序的端口号 sin.sin_addr.s_addr =inet_addr(argv[1]);//网络字节序的ip地址 //绑定服务器的地址信息结构体 if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0) { ERR_MSG("bind"); return -1; } printf("bind success\n"); pthread_t tid1,tid2; pthread_create(&tid1,NULL,torcv,NULL); pthread_create(&tid2,NULL,tosnd,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); close(sfd); return 0; }
客户端
#include "liaotian.h" int port,sfd; LCQ_msg msg; LCQ_msg msgr; void* torcv(void* arg) { while(1) { //接收 //bzero(&msg.text,sizeof(msg.text)); memset(&msgr,0,sizeof(msgr)); //接收服务器发送过来的消息包 if(recvfrom(sfd,&msgr,sizeof(msgr),0,NULL,NULL)<0) { ERR_MSG("recvfrom"); return NULL; } printf("%s: %s\n",msgr.name,msgr.text); } } void* tosnd(void* arg) { struct sockaddr_in sin=*(struct sockaddr_in*)arg; //给服务器发送数据包 while(1) { bzero(&msg.text,sizeof(msg.text)); // printf("请输入>>>"); fgets(msg.text,sizeof(msg.text),stdin); msg.text[strlen(msg.text)-1]=0; //判断发送的是数据包还是退出包 if(strcasecmp(msg.text,"quit")==0) msg.type='Q'; else msg.type='C'; //发送 if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sizeof(sin))<0) { ERR_MSG("sendto"); return NULL; } //发送完退出包后会退出客户端 if(strcasecmp(msg.text,"quit")==0) exit(0); printf("sendto success\n"); } } //#define PORT 8888 //1024-49151 //#define IP "192.168.31.197" //本机ip,ifconfig int main(int argc, const char *argv[]) { if(argc<3) { fprintf(stderr,"请输入ip,port\n"); return -1; } //将获取的字符串强转成整形 port = atoi(argv[2]); if(port<1024||port>49151) { fprintf(stderr,"port error\n"); return -1; } //创建报式套接字 sfd = socket(AF_INET,SOCK_DGRAM,0); if(sfd < 0 ) { ERR_MSG("socket"); return -1; } printf("socket create success\n"); //填充地址信息结构体,真实的地址信息结构体与协议族相关 //AF_INET,所以详情在man 7 ip struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(port);//网络字节序的端口号 sin.sin_addr.s_addr =inet_addr(argv[1]);//网络字节序的ip地址 socklen_t addrlen = sizeof(sin); printf("请先输入你的群名\n"); scanf("%s",msg.name); getchar(); msg.type='L'; //给服务器发送登录包 if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,addrlen) < 0) { ERR_MSG("sendto"); return -1; } /* //绑定服务器的地址信息结构体 if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0) { ERR_MSG("bind"); return -1; } printf("bind success\n"); */ // struct sockaddr_in sin; pthread_t tid1,tid2; pthread_create(&tid1,NULL,torcv,NULL); pthread_create(&tid2,NULL,tosnd,(void*)(&sin)); pthread_join(tid1,NULL); pthread_join(tid2,NULL); close(sfd); return 0; }
效果展示