udp群聊

1. server端维护一个链表,用于存放客户端的联系方式。结构如下:

typedef struct sockaddr_in SA ;

typedef struct client_tag
{
    SA ct_addr;
    struct client_tag* ct_next;
}CNODE, *pcNODE;2. 服务器创建一个socket端口,用于接收客户端发送的消息。消息类别分为:通知上线,通知下线,以及聊天信息。因为消息类别不同,我们使用结构体将客户端发送的消息进行如下封装:
#define TYPE_ON   1
#define TYPE_OFF  2
#define TYPE_CHAT 3
#define SIZE 1024
typedef struct msg_tag
{
    int  msg_type;
    int  msg_len;  /* 实际消息长度 */
    char msg_buf[SIZE];
}MSG, *pMSG;

注意,服务器所创建的socket端口需要绑定自己的联系方式,以便其他客户端可以发消息(sendto函数)给服务器。

3. 服务器使用select轮询函数监听自己的socket端口。当返回值为0(轮询时间内没有客户端发消息)或者-1(收到信号,出错)时,继续轮询;当返回值为1时,说明有客户端发送消息。我们可以从recvfrom函数的传出参数中获取客户端的联系方式,此时根据收到的MSG类型,进行处理。如果MSG类型为上线,则将该客户端的联系方式加入链表;如果MSG类型为下线,则将其从链表中删除;如果MSG类型为聊天信息,则服务器将其转发给所有客户端。

server端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define TYPE_ON   1
#define TYPE_OFF  2
#define TYPE_CHAT 3
#define SIZE 1024
typedef struct sockaddr_in SA ;
typedef struct msg_tag
{
    int msg_type ;
    int msg_len;
    char msg_buf[SIZE] ;
}MSG, *pMSG;
typedef struct client_tag
{
    SA ct_addr ;
    struct client_tag* ct_next ;
}CNODE, *pCNODE;

void msg_broadcast(int sockfd, char* msg, pCNODE phead)
{
    int n ;
    while(phead)
    {
        n = sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&phead -> ct_addr, sizeof(SA) );

        printf("%d: %s : %d \n", n, inet_ntoa(phead -> ct_addr.sin_addr), ntohs(phead -> ct_addr.sin_port));
        phead = phead -> ct_next ;
    }
}

void list_insert(pCNODE * phead, pCNODE p)
{
    p ->ct_next = *phead ;
    *phead = p ;
}

void list_delete(pCNODE* phead, SA* p)
{
    pCNODE pCur, pPre ;
    pPre = NULL ;
    pCur = *phead ;
    while(pCur)
    {
        if(pCur -> ct_addr.sin_port == p ->sin_port && pCur ->ct_addr.sin_addr.s_addr ==p ->sin_addr.s_addr )
        {
            break ;
        }else
        {
            pPre = pCur ;
            pCur = pCur -> ct_next ;
        }
    }
    if(pPre == NULL)
    {
        *phead = pCur -> ct_next ;
        free(pCur);
        pCur = NULL ;
    }else
    {
        pPre -> ct_next = pCur -> ct_next ;
        free(pCur);
        pCur = NULL ;
    }
}

int main(int argc, char* argv[])// EXE CONF
{
    if(argc != 2)
    {
        printf("USAGE: EXE CONF ! \n");
        exit(1);
    }
    pCNODE my_list = NULL ;
    /* 创建服务器socket端口 */
    int fd_server ;
    if((fd_server = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    /* 从配置文件中读取服务器联系方式,以便绑定到socket端口 */
    FILE* fp_conf ;
    fp_conf = fopen(argv[1], "r");
    if(fp_conf == NULL)
    {
        perror("fopen");
        exit(1);
    }
    char server_ip[32]="";
    int server_port ;
    fscanf(fp_conf,"%s%d",server_ip, &server_port);
    fclose(fp_conf);
    /* 绑定服务器socket端口的联系方式 */
    SA server_addr ;
    memset(&server_addr, 0, sizeof(SA));
    server_addr.sin_family = AF_INET ;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip);
    if(-1 ==bind(fd_server, (struct sockaddr*)&server_addr, sizeof(SA)))
    {
        perror("bind");
        close(fd_server);
        exit(1);
    }
    /* 设置select参数:监听集合以及轮询时间 */
    fd_set readset, readyset ;
    FD_ZERO(&readset);
    FD_ZERO(&readyset);
    FD_SET(fd_server, &readset);
    struct timeval tm ;

    /* 进入轮询 */
    int select_ret ;
    while(1)
    {
        readyset = readset ;
        tm.tv_sec = 0 ;
        tm.tv_usec = 1000 ;
        select_ret = select(fd_server + 1, &readyset, NULL, NULL, &tm);
        if(select_ret == 0)
        {
            continue ;
        }else if(select_ret == -1)
        {
            continue ;
        }else if(select_ret == 1)
        {
            pCNODE pNew = (pCNODE)calloc(1, sizeof(CNODE));
            int len = sizeof(SA);
            char info[1024];
            MSG my_msg ;
            memset(&my_msg, 0, sizeof(MSG));
            recvfrom(fd_server,&my_msg,sizeof(my_msg), 0, (struct sockaddr*)&(pNew ->ct_addr), &len);
                if(my_msg.msg_type == TYPE_ON)//on
                {
                    list_insert(&my_list, pNew );
                    printf("%s:%d on! \n",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port));

                }else if(my_msg.msg_type == TYPE_OFF)// off
                {
                    list_delete(&my_list, &(pNew -> ct_addr) );
                    printf("%s:%d off! \n",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port));
                    //kris add: 当客户端通知下线时,发送一条空消息给客户端,这样可以使对方孙子的recvfrom返回值为0
                    //从而可以退出循环,退出进程
                    sendto(fd_server,"",0,0,(struct sockaddr*)&(pNew->ct_addr),sizeof(SA));
                }else //send
                {
                    printf("chat msg ! \n");
                    memset(info, 0, 1024);
                    sprintf(info,"\tfrom %s:%5d:\n%s\n",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port),my_msg.msg_buf);
                    puts(info);
                    msg_broadcast(fd_server, info, my_list);
                }
        }
    }

    return 0 ;
}

client端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
typedef struct sockaddr* pSA ;
typedef struct sockaddr_in SA ;
#define TYPE_ON 1
#define TYPE_OFF 2
#define TYPE_CHAT 3
#define SIZE 1024

/* 将消息封装成结构体 */
typedef struct msg_tag
{
    int msg_type ;
    int msg_len;
    char msg_buf[SIZE] ;
}MSG, *pMSG;

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("USAGE: EXE CONF ! \n");
        exit(1);
    }
    /* 从配置文件中读取服务器的联系方式:IP及端口号 */
    FILE* fp_conf ;
    char server_ip[32]="";
    int server_port ;
    fp_conf = fopen(argv[1], "r");
    if(fp_conf == NULL)
    {
        perror("fopen");
        exit(1);
    }
    fscanf(fp_conf,"%s%d",server_ip, &server_port);
    fclose(fp_conf);

    /* 创建客户端socket */
    int fd_client ;
    if((fd_client = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    /* 存入服务器联系方式 */
    SA server_addr ;
    memset(&server_addr, 0, sizeof(SA));
    server_addr.sin_family = AF_INET ;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip);
    /* 通知服务器上线 */
    //MSG my_msg = {TYPE_ON,2,"on"} ;
    MSG my_msg = {TYPE_ON,0,""} ;
    sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));
    /* 孙子进程用于接收服务器转发的消息,并显示在屏幕上 */
    /* 当儿子进程fork出孙子后,立马会退出,从而被父进程(主程序)wait掉。
     * 从而孙子成为孤儿进程,当其退出时资源会被init所回收。
     * 实际上fork出孙子有两点原因,如下:
     * 一是不愿意父进程wait子进程,因为wait是阻塞函数。
     * 二是如果不wait,在子进程先退的情况下父进程不能回收其资源,从而先退的子进程会成为僵尸进程。
     * 现在让儿子fork出孙子后立马滚蛋,这样孙子就直接变成孤儿进程了,由init收养,并在退出时由init回收资源。
     * 注意,父进程先滚蛋其实是无碍的,大不了init做儿子(孤儿进程)的爹,并由init来回收资源。但通常父进程会模拟服务器,不会退。*/
    if(fork() == 0)
    {
        if(fork() == 0)
        {

            char msg_buf[1024];
            // recvfrom是阻塞函数,孙子进程退出程序后,会被init回收。此处
            while(memset(msg_buf, 0, 1024), recvfrom(fd_client, msg_buf, 1024, 0, NULL, NULL) > 0)
            {
                write(1,msg_buf, strlen(msg_buf));

            }
            printf("child exit ! \n");
            close(fd_client);
            exit(0);
            /*  用于说明爷爷退了之后,孙子不会被一起带走。
                sleep(5);
                while(1)
                {
                    printf("hahahahaha\n");
                }
            */
        }
        close(fd_client);
        exit(0);
    }
    wait(NULL);
    /* 从键盘输入消息,发送给服务器,按ctrl+D退出循环 */
    while(memset(&my_msg, 0, sizeof(MSG)), fgets(my_msg.msg_buf, SIZE, stdin) != NULL)
    {
        my_msg.msg_type = TYPE_CHAT ;
        my_msg.msg_len = strlen(my_msg.msg_buf);
        sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));
    }
    /* 向服务器发送离线消息 */
    my_msg.msg_type = TYPE_OFF ;
    my_msg.msg_len = 0 ;
    sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));    

    close(fd_client);

    return 0 ;
    /*注意:只要主进程滚蛋了,马上会显示出shell界面。但是此时,fork出来的进程可能并没有结束。
     *fork出来的进程先滚蛋,是不会显示shell界面的。只有主进程滚蛋,才会显示出shell界面。     */
}
时间: 2024-11-12 17:43:39

udp群聊的相关文章

Java UDP使用Socket进行网络聊天(2)之群聊版

作者 : 卿笃军 原文地址:http://blog.csdn.net/qingdujun/article/details/39312241 本文演示,使用Socket进行网络聊天之群聊,实现客户端给"局域网"网段里面的所以机器发送广播,当发送“886”的时候,表示客户端关闭. 1)客户端,给服务器发送数据,发送“886”表示关闭客户端. 2)服务器,一直监听9527端口,将监听到的数据打印在控制台上. 3)客户端+服务器版本,实现既可以发送数据,又可以接受数据的多线程聊天程序. 需要注

Java 使用Socket进行网络聊天(2)之群聊版

作者 : 卿笃军 原文地址:http://blog.csdn.net/qingdujun/article/details/39312241 本文演示,使用Socket进行网络聊天之群聊,实现客户端给"局域网"网段里面的所以机器发送广播,当发送"886"的时候,表示客户端关闭. 1)客户端,给服务器发送数据,发送"886"表示关闭客户端. 2)服务器,一直监听9527端口,将监听到的数据打印在控制台上. 3)客户端+服务器版本,实现既可以发送数据,

Java套接字编程实现群聊与私聊[原理版]

简介 运用Java套接字我们几乎可以完成一个网络聊天软件的小产品,本文不涉及UI部分,仅对原理部分用代码演示一下.一个可以多人聊天的小功能,在Linux系统上用telnet亲测可用. 服务器代码 package demo0811.demo3; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.

基于itchat的微信群聊小助手基础开发(一)

前段时间由于要管理微信群,基于itchat开发了一个简单的微信机器人 主要功能有: 图灵机器人功能 群聊昵称格式修改提示 消息防撤回功能 斗图功能 要开发一个基于itchat的最基本的聊天机器人,在github上作者已经给出基本的操作,本博文只对附加的功能进行说明总结. 确保已经了解itchat的基本操作,先对功能1和功能2进行说明: 1.图灵机器人功能 该功能也是最基础的功能之一,itchat的基础案例也有相关例子. 图灵机器人简单而言就是以一定的规则给图灵的服务器发送数据包,图灵的服务器会以

实现了私聊和群聊功能的聊天工具

在前面的博客(简单的C/S聊天室)中,我们已经提到了,采用的是多线程的方法.服务器端主线程负责不断的侦听端口,子线程负责接收和发送消息.客户端主线程需要接收键盘消息,将其发送到服务器端,子线程需要接收服务器端发过来的消息.在这个简易的C/S聊天室的实现中,仅仅实现了群聊的功能,没有实现私聊.那么,本文就讲实现私聊和群聊. 首先我们想到的是,消息发过来,我怎么知道是公聊消息还是私聊消息呢.所以,这里需要对消息进行处理,比如说在消息前后都加上一些特殊的字符,我们称为协议字符.为此,我们可以定义一个接

如何解决群聊(MUC)聊天室重复存储、接收自己发送的消息的问题

CHENYILONG Blog 如何#解决方案#群聊(MUC)聊天室重复存储.接收自己发送的消息 编号 项目 描述 1 问题描述 单聊没问题,群聊会出现自动回复的问题 数据库中存储的数据出现的问题 界面上出现的问题:类似自动回复.回音壁一样一模一样地回答.  2 问题产生的原因 3 群聊基本的原理示意图 聊天内容的显示是经由从数据库进行的读取排序, 4 #解决方案# 拦截阻挡红色区域的执行  5 失败的尝试:尝试但是没有效果的方法 // AppDelegate.m中#pragma 接收消息代理监

Lync Server 2010部署群聊服务器

最近有网友问到Lync Server 2010部署群聊服务器,在此把部署文档发出来供参考. 下载链接:http://down.51cto.com/data/2256828

Java--&gt;实现群聊功能(C/S模式--TCP协议)

--> Java 对TCP协议的支持: --> java.net包中定义了两个类ServerSocket 和Socket ,分别用来实现双向连接的server 端和client 端. --> Client 类定义客户端 package com.dragon.java.tcpchat; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; /** * 客户端 * *

python-使用正则快速解析QQ群聊记录

使用正则表达式,对QQ群聊天记录进行解析,用于分析日期.成员等维度发言情况. 原始文本是2014-03-28 15:04:25 №┽◎Eagle(369029696) 解析之后yyyy=2014mm = 03dd = 28hh = 15mi  =04ss = 25nick = №┽◎Eagleqq = 369029696 代码如下 # -*- coding: utf-8 -*- """ zhangbo2012 http://www.cnblogs.com/zhangbo201