udpchat.h
#ifndef __UDPCHAT_H__
#define __UDPCHAT_H__
#define NAME_LENGTH 20
#define TEXT_LENGTH 128
#define SERVER_IP "192.168.8.249"
#define SERVER_PORT 8888
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
typedef struct _protocol
{
char type; // L 登录 C 聊天 Q 退出
char name[NAME_LENGTH];
char text[TEXT_LENGTH];
} protocol_t;
#define ERR_MSG(msg) do {\
fprintf(stderr,"line: __%d__ ",__LINE__);\
perror(msg);\
} while (0)
//使用单向链表存储客户端的地址信息
typedef struct _node
{
struct sockaddr_in cin;
struct _node *next;
} *linklist,Node;
//创建
linklist list_create();
//尾插
int list_insert_tail(linklist L,struct sockaddr_in e);
//任意位置删
int list_delete_value(linklist L,struct sockaddr_in e);
void list_show(linklist L);
#endif
01_udp_server.c
#include "udpchat.h"
//回收僵尸进程
void handler(int sig);
int main(int argc, char const *argv[])
{
//捕获17号信号,注册新的处理函数,用于回收僵尸进程
__sighandler_t s = signal(SIGCHLD,handler);
if (SIG_ERR == s)
{
printf("signal");
return -1;
}
//1.创建宝石套接字 socket
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if (sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//服务器自身的地址信息结构体,真实的地址信息结构体根据AF_INET查找
//man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SERVER_PORT);
sin.sin_addr.s_addr = inet_addr(SERVER_IP);
//2.绑定服务器的地址信息结构体 bind
if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("udp server bind success\n");
char buf[128] = {0};
ssize_t ret = 0;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
protocol_t protocol;
pid_t cpid;
//申请地址链表
linklist L = list_create();
if (NULL == L)
{
ERR_MSG("linklist create failed\n");
}
cpid = fork();
if(cpid > 0)
{
//父进程接收客户端信息,同时转发
// L 登录 1.遍历链表转发登录信息 2.将登录用户的地址信息结构体添加到链表中
// C 聊天 1.遍历链表转发的聊天信息 2.不需要发给自己
// Q 退出 1.遍历链表转发退出信息 2.删除退出的客户端节点
while (1)
{
bzero(&protocol,sizeof(protocol));
ret = recvfrom(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&cin,&addrlen);
switch (protocol.type)
{
case 'L':
printf("%s已登录...\n",protocol.name);
list_insert_tail(L,cin);
list_show(L);
//将登录消息转发其他用户
linklist p2 = L->next;
while (NULL!=p2)
{
printf("进入while循环,%d\n",__LINE__);
printf("p2->cin.sin_port = %d\n",p2->cin.sin_port);
printf("cin.sin_port = %d\n",cin.sin_port);
if (p2->cin.sin_port!=cin.sin_port)
{
if(sendto(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&(p2->cin),sizeof(p2->cin)) < 0)
{
fprintf(stderr,"%s login msg send fail\n",protocol.name);
return -1;
}
printf("%s login msg send success\n",protocol.name);
}
p2=p2->next;
}
break;
case 'C':
//将聊天消息转发其他用户
printf("开始转发聊天信息给其他用户\n");
linklist p3 = L->next;
while (NULL!=p3)
{
if (p3->cin.sin_port!=cin.sin_port)
{
if(sendto(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&(p3->cin),sizeof(p3->cin)) < 0)
{
fprintf(stderr,"%s msg send fail\n",protocol.name);
return -1;
}
printf("%s msg send success\n",protocol.name);
}
p3=p3->next;
}
break;
case 'Q':
printf("%s已退出...\n",protocol.name);
//将退出消息转发其他用户
linklist p4 = L->next;
while (NULL!=p4)
{
if (p4->cin.sin_port!=cin.sin_port)
{
if(sendto(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&(p4->cin),sizeof(p4->cin)) < 0)
{
fprintf(stderr,"%s msg send fail\n",protocol.name);
return -1;
}
printf("%s msg send success\n",protocol.name);
} else
{
//删除节点
list_delete_value(L,cin);
}
p4=p4->next;
}
break;
default:
break;
}
}
} else if(cpid == 0)
{
//子进程发送系统信息
while (1)
{
bzero(&protocol,sizeof(protocol));
strcpy(protocol.name,"administrator");
protocol.type = 'C';
fgets(protocol.text,TEXT_LENGTH,stdin);
protocol.text[strlen(protocol.text)-1] = 0;
linklist p1 = L->next;
while (NULL!=p1)
{
if(sendto(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&(p1->cin),sizeof(p1->cin)) < 0)
{
fprintf(stderr,"%s system msg send fail\n",protocol.name);
return -1;
}
printf("%s system msg send success\n",protocol.name);
p1=p1->next;
}
}
} else
{
ERR_MSG("fork");
return -1;
}
//5.关闭套接字文件描述符
close(sfd);
return 0;
}
linklist list_create()
{
linklist L = (linklist)malloc(sizeof(Node));
if (NULL == L)
{
ERR_MSG("malloc");
return NULL;
}
memset(&(L->cin),0,sizeof(L->cin));
L->next = NULL;
printf("链表初始化成功\n");
return L;
}
int list_insert_tail(linklist L,struct sockaddr_in e)
{
if (NULL == L)
{
ERR_MSG("insert fail");
return -1;
}
//遍历到链表尾部
linklist p = L;
while (NULL != p->next)
{
p = p->next;
}
//申请节点
linklist q = (linklist)malloc(sizeof(Node));
if (NULL == q)
{
ERR_MSG("node apply failed\n");
return -1;
}
q->next = NULL;
q->cin = e;
//进行尾插
q->next = p->next;
p->next = q;
printf("tail insert success\n");
return 0;
}
int list_delete_value(linklist L,struct sockaddr_in e)
{
if (NULL == L)
{
ERR_MSG("delete fail");
return -1;
}
linklist p = L->next;
//找到p的前驱节点
while (NULL!=p->next)
{
if ((e.sin_addr.s_addr == p->next->cin.sin_addr.s_addr)&&(e.sin_port == p->next->cin.sin_port))
{
break;
}
p = p->next;
}
if (NULL==p->next)
{
printf("delete node failed\n");
return -1;
}
else
{
linklist q = p->next;
p->next = q->next;
free(q);
printf("delete node success\n");
return 0;
}
}
void list_show(linklist L)
{
printf("地址链表中数据为:\n");
linklist p = L->next;
while (p!=NULL)
{
printf("[%s | %d]\n",inet_ntoa(p->cin.sin_addr),ntohs(p->cin.sin_port));
p = p->next;
}
}
void handler(int sig)
{
while (waitpid(-1,NULL,WNOHANG)>0); //循环回收僵尸进程
}
02_udp_client.c
#include "udpchat.h"
//回收僵尸进程
void handler(int sig);
int main(int argc, char const *argv[])
{
//捕获17号信号,注册新的处理函数,用于回收僵尸进程
__sighandler_t s = signal(SIGCHLD,handler);
if (SIG_ERR == s)
{
printf("signal");
return -1;
}
//1.创建报式套接字 socket
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if (sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//服务器自身的地址信息结构体,真实的地址信息结构体根据AF_INET查找
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SERVER_PORT);
sin.sin_addr.s_addr = inet_addr(SERVER_IP);
socklen_t sin_addrlen = sizeof(sin);
char buf[128] = {0};
ssize_t ret = 0;
struct sockaddr_in cin; //存储发送方的地址信息
socklen_t addrlen = sizeof(cin);
pid_t cpid;
protocol_t protocol;
memset(&protocol,0,sizeof(protocol));
//进行登录操作,输入用户名
printf("请输入用户名>>");
fgets(protocol.name,NAME_LENGTH,stdin);
protocol.name[strlen(protocol.name)-1] = 0;
//构造发送消息结构体发送给服务器
protocol.type = 'L';
//发送登录信息
if(sendto(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("client send login info to server success\n");
//登录成功后创建父子进程进行: 1.发送聊天信息,退出信息 2.
cpid = fork();
if(cpid > 0)
{
while (1)
{
bzero(protocol.text,TEXT_LENGTH);
//父进程用于发送
//1.聊天信息
//2.退出==> 输入quit 退出
fgets(protocol.text,TEXT_LENGTH,stdin);
protocol.text[strlen(protocol.text)-1] = 0;
if (0 == strcmp(protocol.text,"quit") )
{
protocol.type = 'Q';
if(sendto(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
ERR_MSG("quit msg sent to server");
return -1;
}
printf("quit msg sent to server success\n");
//结束子进程
kill(cpid,SIGKILL);
break;
} else
{
protocol.type = 'C';
}
//发送
if(sendto(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("chatting msg sent to server success\n");
}
printf("client parent process exited\n");
} else if(cpid == 0)
{
while (1)
{
//子进程用于接收服务器发来的数据
bzero(&protocol,sizeof(protocol));
ret = recvfrom(sfd,&protocol,sizeof(protocol),0,(struct sockaddr *)&sin,&sin_addrlen);
if (ret < 0)
{
ERR_MSG("recvfrom");
return -1;
}
//打印接收到的信息
switch (protocol.type)
{
case 'L':
printf("%s已登录...\n",protocol.name);
break;
case 'C':
printf("%s:%s\n",protocol.name,protocol.text);
break;
case 'Q':
printf("%s已退出...\n",protocol.name);
break;
default:
break;
}
}
} else
{
ERR_MSG("fork");
return -1;
}
//5.关闭套接字文件描述符
close(sfd);
return 0;
}
void handler(int sig)
{
while (waitpid(-1,NULL,WNOHANG)>0); //循环回收僵尸进程
}