一、UDP网络编程:
1.1 流程
服务器流程:
创建用户数据报套接字
填充服务器的网络信息结构体
绑定套接字与服务器网络信息结构体
收发数据
关闭套接字
客户端流程:
创建用户数据报套接字
填充服务器的网络信息结构体
收发数据
关闭套接字
二、基于UDP的网络群聊聊天室
2.1 功能:
有新用户登录,其他在线的用户可以收到登录信息
有用户群聊,其他在线的用户可以收到群聊信息
有用户退出,其他在线的用户可以收到退出信息
服务器可以发送系统信息
提示:
客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程
服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存
数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据
2.2 流程图
2.3 代码实现
头文件:dup.h
#ifndef __UDP_H__
#define __UDP_H__#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<arpa/inet.h>
#include<signal.h>
#include<wait.h>#define N 128
#define M 32#define ERRLOG(msg) do{\printf("%s %s(%d):", __FILE__, __func__, __LINE__);\perror(msg);\exit(-1);\
}while(0)typedef struct _Node{struct sockaddr_in addr; struct _Node *next;
}node_t;typedef struct _Msg{char code;char user[M];char text[N];
}msg_t;
#endif
服务器:server.c
#include "udp.h"//创建节点的函数
int create_node(node_t **phead){*phead = (node_t *)malloc(sizeof(node_t));if(NULL == *phead){printf("内存分配失败\n");exit(-1);}(*phead)->next=NULL;return 0;
}
//尾插法
int insert_data_by_tail(node_t *phead,struct sockaddr_in addr){if(NULL == phead){printf("入参为NULL,请检查\n");return -1;}//将新客户端使用尾插法插入链表中node_t *pnew = NULL;create_node(&pnew);pnew->addr = addr; node_t *ptemp =phead;while(ptemp->next != NULL){ptemp = ptemp->next;}//让尾结点的指针域指向新节点ptemp->next = pnew;return 0;
}int main(int argc,const char *argv[]){ if(3 != argc){printf("Uage:%s <IP><port>\n",argv[0]);return -1;}int sockfd = 0;if(-1==(sockfd=socket(AF_INET,SOCK_DGRAM,0))){ERRLOG("socket error");}struct sockaddr_in serveraddr;memset(&serveraddr,0,sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_addr.s_addr = inet_addr(argv[1]);socklen_t serveraddr_len = sizeof(serveraddr);if(-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serveraddr_len)){ERRLOG("bind error");} struct sockaddr_in clientaddr,temp_clientaddr;memset(&clientaddr,0,sizeof(clientaddr));socklen_t clientaddr_len = sizeof(clientaddr);char name[32] = {0};pid_t pid = 0;msg_t msg;msg_t msg_send;//创建头结点node_t *phead;create_node(&phead);phead->addr = clientaddr;if(-1 == (pid = fork())){ERRLOG("fork error");}else if(0 == pid){ //子进程 接收数据 (1、d 登录操作 2、q 群聊操作 3、t 退出操作) while(1){memset(&msg,0,sizeof(msg));if(-1 == recvfrom(sockfd, (void*)&msg, sizeof(msg),0, (struct sockaddr *)&clientaddr,&clientaddr_len)){perror("recv error");} switch(msg.code){// 1、d 登录操作 2、q 群聊操作 3、t 退出操作 case 'd':printf("[%s]该玩家已上线\n", msg.user); insert_data_by_tail(phead,clientaddr);node_t *q=phead->next; while(q != NULL){msg.code='d';if(-1 == sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&q->addr,sizeof(q->addr))){ERRLOG("send error");}q=q->next;}break; case 'q': if(strcmp("管理员",msg.user)!=0){printf("[%s]:%s\n",msg.user, msg.text);}node_t *p = phead->next; while(p != NULL){msg.code='q';if(-1 == sendto(sockfd,(void *)&msg,sizeof(msg),0,(struct sockaddr *)&p->addr,sizeof(p->addr))){ERRLOG("send error");}p=p->next;} break; case 't': printf("[%s]:退出了...\n", msg.user);node_t *t = phead; node_t *pdel = NULL; while(t->next != NULL){msg.code='t';if( 0 == memcmp(&(t->next->addr), &clientaddr,sizeof(clientaddr))){pdel = t->next;t->next = pdel->next;free(pdel);}else{t = t->next;if(-1 == sendto(sockfd, &msg,sizeof(msg),0,(struct sockaddr *)&t->addr,sizeof(t->addr))){ERRLOG("send error");}} } break;}}}else if(0 < pid){//父进程 发送系统消息while(1){ strcpy(msg_send.user,"管理员");memset(msg_send.text,0,N);fgets(msg_send.text,N,stdin);msg_send.text[strlen(msg_send.text)-1] = '\0';msg_send.code = 'q'; if(-1 == sendto(sockfd,&msg_send,sizeof(msg_send),0,(struct sockaddr *)&serveraddr,serveraddr_len)){ERRLOG("send error"); } }} kill(pid, SIGKILL);wait(NULL);//给子进程回收资源exit(0); close(sockfd);return 0;
}
客户端:client.c
#include "udp.h"int main(int argc, const char *argv[])
{if (3 != argc){printf("Usage: %s <IP> <port>\n", argv[0]);return -1;}//创建用户数据报套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){ERRLOG("socket error");}//填充服务器网络信息结构体struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;//网络字节序的端口号 8888 9999 6789 等 都可以serveraddr.sin_port = htons(atoi(argv[2]));//网络字节序的IP地址,IP地址不能乱填//自己的主机ifconfig 查到的ip地址是多少就填多少//如果本机测试使用 也可以填写 127.0.0.1serveraddr.sin_addr.s_addr = inet_addr(argv[1]);socklen_t serveraddr_len = sizeof(serveraddr);int nbytes = 0;char name[32]={0};msg_t msg;pid_t pid;struct sockaddr_in clientaddr;memset(&clientaddr,0,sizeof(clientaddr));socklen_t clientaddr_len = sizeof(clientaddr);//输入用户名,完成登陆操作printf("请输入登录信息:");msg.code = 'd';memset(msg.user, 0, M);fgets(name, M, stdin);//在终端获取用户名strcpy(msg.user,name);msg.user[strlen(msg.user) - 1] = '\0'; //清空结尾的 '\n' if (-1 == sendto(sockfd,&msg,sizeof(msg),0, (struct sockaddr *)&serveraddr,serveraddr_len)){ //给服务器发送用户名ERRLOG("send error");}//创建进程if(-1 == (pid = fork())){ERRLOG("fork error");}else if(0 == pid){ //子进程 接收数据 while (1){memset(&msg,0,sizeof(msg));//接收服务器的应答if (-1 == (nbytes=recvfrom(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&serveraddr,&serveraddr_len))){ERRLOG("recv error");} // printf("current ------->%d\n",strcmp(msg.user,name)); if(strcmp(msg.user,name) == -10){ continue;}else{//打印应答信息switch(msg.code){case 'd':printf("[%s]登录上线了....\n", msg.user); break; case 'q':printf("[%s]:%s\n",msg.user,msg.text);break;case 't': printf("[%s]退出了....\n", msg.user); break;} } } }else if(0 < pid){//父进程 发送数据(2、q:群聊操作 3、t:退出操作)while(1){//在终端获取群聊memset(msg.text, 0, N);fgets(msg.text, N, stdin);msg.text[strlen(msg.text) - 1] = '\0'; //清空结尾的 '\n' if( 0 ==strcmp(msg.text, "quit")){msg.code = 't'; if (-1 == sendto(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&serveraddr,serveraddr_len)){ERRLOG("send error"); } break;}else{msg.code = 'q'; }//给服务器发送群聊消息if (-1 == sendto(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&serveraddr,serveraddr_len)){ERRLOG("send error");}} } //关闭套接字close(sockfd);return 0;
}