1、基于UDP的网络聊天室
服务器:
#include<myhead.h>
//定义一个结构体存储成员信息
typedef struct Node
{
struct sockaddr_in cin;
struct Node *next;
}*Linklist;
//定义一个结构体表示消息类型
typedef struct _msg
{
char code; //L:登录,C:群聊,Q:退出,S:系统消息
char name[20];
char text[128];
}msg_t;
//登录操作的函数
int do_login(int sfd,msg_t msg,Linklist head,struct sockaddr_in cin)
{
//打印登录成功
printf("%s [%s:%d]登录成功\n,",msg.name, (char*)inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
sprintf(msg.text,"----------%s登陆成功---------\n",msg.name);
//先遍历链表,将新用户加入群聊的消息发给所有人
Linklist p = head;
while(p->next != NULL)
{
p = p->next;
//给客户端发送消息
sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->cin), sizeof(p->cin));
}
//将新用户的网络信息结构体,头插入链表
Linklist temp = NULL;
if((temp = (Linklist)malloc(sizeof(struct Node*))) == NULL)
{
printf("malloc error\n");
return -1;
}
temp->cin = cin;
temp->next = NULL;
head->next = temp;
return 0;
}
void do_chat(int sfd, msg_t msg, Linklist head,struct sockaddr_in cin)
{
printf("%s [%s:%d]chat成功\n",msg.name,(char*)inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
char buf[200]="";
sprintf(buf, "%s:%s",msg.name, msg.text);
strcpy(msg.text, buf);
//遍历链表,将群聊消息发送给客户端
Linklist p = head;
while(p->next != NULL)
{
p = p->next;
//判断是否给自己发送消息
if(memcmp(&cin, &(p->cin), sizeof(cin)))
{
sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->cin), sizeof(p->cin));
}
}
return ;
}
void do_quit(int sfd,msg_t msg,Linklist head, struct sockaddr_in cin)
{
sprintf(msg.text,"---------%s已下线------\n",msg.name);
//遍历链表,是自己就将自己在链表里删除
//不是自己就发送下线通知
Linklist p=head;
Linklist del = NULL;
while(p->next != NULL)
{
if(memcmp(&(p->next->cin),&cin,sizeof(cin)))
{
//不是自己
sendto(sfd,&msg,sizeof(msg), 0, (struct sockaddr*)&(p->cin),sizeof(p->cin));
}
else
{
del = p->next;
p->next = del->next;
free(del);
del = NULL;
}
}
return ;
}
int main(int argc, const char *argv[])
{
//判断终端输入是否违规
if(argc != 3)
{
printf("请输入ip地址和端口号!!!\n");
return -1;
}
//1、创建用于通信的套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd == -1)
{
perror("socket error");
return -1;
}
//2、绑定
//填充地址信息
struct sockaddr_in sin;
sin.sin_family = AF_INET; //地址族
sin.sin_port = htons(atoi(argv[2])); //端口号
sin.sin_addr.s_addr = inet_addr(argv[1]); //ip地址
//将地址信息绑定到服务器
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error\n");
return -1;
}
//定义客户端地址信息用于接收
struct sockaddr_in cin;
socklen_t socklen = sizeof(cin);
msg_t msg; //定义接收的消息
pid_t pid = fork();
if(pid == -1)
{
perror("fork error");
return -1;
}
else if(pid > 0)
{
//父进程:用于发送系统消息
strcpy(msg.name, "*ststem*:");
msg.code = 'C'; //服务端向客户端群发消息
while(1)
{
//清空消息结构体
memset(&msg,0,sizeof(msg));
//输入要发送的消息
fgets(msg.text, sizeof(msg.text), stdin);
msg.text[strlen(msg.text)-1] = 0;
//发送消息
sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&sin, sizeof(sin));
}
}
else
{
//子进程
//创建链表头结点存储客户端信息
Linklist head=NULL;
head = (Linklist)malloc(sizeof(struct Node));
if(head == NULL)
{
printf("error\n");
return -1;
}
memset(head, 0, sizeof(head));
head->next = NULL;
while(1)
{
//清空数据和新用户信息
memset(&msg, 0, sizeof(msg));
memset(&cin, 0, sizeof(cin));
//接收客户端发送的信息
recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&cin, &socklen);
//判断消息操作码
switch(msg.code)
{
case 'L':
{
do_login(sfd, msg, head, cin);
}break;
case 'C':
{
do_chat(sfd, msg, head, cin);
}break;
case 'Q':
{
do_quit(sfd, msg, head, cin);
}break;
}
}
}
return 0;
}
客户端:
#include<myhead.h>
//定义消息类型结构体
typedef struct msg
{
char code; //操作码
char name[20];
char text[128];
}msg_t;
typedef void (*sighandler_t)(int);
void handler(int sig)
{
//回收子进程资源并退出
while(waitpid(-1, NULL, WNOHANG) > 0);
exit(0);
}
int main(int argc, const char *argv[])
{
//判断终端输入
if(argc != 3)
{
printf("请输入ip地址和端口号!\n");
return -1;
}
//注册信号处理函数,让子进程退出后,父进程回收子进程资源并退出
sighandler_t s = signal(SIGCHLD, handler);
if(s == SIG_ERR)
{
perror("signal error");
return -1;
}
//创建用于通信的套接字
int cfd = socket(AF_INET, SOCK_DGRAM, 0);
if(cfd == -1)
{
perror("socket error");
return -1;
}
//2、绑定
//填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
//清空消息内容
msg_t msg;
memset(&msg, 0, sizeof(msg));
//输入用户名
printf("请输入用户名>>>");
fgets(msg.name, sizeof(msg.name), stdin);
msg.name[strlen(msg.name)-1] = 0;
msg.code = 'L';
//给服务器发送消息
sendto(cfd, &msg,sizeof(msg),0, (struct sockaddr*)&sin,sizeof(sin));
//创建进程
pid_t pid = fork();
if(pid == -1)
{
perror("fork error");
return -1;
}
else if(pid > 0)
{
//父进程
//循环终端接受数据并发给客户端
while(1)
{
memset(msg.text, 0, sizeof(msg.text));
fgets(msg.text, sizeof(msg.text),stdin); //在终端获取聊天信息
msg.text[strlen(msg.text)-1] = 0;
//判断输入的聊天内容
if(strcmp(msg.text,"quit") == 0)
{
msg.code = 'Q';
strcpy(msg.text, "下线了");
}
else
{
msg.code = 'C';
}
sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sizeof(sin));
if(msg.code == 'Q')
exit(0);
}
}
else
{
//子进程
//循环接收并打印消息
while(1)
{
recvfrom(cfd, &msg, sizeof(msg), 0, NULL,NULL);
}
printf("[%s]:%s\n",msg.name,msg.text);
}
close(cfd);
return 0;
}