基于UDP的网络聊天室

需求:

创建基于UDP的网络聊天室服务器与客户端

服务器的功能:

  • 随时转发客户端的登录、群聊及退出请求

  • 随时发送服务器信息

客户端的功能:

  • 随时发送登录 群聊 下线的请求

  • 随时接受服务器发送的信息

代码实现过程:

SerUdpchat:

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

#define IP "192.168.1.164" 
#define PORT 6666

#define MSG_ERR(msg){\
    fprintf(stderr,"line : %d\n",__LINE__);\
    perror("msg");\
}

//重命名节点结构体数据类型

//定义结点结构体类型
typedef struct Node{
    union{
        int len;         //头结点数据域,可以不用,也可以用于存放结点长度
        struct sockaddr_in data;  //普通结点数据域,存放数据项
    };
    struct Node *next;  //指针域,指向下一个结点的结点指针
}LinkList;

socklen_t addrlen = 0;
char buf[128] = "";
ssize_t res = 0;
int sfd = 0;
LinkList *L = NULL;
LinkList *q1 = NULL;
LinkList *q2 = NULL;
LinkList *tempq = NULL;
struct sockaddr_in sein;

//数据协议
struct rule{
    char type;
    char name[32];
    char text[128];
}msg;

//创建链表
LinkList *list_create();

//链表判空
int list_ifempt(LinkList *L);

//申请结点封装数据函数
LinkList *list_NodeIn(struct sockaddr_in e);

//链表    头插入
int list_insert_head(LinkList *L,struct sockaddr_in e);

//recvfrom处理函数
void* funcR(void* arg);

//sendto处理函数
void* funcS(void* arg);

int main(int argc, const char *argv[])
{
    L = list_create();
    if(NULL == L){
        MSG_ERR(list_create);
        return -1;
    }
    //创建流式套接字
    sfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sfd < 0){
        MSG_ERR(socket);
        return -1;
    }

    //允许端口快速重用成功
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) <0)
    {
        MSG_ERR(setsockopt);
        return -1;                                                          
    }
    printf("允许端口快速重用成功\n");

    //填充服务器地址信息结构体
    sein.sin_family         = AF_INET;
    sein.sin_port         = htons(PORT);
    sein.sin_addr.s_addr = inet_addr(IP);
    addrlen = sizeof(sein);

    //绑定服务器IP和端口
    if(bind(sfd,(struct sockaddr*)&sein,sizeof(sein)) < 0){
        MSG_ERR(bind);
        return -1;
    }

    char buf[128] = "";
    ssize_t res = 0;
    struct sockaddr_in cin;//存储接收到的数据包的地址信息

    //接收信息的线程
    pthread_t tidR;
    if(pthread_create(&tidR,NULL,funcR,&cin) < 0){
        MSG_ERR(pthread_create);
        return -1;
    }

    //发送系统信息的线程
    pthread_t tidS;
    if(pthread_create(&tidS,NULL,funcS,NULL) < 0){
        MSG_ERR(pthread_create);
        return -1;
    }

    while(1);
    
    return 0;
}

//recvfrom处理函数
void* funcR(void* arg){

    //获得空的客户端地址信息结构体
    struct sockaddr_in cin = *((struct sockaddr_in*)arg);
    //计算客户端地址信息结构体的大小
    socklen_t caddrlen = sizeof(cin);
    while(1){
        bzero(&msg,sizeof(msg));
        //获得客户发送的数据包以及发送端地址信息结构体
        res = recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,&caddrlen);
        if(res < 0){
            MSG_ERR(recv);
            return NULL;
        }
        //分析数据包协议类型
        switch(msg.type){
        //登录协议
        case 'L':
            printf("[%s]已上线\n",msg.name);
            //将成功登录的客户端地址信息结构体链接到链表上
            list_insert_head(L,cin);
            //初始化头指针
            LinkList *q = L->next;
            while(q != NULL){
                //将数据包遍历转发给链表上存在的客户端
                if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(q->data),sizeof(q->data)) < 0){
                    MSG_ERR(sendto);
                }
                q=q->next;
            }
            break;
        //群聊协议
        case 'C':
            //因为发送系统消息时通过服务器转发
            //所以要判断发送端不为服务器才发送
            if(strcmp(msg.name,"*****SYSTEM*****") != 0){
                printf("[%s]说:%s\n",msg.name,msg.text);
            }
            q1 = L->next;
            while(q1 != NULL){
                //将数据包遍历转发给链表上存在的客户端
                if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(q1->data),sizeof(q1->data)) < 0){
                    MSG_ERR(sendto);
                }
                q1 = q1->next;
                }
            break;
        //退出协议
        case 'Q':
            printf("[%s]已退出\n",msg.name);
            q2 = L->next;
            //定义记录指针用于标记要输出的用户
            tempq = NULL;
            while(q2->next != NULL){
                //利用memset遍历比较用户的完整地址信息结构体,ip,端口
                //若一致,则是该用户发送的退出请求,将其删除
                if(memcmp(&(q2->next->data),&cin,sizeof(cin)) == 0){
                     tempq = q2->next;
                    q2->next = tempq->next;
                    free(tempq);
                }
                //若不是,转发退出信息给其他用户
                else{
                    q2=q2->next;
                    if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&q2->data,sizeof(q2->data)) < 0){
                        MSG_ERR(sendto);
                    }
                }
            }
            break;
        }
    }
}

//sendto处理函数
void* funcS(void* arg){
    //若沿用msg会导致发送的数据包被recvfrom处理函数一直覆盖
    //因此使用新的数据包
    struct rule msg_manager;
    msg_manager.type = 'C';
    strcpy(msg_manager.name,"*****SYSTEM*****");
//    struct sockaddr_in sin = *((struct sockaddr_in*)arg);
    while(1){
        bzero(buf,sizeof(buf));
        fgets(buf,128,stdin);
        buf[strlen(buf)-1]='\0';
        strcpy(msg_manager.text,buf);
        //将数据包发给服务器自身,通过服务器转发该数据包给所有用户
        res = sendto(sfd,&msg_manager,sizeof(msg_manager),0,(struct sockaddr*)&sein,sizeof(sein));
        if(res < 0){
            MSG_ERR(send);
            return NULL;
        }
    }
}

//创建链表
LinkList *list_create(){
    //申请一个结点的空间
    LinkList *L=(LinkList*)malloc(sizeof(LinkList));
    if(NULL==L){
        printf("申请空间失败\n");
        return NULL;
    }
    
    //结点初始化
    L->len = 0;
    L->next = NULL;    //悬空结点指针

    printf("申请空间成功\n");
    //返回链表首地址
    return L;
}

//申请结点封装数据函数
LinkList *list_NodeIn(struct sockaddr_in e){
    //申请一个结点的空间
    LinkList *p = (LinkList*)malloc(sizeof(LinkList));
    if(NULL==p){
        printf("空间申请失败\n");
        return NULL;
    }

    //结点初始化
    p->data=e;
    p->next=NULL;    
    //返回结点地址
    return p;
}

//链表    头插入
int list_insert_head(LinkList *L,struct sockaddr_in e){
    //判断逻辑
    if(NULL==L){
        printf("所给链表不合法\n");
        return 0;
    }

    //申请结点封装数据
    LinkList *p=list_NodeIn(e);

    //头插入逻辑
    p->next=L->next;     //先令后一节点挂载自己
    L->next=p;             //再令自己挂载头结点

    //表长变化
    L->len++;
    printf("插入成功\n");
    return 1;
}

CliUdpchat:

#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>

#define IP "192.168.1.164" 
#define PORT 6666

#define MSG_ERR(msg){\
    fprintf(stderr,"line : %d\n",__LINE__);\
    perror("msg");\
}

//recvfrom处理函数
void* funcR(void* arg);

//sendto处理函数
void* funcS(void* arg);

socklen_t addrlen = 0;
char buf[128] = "";
char myname[32] = "";
ssize_t res = 0;
int sfd = 0;

//数据协议
struct rule{
    char type;
    char name[32];
    char text[128];
}msg;

int main(int argc, const char *argv[])
{
    //创建流式套接字
    sfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sfd < 0){
        MSG_ERR(socket);
        return -1;
    }
    
    //填充服务器地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family         = AF_INET;
    sin.sin_port         = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(IP);
    addrlen = sizeof(sin);

    //用于接受服务器转发的信息的线程
    pthread_t tidR;
    if(pthread_create(&tidR,NULL,funcR,&sin) < 0){
        MSG_ERR(pthread_create);
        return -1;
    }
    
    //发送请求的线程
    pthread_t tidS;
    if(pthread_create(&tidS,NULL,funcS,&sin) < 0){
        MSG_ERR(pthread_create);
        return -1;
    }

    pthread_detach(tidR);
    pthread_join(tidS,NULL);

    return 0;
}

//recvfrom处理函数
void* funcR(void* arg){

    //获得服务器地址信息结构体
    struct sockaddr_in sin = *(struct sockaddr_in*)arg;
    while(1){
        //bzero(&msg,sizeof(msg));
        //获得服务器发送的数据包即服务器地址信息结构体
        res = recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen);
        if(res < 0){
            MSG_ERR(recv);
            return NULL;
        }
        //若收到的数据包是自己发送的,则不打印信息
        if(strcmp(msg.name,myname) == 0){
            continue;
        }
        else{
            //判断协议类型
            switch(msg.type){
            case 'L':
                printf("[%s]已上线\n",msg.name);
                break;
            case 'C':
                printf("[%s]说:%s\n",msg.name,msg.text);
                break;
            case 'Q':
                printf("[%s]已退出\n",msg.name);
                break;
            }
        }
    }
}

//sendto处理函数
void* funcS(void* arg){
    //获得服务器地址信息结构体
    struct sockaddr_in sin = *(struct sockaddr_in*)arg;
    //在发送群聊前发送登录协议
    bzero(&msg,sizeof(msg));
    msg.type = 'L';
    bzero(myname,sizeof(myname));
    fgets(myname,sizeof(msg.name),stdin);
    myname[strlen(myname)-1] = 0;
    strcpy(msg.name,myname);
    res = sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,addrlen);
    if(res < 0){
        MSG_ERR(send);
        return NULL;
    }
    //开始发送群聊与退出请求
    while(1){    
    //    bzero(&msg,sizeof(msg));
        bzero(buf,sizeof(buf));
        fgets(buf,32,stdin);
        buf[strlen(buf)-1]='\0';
        strcpy(msg.text,buf);
        strcpy(msg.name,myname);
        //通过判断数据内容确定是群聊还是退出请求
        if(strcmp(msg.text,"quit") == 0){
            msg.type = 'Q';
            res = sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,addrlen);
            if(res < 0){
                MSG_ERR(send);
                return NULL;
            }
            break;
        }
        else{
            msg.type = 'C';
            res = sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,addrlen);
            if(res < 0){
                MSG_ERR(send);
                return NULL;
            }
        }
    }
}

代码实现结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值