项目需求:
如果有用户登录,其他用户可以收到这个人的登录信息
如果有人发送信息,其他用户可以收到这个人的群聊信息
如果有人下线,其他用户可以收到这个人的下线信息
服务器可以发送系统信息
UDPchatSer
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "line:%d ", __LINE__);\
perror(msg);\
}while(0)
#define PORT 6666
#define IP "192.168.31.123"
//定义结点结构体类型
typedef struct Node{
union{
int len; //头结点数据域,可以不用,也可以用于存放结点长度
struct sockaddr_in data; //普通结点数据域,存放数据项
};
struct Node *next; //指针域,指向下一个结点的结点指针
}LinkList;
int sfd=0;
char buf[128]="";
ssize_t res=0;
LinkList *L = NULL;
LinkList *q1 = NULL;
LinkList *q2 = NULL;
LinkList *tempq = NULL;
struct sockaddr_in sein;
//数据协议
struct rule{
char type; //L登录,C群聊,Q退出
char name[32];
char text[128];
}msg;
//创建链表
LinkList *list_create();
//链表判空
int list_ifempt(LinkList *L);
//申请结点封装数据函数
LinkList *list_NodeIn(struct sockaddr_in e);
//链表头插
int list_insert_head(LinkList *L,struct sockaddr_in e);
//recvfrom处理函数
void* funcR(void* arg);
//sendto处理函数
void* funcS(void* arg);
int main(int argc, const char *argv[])
{
//创建链表
L = list_create();
if(NULL == L)
{
ERR_MSG("list_create");
return -1;
}
//创建流式套接字
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;
}
printf("允许端口快速重用成功\n");
//填充服务器地址信息结构体
sein.sin_family = AF_INET;
sein.sin_port = htons(PORT);
sein.sin_addr.s_addr = inet_addr(IP);
socklen_t addrlen=sizeof(sein);
//绑定服务器IP和端口
if(bind(sfd,(struct sockaddr*)&sein,sizeof(sein)) < 0)
{
ERR_MSG("bind");
return -1;
}
char buf[128]="";
ssize_t res=0;
struct sockaddr_in cin;
//接收信息的线程
pthread_t tidR;
if(pthread_create(&tidR,NULL,funcR,&cin) < 0)
{
ERR_MSG("pthread_create");
return -1;
}
//发送系统信息的线程
pthread_t tidS;
if(pthread_create(&tidS,NULL,funcS,NULL) < 0)
{
ERR_MSG("pthread_create");
return -1;
}
return 0;
}
//recvfrom处理函数
void* funcR(void* arg)
{
//获得客户端地址信息结构体
struct sockaddr_in cin = *((struct sockaddr_in*)arg);
//计算客户端地址信息结构体的大小
socklen_t caddrlen = sizeof(cin);
while(1)
{
res=recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,&caddrlen);
if(res<0)
{
ERR_MSG("recvfrom");
}
//分析数据包协议类型
switch(msg.type)
{
//登录协议
case 'L':
printf("[%s]已上线\n",msg.name);
//将成功登录的客户端地址信息结构体链接到链表上
list_insert_head(L,cin);
//初始化头指针
LinkList *q = L->next;
while(q!=NULL)
{
//将数据包遍历转发给链表上存在的客户端
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(q->data),sizeof(q->data)) < 0)
{
ERR_MSG("sendto");
}
q=q->next;
}
break;
//群聊协议
case 'C':
//判断发送端是否为服务器,不为服务器则发送
if(strcmp(msg.name,"---SYSTEM---")!=0)
{
printf("[%s]说:%s\n",msg.name,msg.text);
}
q1=L->next;
while(q1!=NULL)
{
//将数据包遍历转发给链表上存在的客户端
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(q1->data),sizeof(q1->data)) < 0)
{
ERR_MSG("sendto");
}
q1=q1->next;
}
break;
//退出协议
case 'Q':
printf("[%s]已退出\n",msg.name);
q2=L->next;
//定义记录指针用于标记要输出的用户
tempq=NULL;
while(q2->next!=NULL)
{
//判断是否是该用户发出的退出请求,若是将其删除
if(memcmp(&(q2->next->data),&cin,sizeof(cin))==0)
{
tempq=q2->next;
q2->next=tempq->next;
free(tempq);
}
//若不是,转发退出信息给其他用户
else
{
q2=q2->next;
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(q2->data),sizeof(q2->data)) < 0)
{
ERR_MSG("sendto");
}
}
}
break;
}
}
}
//sendto处理函数
void* funcS(void* arg)
{
struct rule msg_addr;
msg_addr.type='C';
strcpy(msg_addr.name,"---SYSTEM---");
while(1)
{
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
strcpy(msg_addr.text,buf);
//将数据包发给服务器自身,通过服务器转发该数据包给所有用户
res = sendto(sfd,&msg_addr,sizeof(msg_addr),0,(struct sockaddr*)&sein,sizeof(sein));
if(res < 0)
{
ERR_MSG("send");
}
}
}
//创建链表
LinkList *list_create()
{
//申请一个结点的空间
LinkList *L=(LinkList*)malloc(sizeof(LinkList));
if(NULL==L)
{
printf("申请空间失败\n");
return NULL;
}
//结点初始化
L->len = 0;
L->next = NULL; //悬空结点指针
printf("申请空间成功\n");
//返回链表首地址
return L;
}
//申请结点封装数据函数
LinkList *list_NodeIn(struct sockaddr_in e)
{
//申请一个结点的空间
LinkList *p = (LinkList*)malloc(sizeof(LinkList));
if(NULL==p)
{
printf("空间申请失败\n");
return NULL;
}
//结点初始化
p->data=e;
p->next=NULL;
//返回结点地址
return p;
}
//链表头插
int list_insert_head(LinkList *L,struct sockaddr_in e)
{
//判断逻辑
if(NULL==L)
{
printf("所给链表不合法\n");
return 0;
}
//申请结点封装数据
LinkList *p=list_NodeIn(e);
//头插入逻辑
p->next=L->next; //先令后一节点挂载自己
L->next=p; //再令自己挂载头结点
//表长变化
L->len++;
printf("插入成功\n");
return 1;
}
UDPchatCli
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"line : %d",__LINE__);\
perror("msg");\
}
#define PORT 6666
#define IP "192.168.31.123"
//recvfrom处理函数
void* funcR(void* arg);
//sendto处理函数
void* funcS(void* arg);
char buf[128] = "";
char myname[32] = "";
ssize_t res = 0;
int sfd=0;
//数据协议
struct rule
{
char type;
char name[32];
char text[128];
}msg;
int main(int argc, const char *argv[])
{
//创建流式套接字
sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd < 0)
{
ERR_MSG(socket);
return -1;
}
//填充服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
socklen_t addrlen = sizeof(sin);
//用于接受服务器转发的信息的线程
pthread_t tidR;
if(pthread_create(&tidR,NULL,funcR,&sin) < 0)
{
ERR_MSG(pthread_create);
return -1;
}
//发送请求的线程
pthread_t tidS;
if(pthread_create(&tidS,NULL,funcS,&sin) < 0)
{
ERR_MSG(pthread_create);
return -1;
}
pthread_detach(tidR);
pthread_join(tidS,NULL);
return 0;
}
//recvfrom处理函数
void* funcR(void* arg)
{
//获得服务器地址信息结构体
struct sockaddr_in sin = *(struct sockaddr_in*)arg;
while(1)
{
res=recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen);
if(res<0)
{
ERR_MSG("recvfrom");
return -1;
}
//若收到的数据包是自己发送的,则不打印信息
if(strcmp(msg.name,myname) == 0){
continue;
}
else
{
//判断协议类型
switch(msg.type)
{
case 'L':
printf("[%s]已上线\n",msg.name);
break;
case 'C':
printf("[%s]说:%s\n",msg.name,msg.text);
break;
case 'Q':
printf("[%s]已退出\n",msg.name);
break;
}
}
}
}
//sendto处理函数
void* funcS(void* arg)
{
//获得服务器地址信息结构体
struct sockaddr_in sin = *(struct sockaddr_in*)arg;
//在发送群聊前发送登录协议
bzero(msg,sizeof(msg));
msg.type='L';
bzero(myname,sizeof(myname));
fgets(myname,sizeof(msg.name),stdin);
myname[strlen(myname)-1]='\0';
strcpy(myname,msg.name);
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen)<0)
{
ERR_MSG("sendto");
return -1;
}
//开始发送群聊与退出请求
while(1)
{
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
strcpy(msg.name,myname);
strcpy(msg.text,buf);
//通过判断数据内容确定群聊还是退出请求
if(strcmp(msg.text,"quit")==0)
{
msg.type='Q';
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen)<0)
{
ERR_MSG("sendto");
return -1;
}
break;
}
else
{
msg.type='C';
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen)<0)
{
ERR_MSG("sendto");
return -1;
}
}
}
}