项目:基于UDP的网络聊天室

该项目实现了一个基于UDP的聊天系统,包括服务器(UDPchatSer)和客户端(UDPchatCli)。服务器接收用户的登录、群聊和下线信息,通过链表管理在线用户,并将这些信息广播给其他用户。客户端可以发送登录请求、群聊消息和退出请求。服务器使用多线程分别处理接收和发送信息。
摘要由CSDN通过智能技术生成

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息

  1. 如果有人发送信息,其他用户可以收到这个人的群聊信息

  1. 如果有人下线,其他用户可以收到这个人的下线信息

  1. 服务器可以发送系统信息

UDPchatSer

#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 ERR_MSG(msg) do{\
    fprintf(stderr, "line:%d ", __LINE__);\
    perror(msg);\
}while(0)

#define PORT 6666               
#define IP  "192.168.31.123"  

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

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

//数据协议
struct rule{
    char type;     //L登录,C群聊,Q退出
    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)
    {
        ERR_MSG("list_create");
        return -1;
    }
    //创建流式套接字
    sfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
 
    //允许端口快速复用成功
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) <0)
    {
        ERR_MSG("setsockopt");
        return -1;                                                          
    }
    printf("允许端口快速重用成功\n");
 
    //填充服务器地址信息结构体
    sein.sin_family      = AF_INET;
    sein.sin_port        = htons(PORT);
    sein.sin_addr.s_addr = inet_addr(IP);
    socklen_t addrlen=sizeof(sein);

    //绑定服务器IP和端口
    if(bind(sfd,(struct sockaddr*)&sein,sizeof(sein)) < 0)
    {
        ERR_MSG("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)
    {
        ERR_MSG("pthread_create");
        return -1;
    }

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

//recvfrom处理函数
void* funcR(void* arg)
{
    //获得客户端地址信息结构体
    struct sockaddr_in cin = *((struct sockaddr_in*)arg);
    //计算客户端地址信息结构体的大小
    socklen_t caddrlen = sizeof(cin);
    while(1)
    {
        res=recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,&caddrlen);
        if(res<0)
        {
            ERR_MSG("recvfrom");
        }
        //分析数据包协议类型
        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)
                {
                    ERR_MSG("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)
                {
                    ERR_MSG("sendto");
                }
                q1=q1->next;
            }
            break;
        //退出协议
        case 'Q':
            printf("[%s]已退出\n",msg.name);
            q2=L->next;
            //定义记录指针用于标记要输出的用户
            tempq=NULL;
            while(q2->next!=NULL)
            {
                //判断是否是该用户发出的退出请求,若是将其删除
                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)
                    {
                        ERR_MSG("sendto");
                    }
                }
            }
            break;
        }
    }
}

//sendto处理函数
void* funcS(void* arg)
{
    struct rule msg_addr;
    msg_addr.type='C';
    strcpy(msg_addr.name,"---SYSTEM---");
    while(1)
    {
        bzero(buf,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1]='\0';
        strcpy(msg_addr.text,buf);
        //将数据包发给服务器自身,通过服务器转发该数据包给所有用户
        res = sendto(sfd,&msg_addr,sizeof(msg_addr),0,(struct sockaddr*)&sein,sizeof(sein));
        if(res < 0)
        {
            ERR_MSG("send");
        }
    }
}

//创建链表
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;
}

UDPchatCli

#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 ERR_MSG(msg) do{\
    fprintf(stderr,"line : %d",__LINE__);\
    perror("msg");\
}

#define PORT 6666
#define IP  "192.168.31.123"

//recvfrom处理函数
void* funcR(void* arg);
 
//sendto处理函数
void* funcS(void* arg);
 
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)
    {
        ERR_MSG(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);
    socklen_t addrlen = sizeof(sin);
    
    //用于接受服务器转发的信息的线程
    pthread_t tidR;
    if(pthread_create(&tidR,NULL,funcR,&sin) < 0)
    {
        ERR_MSG(pthread_create);
        return -1;
    }
    
    //发送请求的线程
    pthread_t tidS;
    if(pthread_create(&tidS,NULL,funcS,&sin) < 0)
    {
        ERR_MSG(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)
    {
        res=recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen);
        if(res<0)
        {
            ERR_MSG("recvfrom");
            return -1;
        }
        //若收到的数据包是自己发送的,则不打印信息
        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(myname,msg.name);
    if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen)<0)
    {
        ERR_MSG("sendto");
        return -1;
    }
    //开始发送群聊与退出请求
    while(1)
    {
        bzero(buf,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1]='\0';
        strcpy(msg.name,myname);
        strcpy(msg.text,buf);
        //通过判断数据内容确定群聊还是退出请求
        if(strcmp(msg.text,"quit")==0)
        {
            msg.type='Q';
            if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen)<0)
            {
                ERR_MSG("sendto");
                return -1;
            }
            break;
        }
        else
        {
            msg.type='C';
            if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,&addrlen)<0)
             {
                ERR_MSG("sendto");
                return -1;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值