Linux 网络编程(黑客教程小组)20 socket编程(十五)udp聊天室

服务器

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

// 名字长度包含'\0'
#define _INT_NAME (64)
// 报文最大长度,包含'\0'
#define _INT_TEXT (512)

//4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
#define CERR(fmt, ...) \
    fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
         __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)

//4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
#define CERR_EXIT(fmt,...) \
    CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
/*
 * 简单的Linux上API错误判断检测宏, 好用值得使用
 */
#define IF_CHECK(code) \
    if((code) < 0) \
        CERR_EXIT(#code)

// 发送和接收的信息体
struct umsg{
    char type;                //协议 '1' => 向服务器发送名字, '2' => 向服务器发送信息, '3' => 向服务器发送退出信息
    char name[_INT_NAME];    //保存用户名字
    char text[_INT_TEXT];    //得到文本信息,空间换时间
};

// 维护一个客户端链表信息,记录登录信息
typedef struct ucnode {
    struct sockaddr_in addr;
    struct ucnode* next;
} *ucnode_t ;


// 新建一个结点对象
static inline ucnode_t _new_ucnode(struct sockaddr_in* pa){
    ucnode_t node = calloc(sizeof(struct ucnode), 1);    
    if(NULL == node)
        CERR_EXIT("calloc sizeof struct ucnode is error. ");
    node->addr = *pa;
    return node;
}

// 插入数据,这里head默认头结点是当前服务器结点
static inline void _insert_ucnode(ucnode_t head, struct sockaddr_in* pa) {
    ucnode_t node = _new_ucnode(pa);
    node->next = head->next;
    head->next = node;    
}

// 这里是有用户登录处理
static void _login_ucnode(ucnode_t head, int sd, struct sockaddr_in* pa, struct umsg* msg) {
    _insert_ucnode(head, pa);
    head = head->next;
    // 从此之后才为以前的链表
    while(head->next){
        head = head->next;
        IF_CHECK(sendto(sd, msg, sizeof(*msg), 0, (struct sockaddr*)&head->addr, sizeof(struct sockaddr_in)));
    }
}

// 信息广播
static void _broadcast_ucnode(ucnode_t head, int sd, struct sockaddr_in* pa, struct umsg* msg) {
    int flag = 0; //1表示已经找到了
    while(head->next) {
        head = head->next;
        if((flag) || !(flag=memcmp(pa, &head->addr, sizeof(struct sockaddr_in))==0)){
            IF_CHECK(sendto(sd, msg, sizeof(*msg), 0, (struct sockaddr*)&head->addr, sizeof(struct sockaddr_in)));
        }
    }
}

// 有人退出群聊
static void _quit_ucnode(ucnode_t head, int sd, struct sockaddr_in* pa, struct umsg* msg) {
    int flag = 0;//1表示已经找到
    while(head->next) {
        if((flag) || !(flag = memcmp(pa, &head->next->addr, sizeof(struct sockaddr_in))==0)){
            IF_CHECK(sendto(sd, msg, sizeof(*msg), 0, (struct sockaddr*)&head->next->addr, sizeof(struct sockaddr_in)));
            head = head->next;
        }        
        else { //删除这个退出的用户
            ucnode_t tmp = head->next;
            head->next = tmp->next;
            free(tmp);
        }
    }        
}

// 销毁维护的对象池,没有往复杂的考虑了简单处理退出了
static void _destroy_ucnode(ucnode_t* phead) {
    ucnode_t head;
    if((!phead) || !(head=*phead)) return;    
    while(head){
        ucnode_t tmp = head->next;
        free(head);
        head = tmp;
    }    

    *phead = NULL;
}

/*
 * udp聊天室的服务器, 子进程广播信息,父进程接受信息
 */
int main(int argc, char* argv[]) {
    int sd, rt;
    struct sockaddr_in addr = { AF_INET };
    socklen_t alen = sizeof addr;
    struct umsg msg;    
    ucnode_t head;

    // 这里简单检测
    if(argc != 3) {
        fprintf(stderr, "uage : %s [ip] [port]\n", argv[0]);
        exit(-1);
    }    
    // 下面对接数据
    if((rt = atoi(argv[2]))<1024 || rt > 65535)
        CERR("atoi port = %s is error!", argv[2]);
    // 接着判断ip数据
    IF_CHECK(inet_aton(argv[1], &addr.sin_addr));
    addr.sin_port = htons(rt); //端口要采用网络字节序
    // 创建socket
    IF_CHECK(sd = socket(PF_INET, SOCK_DGRAM, 0));
    // 这里bind绑定设置的地址
    IF_CHECK(bind(sd, (struct sockaddr*)&addr, alen));
    
    //开始监听了
    head = _new_ucnode(&addr);    
    for(;;){
        bzero(&msg, sizeof msg);
        IF_CHECK(recvfrom(sd, &msg, sizeof msg, 0, (struct sockaddr*)&addr, &alen));
        msg.name[_INT_NAME-1] = msg.text[_INT_TEXT-1] = '\0';
        fprintf(stdout, "msg is [%s:%d] => [%c:%s:%s]\n", inet_ntoa(addr.sin_addr),
                    ntohs(addr.sin_port), msg.type, msg.name, msg.text);
        // 开始判断处理
        switch(msg.type) {
        case '1':_login_ucnode(head, sd, &addr, &msg);break;
        case '2':_broadcast_ucnode(head, sd, &addr, &msg);break;
        case '3':_quit_ucnode(head, sd, &addr, &msg);break;
        default://未识别的异常报文,程序把其踢走
            fprintf(stderr, "msg is error! [%s:%d] => [%c:%s:%s]\n", inet_ntoa(addr.sin_addr),
                    ntohs(addr.sin_port), msg.type, msg.name, msg.text);
            _quit_ucnode(head, sd, &addr, &msg);
            break;
        }        
    }
        
    // 这段代码是不会执行到这的, 可以加一些控制让其走到这. 看人
    close(sd);
    _destroy_ucnode(&head);    
    return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

// 名字长度包含'\0'
#define _INT_NAME (64)
// 报文最大长度,包含'\0'
#define _INT_TEXT (512)

//4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
#define CERR(fmt, ...) \
    fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
         __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)

//4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
#define CERR_EXIT(fmt,...) \
    CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
/*
 * 简单的Linux上API错误判断检测宏, 好用值得使用
 */
#define IF_CHECK(code) \
    if((code) < 0) \
        CERR_EXIT(#code)

// 发送和接收的信息体
struct umsg{
    char type;                //协议 '1' => 向服务器发送名字, '2' => 向服务器发送信息, '3' => 向服务器发送退出信息
    char name[_INT_NAME];    //保存用户名字
    char text[_INT_TEXT];    //得到文本信息,空间换时间
};

/*
 * udp聊天室的客户端, 子进程发送信息,父进程接受信息
 */
int main(int argc, char* argv[]) {
    int sd, rt;
    struct sockaddr_in addr = { AF_INET };
    socklen_t alen = sizeof addr;
    pid_t pid;
    struct umsg msg = { '1' };    

    // 这里简单检测
    if(argc != 4) {
        fprintf(stderr, "uage : %s [ip] [port] [name]\n", argv[0]);
        exit(-1);
    }    
    // 下面对接数据
    if((rt = atoi(argv[2]))<1024 || rt > 65535)
        CERR("atoi port = %s is error!", argv[2]);
    // 接着判断ip数据
    IF_CHECK(inet_aton(argv[1], &addr.sin_addr));
    addr.sin_port = htons(rt);
    // 这里拼接用户名字
    strncpy(msg.name, argv[3], _INT_NAME - 1);
    
    //创建socket 连接
    IF_CHECK(sd = socket(PF_INET, SOCK_DGRAM, 0));
    // 这里就是发送登录信息给udp聊天服务器了
    IF_CHECK(sendto(sd, &msg, sizeof msg, 0, (struct sockaddr*)&addr, alen));    
    
    //开启一个进程, 子进程处理发送信息, 父进程接收信息
    IF_CHECK(pid = fork());
    if(pid == 0) { //子进程,先忽略退出处理防止成为僵尸进程
        signal(SIGCHLD, SIG_IGN);                
        while(fgets(msg.text, _INT_TEXT, stdin)){
            if(strcasecmp(msg.text, "quit\n") == 0){ //表示退出
                msg.type = '3';
                // 发送数据并检测
                IF_CHECK(sendto(sd, &msg, sizeof msg, 0, (struct sockaddr*)&addr, alen));
                break;
            }
            // 洗唛按发送普通信息
            msg.type = '2';
            IF_CHECK(sendto(sd, &msg, sizeof msg, 0, (struct sockaddr*)&addr, alen));
        }
        // 处理结算操作,并杀死父进程
        close(sd);
        kill(getppid(), SIGKILL);
        exit(0);
    }
    // 这里是父进程处理数据的读取
    for(;;){
        bzero(&msg, sizeof msg);
        IF_CHECK(recvfrom(sd, &msg, sizeof msg, 0, (struct sockaddr*)&addr, &alen));
        msg.name[_INT_NAME-1] = msg.text[_INT_TEXT-1] = '\0';
        switch(msg.type){
        case '1':printf("%s 登录了聊天室!\n", msg.name);break;
        case '2':printf("%s 说了: %s\n", msg.name, msg.text);break;
        case '3':printf("%s 退出了聊天室!\n", msg.name);break;
        default://未识别的异常报文,程序直接退出
            fprintf(stderr, "msg is error! [%s:%d] => [%c:%s:%s]\n", inet_ntoa(addr.sin_addr),
                    ntohs(addr.sin_port), msg.type, msg.name, msg.text);
            goto __exit;
        }
    }    

__exit:    
    // 杀死并等待子进程退出
    close(sd);
    kill(pid, SIGKILL);
    waitpid(pid, NULL, -1);    

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
linux C语言 网络编程教程及源码 一、网络应用层编程 1、Linux网络编程01——网络协议入门 2、Linux网络编程02——无连接和面向连接的区别 3、Linux网络编程03——字节序和地址转换 4、Linux网络编程04——套接字 5、Linux网络编程05——C/S与B/S架构的区别 6、Linux网络编程06——UDP协议编程 7、Linux网络编程07——广播 8、Linux网络编程08——多播 9、Linux网络编程09——TCP编程之客户端 10、Linux网络编程10——TCP编程之服务器 11、Linux网络编程11——tcp、udp迭代服务器 12、Linux网络编程12——tcp三次握手、四次挥手 13、Linux网络编程13——connect()、listen()和accept()三者之间的关系 14、Linux网络编程14——I/O复用之select详解 15、Linux网络编程15——I/O复用之poll详解 16、Linux网络编程16——I/O复用之epoll详解 17、Linux网络编程17——tcp并发服务器(多进程) 18、Linux网络编程18——tcp并发服务器(多线程) 19、Linux网络编程——tcp高效并发服务器(select实现) 20、Linux网络编程——tcp高效并发服务器(poll实现) 21、Linux网络编程——tcp高效并发服务器(epoll实现) 二、网络底层编程黑客模式) 1、Linux网络编程1——啥叫原始套接字 2、Linux网络编程2——原始套接字编程 3、Linux网络编程3——原始套接字实例:MAC头分析 4、Linux网络编程4——原始套接字实例:MAC地址扫描器 5、Linux网络编程5——IP数据报格式详解 6、Linux网络编程6——TCP、UDP数据包格式详解 7、Linux网络编程7——原始套接字实例:发送UDP数据包 8、Linux网络编程8——libpcap详解 9、Linux网络编程9——libnet详解

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值