服务器流程:
创建用户数据报套接字
填充服务器的网络信息结构体
绑定套接字与服务器网络信息结构体
收发数据
关闭套接字
客户端流程:
创建用户数据报套接字
填充服务器的网络信息结构体
收发数据
关闭套接字
有新用户登录,其他在线的用户可以收到登录信息
有用户群聊,其他在线的用户可以收到群聊信息
有用户退出,其他在线的用户可以收到退出信息
服务器可以发送系统信息
提示:
客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程
服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存
数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据
头文件:dup.h
#ifndef __UDP_H__
#define __UDP_H__
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 \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 \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.1
serveraddr.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;
}