版权声明:转载请注明出处 https://blog.csdn.net/qq_37689106/article/details/84189959
总体思想:
先是创建一个链表用来存放连入服务器的sockaddr_in信息,有人登录加入链表节点,有人下线删除节点。
创建一个类似消息队列结构的结构体,用来存放用户的当前状态的类型、名字、消息。
将每个UDP客户端连接服务器后将其的sockaddr_in信息加入到链表中,通过数据的类型去判断服务器去执行哪个函数(登录广播、转发聊天信息、下线广播)。
1、UDP服务器部分
第一点 运行服务器会fork()一个子进程,该子进程用于服务器本身发送聊天的内容,父进程用于接收客户端的消息。
第二点 服务器通过接收的信息去判断类型(‘L’ ‘C’ ‘Q’)执行相应的处理函数。
第三点 处理函数 type = ‘L’ 会向当前链表中除自己以外的所有成员广播 name上线,随后将其加入到链表中。type = ‘C’ 会向当前链表中除自己以外的所有成员广播消息内容。type = ‘Q’ 首先是通知所有用户name下线,在链表中将其删除。
2、客户端部分
第一点 运行客户端程序,首先是将消息类型设置成’L’,令服务器广播告知其他成员该用户上线。
第二点 随后创建一个子进程,该进程用于发送聊天的消息,父进程用于接收服务器转发的其他用户发过来的消息。
第三点 当子进程判断输入的消息是"quit"将数据类型改成’Q’,以便服务器将其在链表中删除,并告知其他用户某人下线。
service:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define N 128
typedef struct msg
{
char type;
char name[N];
char text[N];
}msg_t;
int main(int argc, const char *argv[])
{
char buf[N] = {0};
int sockfd,ret,newsockfd;
struct sockaddr_in server,client;
pid_t pid;
msg_t msg;
memset(&msg,0,sizeof(msg)) ;
if(argc != 3)
{
printf("运行程序时带入ip 和 port\n");
exit(-1);
}
socklen_t addrlen = sizeof(client);
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
printf("sockfd=%d\n",sockfd);
memset(&server,0,sizeof(server));
server.sin_family = AF_INET ;
server.sin_port = htons( atoi(argv[2]) );
server.sin_addr.s_addr = inet_addr(argv[1]);
msg.type = 'L';
printf("input your name >:");
fgets(msg.name,N,stdin);
msg.name[strlen(msg.name) -1 ] = 0 ;
ret = sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&server,addrlen) ;
if(ret < 0)
{
perror("write");
exit(-1);
}
pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
else if(pid == 0)
{
msg.type = 'C';
while(1)
{
printf(">:");
fgets(msg.text,N,stdin);
if(strncmp(msg.text,"quit",4) == 0 )
{
msg.type = 'Q';
ret = sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&server,addrlen) ;
if(ret < 0)
{
perror("write");
exit(-1);
}
kill(getppid(),SIGUSR1);
exit(0);
}
ret = sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&server,addrlen) ;
if(ret < 0)
{
perror("write");
exit(-1);
}
}
}
else
{
while(1)
{
ret = recvfrom(sockfd,&msg,sizeof(msg),0,NULL,NULL);
if(ret < 0)
{
printf("read");
exit(-1);
}
printf("%s\n",msg.text);
}
}
close(sockfd);
return 0;
}
client:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 128
typedef struct node
{
struct sockaddr_in addr; //ip 地址
struct node * next;
}linklist_t; //链表
typedef struct msg
{
char type;
char name[N];
char text[N];
}msg_t;
linklist_t * create_empty_linkist(void)
{
linklist_t * h = (linklist_t *)malloc(sizeof(linklist_t)) ;
h->next = NULL;
memset(&h->addr,0,sizeof(h->addr));
return h;
}
int process_login(linklist_t * h,int sockfd,struct sockaddr_in *addr,msg_t *msgp)
{
linklist_t * p = h->next;
int ret ;
sprintf(msgp->text,"%s login",msgp->name);
while(p != NULL)
{
if(memcmp(&p->addr,addr,sizeof(*addr)) != 0 )
{
ret = sendto(sockfd,msgp,sizeof(msg_t),0,(struct sockaddr *)&p->addr,sizeof(p->addr));
if(ret < 0)
{
printf("sendto");
exit(-1);
}
}
p = p->next;
}
p = (linklist_t * )malloc(sizeof(linklist_t)) ; //倒叙插入法
p->next = h->next;
h->next = p ;
p->addr = *addr;
return 0;
}
int process_chat(linklist_t * h,int sockfd,struct sockaddr_in *addr,msg_t *msgp)
{
linklist_t * p = h->next;
char buf[128] = {0};
int ret ;
sprintf(buf,"%s said %s",msgp->name,msgp->text);
strcpy(msgp->text,buf);
while(p != NULL)
{
if(memcmp(&p->addr,addr,sizeof(*addr)) != 0 )
{
ret = sendto(sockfd,msgp,sizeof(msg_t),0,(struct sockaddr *)&p->addr,sizeof(p->addr));
if(ret < 0)
{
printf("sendto");
exit(-1);
}
}
p = p->next;
}
return 0;
}
int process_quit(linklist_t * h,int sockfd,struct sockaddr_in *addr,msg_t *msgp)
{
linklist_t * p = h->next;
linklist_t * q;
char buf[128] = {0};
int ret ;
sprintf(msgp->text,"%s offline",msgp->name);
while(p != NULL)
{
if(memcmp(&p->addr,addr,sizeof(*addr)) != 0 )
{
ret = sendto(sockfd,msgp,sizeof(msg_t),0,(struct sockaddr *)&p->addr,sizeof(p->addr));
if(ret < 0)
{
printf("sendto");
exit(-1);
}
}
p = p->next;
}
p = h ;
while(p->next != NULL)
{
if(memcmp(&p->next->addr,addr,sizeof(*addr)) == 0 )
{
q = p->next;
p->next = p->next->next;
free(q);
break ;
}
p = p->next;
}
return 0;
}
int main(int argc, const char *argv[])
{
char buf[N] ={0};
int sockfd,ret,newsockfd;
struct sockaddr_in myaddr,client;
pid_t pid;
msg_t msg;
memset(&msg,0,sizeof(msg));
if(argc != 3)
{
printf("运行程序时带入ip 和 port\n");
exit(-1);
}
socklen_t addrlen = sizeof(client);
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
printf("sockfd=%d\n",sockfd);
memset(&myaddr,0,sizeof(myaddr));
myaddr.sin_family = AF_INET ;
myaddr.sin_port = htons(atoi(argv[2]));
myaddr.sin_addr.s_addr = inet_addr(argv[1]);
ret = bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr)) ; // 给socket 一个固定的ip 和端口
if(ret < 0)
{
perror("bind");
exit(-1);
}
pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
else if(pid == 0 ) // 发送广播
{
msg.type = 'C';
while(1)
{
printf(">:");
fgets(msg.text,N,stdin);
strcpy(msg.name,"server");
ret = sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&myaddr,addrlen) ;
if(ret < 0)
{
perror("write");
exit(-1);
}
memset(buf,0,N);
}
}
else // parent
{
linklist_t * H = create_empty_linkist();
while(1)
{
ret = recvfrom(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&client,&addrlen);
if(ret < 0)
{
printf("read");
exit(-1);
}
printf("msg.type = %c\n",msg.type);
printf("msg.name = %s\n",msg.name);
printf("msg.text = %s\n",msg.text);
switch(msg.type)
{
case 'L':
process_login(H,sockfd,&client,&msg);
break ;
case 'C':
process_chat(H,sockfd,&client,&msg);
break ;
case 'Q':
process_quit(H,sockfd,&client,&msg);
break ;
default:
break ;
}
#if 0
ret = sendto(sockfd,buf,N,0,(struct sockaddr *)&client,addrlen);
if(ret < 0)
{
printf("sendto");
exit(-1);
}
#endif
}
}
return 0;
}