本文章主要讲解用UDP实现类似QQ群的功能,需要做到一个客户端发送消息,其它在线的客户端都能接收到消息
1.实现思路
在服务端由于需要连接多个客户端,可以选用链表储存客户信息,服务器接收到客户端的消息后,给链表中所有用户都发消息,其中客户端既有发送的操作也需要有接收的操作。
注意:客户端需要至少一次向服务器发送过信息,此时服务器才会记录客户端的IP+port。
2.具体代码
客户端
客户端既有发送的操作也需要有接收的操作,所以我们可以引入线程操作。
#include "head.h"
void *recv_message(void *arg)
{
int sockfd = *((int *)arg);
char buf[100]={0};
int n = 0;
while(1)
{
memset(buf,0,sizeof(buf));
n = recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
if(n < 0)
{
perror("Fail to recvfrom");
}
printf("Recv : %s\n",buf);
}
exit(EXIT_FAILURE);
}
int main(int argc, const char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(server_addr);
char buf[100]={0};
int n = 0;
pthread_t tid = 0;
int ret = 0;
if(argc != 3)
{
fprintf(stderr,"Usage : %s ip port\n",argv[0]);
return -1;
}
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
ret = pthread_create(&tid,NULL,recv_message,(void *)&sockfd);
if(ret != 0)
{
fprintf(stderr,"Fail pthread_create\n");
return -1;
}
bzero(&server_addr,addrlen);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
while(1)
{
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = '\0';
n = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&server_addr,addrlen);
if(n < 0)
{
perror("sendto");
return -1;
}
}
close(sockfd);
return 0;
}
服务端
int main(int argc, const char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(server_addr);
char buf[100]={0};
int n = 0;
if(argc != 3)
{
fprintf(stderr,"Usage : %s ip port\n",argv[0]);
return -1;
}
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
bzero(&server_addr,addrlen);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(sockfd,(struct sockaddr*)&server_addr,addrlen) < 0)
{
perror("bind");
return -1;
}
LinkNode *head = creare_empty_linklist();
while(1)
{
bzero(buf,sizeof(buf));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&addrlen);
if(n < 0)
{
perror("recvfrom");
return -1;
}
if(0 == strncmp(buf,"quit",4))
{
break;
}
printf("client IP: %s\n",inet_ntoa(client_addr.sin_addr));
printf("client port: %d\n",ntohs(client_addr.sin_port));
printf("recv_buf: %s\n",buf);
if(find_linklist(head,client_addr) == false)
{
insert_head_linklist(head,client_addr);
}
boardcast_message(sockfd,head,buf,n,client_addr);
}
return 0;
}
在服务器中因为要保存多个客户端的信息,所以我们还需要补充一个链表文件。
#include "head.h"
LinkNode *creare_empty_linklist()
{
LinkNode *head = (LinkNode *)malloc(sizeof(LinkNode));
if(NULL == head)
{
printf("Fail to malloc\n");
return NULL;
}
head->next = NULL;
return head;
}
void insert_head_linklist(LinkNode *head,DataType data){
LinkNode *node = (LinkNode *)malloc(sizeof(LinkNode));
if(NULL == node)
{
fprintf(stderr,"fail head\n");
return ;
}
memset(node,0,sizeof(LinkNode));
node->data = data;
node->next = head->next;
head->next = node;
}
bool is_empty(LinkNode *head){
return head->next == NULL ? true : false;
}
void insert_tail_linklist(LinkNode *head,DataType data){
LinkNode *node = (LinkNode *)malloc(sizeof(LinkNode));
if(node == NULL)
{
printf("error node\n");
return ;
}
memset(node,0,sizeof(LinkNode));
LinkNode *temp = NULL;
for(temp = head->next;temp->next != NULL;temp = temp->next)
{
}
node->data = data;
node->next = temp->next;
temp->next = node;
}
bool find_linklist(LinkNode *head,DataType data){
LinkNode *temp = head;
while(temp->next != NULL)
{
if(memcmp(&(temp->data),&data,sizeof(DataType)) == 0)
{
return true;
}
temp = temp->next;
}
return false;
}
bool boardcast_message(int sockfd,LinkNode *head,char *buf,int len,DataType data)
{
LinkNode *temp = head->next;
int n = 0;
while(temp != NULL)
{
n = sendto(sockfd,buf,len,0,(struct sockaddr *)&(temp->data),sizeof(DataType));
if(n < 0)
{
perror("Fail to sendto");
}
temp = temp->next;
}
}