网络编程 : 基于UDP的网络群聊聊天室

一、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.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;
}

2.4 实现结果 

  • 23
    点赞
  • 92
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值