项目需求

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发消息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线了,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

开发步骤

1.流程图(一个大致的思路和想法)

2.根据流程图写框架(像做填空题一样,先写出题目,后去填充)

3.逐步实现每个功能(会发现流程图中的错误,回过头去补充完善)

最初的流程图

功能实现后修改完善的流程图

聊天室服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#define ERR_LOG(msg) do{\
    perror(msg);\
    printf("%d %s %s\n",__LINE__,__func__,__FILE__);\
}while(0)
#define USRNLEN 10
#define DATALEN 245
#define BUFFLEN 256
#define DATELEN 30
typedef struct client
{
    char uname[USRNLEN];
    char usrIP[32];
    unsigned short usrPORT;
    struct client* next;
}USR;
//全局变量
int sfd;
struct sockaddr_in saddr;
struct sockaddr_in caddr;
socklen_t saddr_len;
socklen_t caddr_len;
USR* usrclints;
char name[USRNLEN];
char serverIP[32];
char serverPORT[16];
char date[DATELEN]; 
time_t t;
struct tm *tm;
//登录下线消息
char usrname[USRNLEN];
char buff[BUFFLEN];
//转发数据消息
char usrname_tsend[USRNLEN];
char chatdata_tsend[DATALEN];
char buff_tsend[BUFFLEN];
//系统消息
char usrname_tsyssend[USRNLEN];
char chatdata_tsyssend[DATALEN];
char buff_tsyssend[BUFFLEN];
//函数声明
USR* create_usr();
USR* insert_usr(USR* head,char* buf,struct sockaddr_in addr);
USR* delete_usr(USR* head,struct sockaddr_in addr);
void printcli(USR* head);
void transmit(USR* head,char* buf,int size,struct sockaddr_in curaddr);
void systransmit(USR* head,char* buf,int size);
//线程2:发送系统消息
void* handler(void* arg)
{
    pthread_detach(pthread_self());
    while(1)
    {
        bzero(usrname_tsyssend,USRNLEN);
        bzero(chatdata_tsyssend,DATALEN);
        bzero(buff_tsyssend,BUFFLEN);
        strcpy(usrname_tsyssend,"SERVER");
        fgets(chatdata_tsyssend,DATALEN,stdin);
        chatdata_tsyssend[strlen(chatdata_tsyssend)-1] = 0;
        buff_tsyssend[0] = 7;
        strcat(buff_tsyssend+1,usrname_tsyssend);
        strcat(buff_tsyssend+11,chatdata_tsyssend);
        systransmit(usrclints,buff_tsyssend,11+strlen(chatdata_tsyssend));
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    if(argc < 3)
    {
        printf("请输入本地的IP和PORT\n");
        return 0;
    }
    //1、创建数据报式socket套接字
    sfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sfd < 0)
    {
        ERR_LOG("socket");
        return -1;
    }
    //2.绑定本地ip和端口号
    strcpy(serverPORT,argv[2]);
    strcpy(serverIP,argv[1]);
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(*(unsigned short*)argv[2]);
    //inet_aton(argv[1],&saddr.sin_addr);
    inet_pton(AF_INET,argv[1],&saddr.sin_addr.s_addr);
    //saddr.sin_addr.s_addr = inet_addr(argv[1]);
    if(bind(sfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind");
        return -1;
    }
    //3.创建Client对象
    usrclints = create_usr();
    //初始化变量
    caddr_len = sizeof(caddr);
    saddr_len = sizeof(saddr);
    //4.创建2个线程
    pthread_t tid;
    if(pthread_create(tid,NULL,handler,NULL) != 0)
    {
        ERR_LOG("pthread_create");
        return -1;
    }
    printf("服务器启动成功\n");
    //线程1:处理用户登录下线 & 接收转发用户聊天消息
    while(1)
    {
        bzero(buff,BUFFLEN);
        if(recvfrom(sfd,buff,BUFFLEN,0,(struct sockaddr*)&caddr,&caddr_len) < 0)
        {
            ERR_LOG("recvfrom");
            break;
        }
        if(1 == buff[0])//注册登录
        {
            //插入链表
            USR* cli = insert_usr(usrclints,buff,caddr);
            //打印客户端登录信息
            printf(">>");
            time(&t); 
            tm = localtime(&t);
            strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm); 
            printf("%s\n",date);
            printf("增加用户%s:[%s:%d]到链表成功\n",cli->uname,cli->usrIP,cli->usrPORT);    
            printf("***当前所有在线用户列表***\n");//test
            printcli(usrclints);//test
            printf("**************************\n\n");//test
            //发送ACK
            bzero(buff,BUFFLEN);
            buff[0] = 4;
            if(sendto(sfd,buff,1,0,(struct sockaddr*)&caddr,saddr_len) < 0)
            {
                ERR_LOG("sendto");
                break;
            }
            //转发用户登录信息给其他客户端
            bzero(buff,BUFFLEN);
            strcpy(buff+1,cli->uname);
            buff[0] = 5;
            transmit(usrclints,buff,strlen(buff),caddr);        
        }
        else if(2 == buff[0])//下线
        {
            //从链表中删除
            USR* delcli = delete_usr(usrclints,caddr);
            //打印客户端登录信息
            printf(">>");
            time(&t); 
            tm = localtime(&t);
            strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm); 
            printf("%s\n",date);
            char delcliname[USRNLEN] = "";
            strcpy(delcliname,delcli->uname);
            printf("用户%s:[%s:%d]从链表删除成功\n",delcliname,delcli->usrIP,delcli->usrPORT);
            free(delcli);
            printf("***当前所有在线用户列表***\n");//test
            printcli(usrclints);//test
            printf("**************************\n\n");//test        
            //发送ACK
            bzero(buff,BUFFLEN);
            buff[0] = 8;
            if(sendto(sfd,buff,1,0,(struct sockaddr*)&caddr,saddr_len) < 0)
            {
                ERR_LOG("sendto");
                break;
            }
            //转发用户退出信息给其他客户端
            bzero(buff,BUFFLEN);
            strcpy(buff+1,delcliname);
            buff[0] = 6;
            transmit(usrclints,buff,strlen(buff),caddr);
        } 
        else if(3 == buff[0])//转发用户聊天消息
        {
            //printf("%s:%s\n", buff+1,buff+11);//test
            buff[0] = 9;
            transmit(usrclints,buff,11+strlen(buff+11),caddr);    
        }
    }
    //退出
    close(sfd);
    return 0;
}
//创建Client链表
USR* create_usr()
{
    USR* head = (USR*)malloc(sizeof(USR));
    //头结点保存Server信息
    strcpy(head->uname,"Server");
    strcpy(head->usrIP,serverIP);
    head->usrPORT = *(unsigned short*)serverPORT;
    head->next = NULL;
    return head;
}
//增加usr用户
USR* insert_usr(USR* head,char* buf,struct sockaddr_in addr)
{
    USR* node = (USR*)malloc(sizeof(USR));
    strcpy(node->uname,buf+1);
    strcpy(node->usrIP,inet_ntoa(addr.sin_addr));
    node->usrPORT = ntohs(addr.sin_port);
    //头插
    node->next = head->next;
    head->next = node;
    return node;
}
//删除usr用户
USR* delete_usr(USR* head,struct sockaddr_in addr)
{
    //根据端口号进行删除
    USR* pre = head;
    USR* p = head;
    unsigned short port = ntohs(addr.sin_port);
    while(p->next != NULL)
    {
        p = p->next;
        if(p->usrPORT == port)
        {
            pre->next = p->next;
            return p;
        }
        pre = pre->next;
    }
}
//测试:打印链表中所有的客户端用户
void printcli(USR* head)
{
    USR *p = head;
    while(p->next != NULL)
    {
        p = p->next;
        printf("*IP:%s PORT:%d uname:%s\n",p->usrIP,p->usrPORT,p->uname);
    }
}
//转发给其他用户
void transmit(USR* head,char* buf,int size,struct sockaddr_in curaddr)
{
    unsigned short curport = ntohs(curaddr.sin_port);//当前用户的端口号
    struct sockaddr_in otheraddr;
    socklen_t otheraddr_len = sizeof(otheraddr);
    otheraddr.sin_family = AF_INET;
    USR *p = head;
    while(p->next != NULL)
    {
        p = p->next;
        if(curport != p->usrPORT)
        {
            otheraddr.sin_port = htons(p->usrPORT);
            otheraddr.sin_addr.s_addr = inet_addr(p->usrIP);
            if(sendto(sfd,buf,size,0,(struct sockaddr*)&otheraddr,otheraddr_len) < 0)
            {
                ERR_LOG("sendto");
                break;
            }        
        }
    }    
}
//系统广播
void systransmit(USR* head,char* buf,int size)
{
    struct sockaddr_in alladdr;
    socklen_t alladdr_len = sizeof(alladdr);
    alladdr.sin_family = AF_INET;
    USR *p = head;
    while(p->next != NULL)
    {
        p = p->next;
        alladdr.sin_port = htons(p->usrPORT);
        alladdr.sin_addr.s_addr = inet_addr(p->usrIP);
        if(sendto(sfd,buf,size,0,(struct sockaddr*)&alladdr,alladdr_len) < 0)
        {
            ERR_LOG("sendto");
            break;
        }        
    }    
}

聊天室客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#define ERR_LOG(msg) do{\
    perror(msg);\
    printf("%d %s %s\n",__LINE__,__func__,__FILE__);\
}while(0)
#define USRNLEN 10
#define DATALEN 245
#define BUFFLEN 256
#define DATELEN 30
//全局变量
int sfd;
struct sockaddr_in saddr;
socklen_t saddr_len;
char name[USRNLEN];
char date[DATELEN];
time_t t;
struct tm *tm;
//登录下线消息
char usrname[USRNLEN];
char buff[BUFFLEN];
//接收聊天数据
char chatdata[DATALEN];
//发送数据消息
char usrname_tsend[USRNLEN];
char chatdata_tsend[DATALEN];
char buff_tsend[BUFFLEN];
//线程1:发送聊天数据 & 发送退出消息
void* handler_1(void* arg)
{
    pthread_detach(pthread_self());
    while(1)
    {
        bzero(usrname_tsend,USRNLEN);
        bzero(chatdata_tsend,DATALEN);
        bzero(buff_tsend,BUFFLEN);
        strcpy(usrname_tsend,name);
        fgets(chatdata_tsend,DATALEN,stdin);
        chatdata_tsend[strlen(chatdata_tsend)-1] = 0;    
        if(strncasecmp(chatdata_tsend,"quit",4) == 0)//退出请求
        {
            //7.预下线
            bzero(usrname,USRNLEN);
            bzero(buff,BUFFLEN);
            //封装预下线数据报并发送
            buff[0] = 2;
            strcat(buff+1,usrname);
            if(sendto(sfd,buff,1+strlen(usrname),0,(struct sockaddr*)&saddr,saddr_len) < 0)
            {
                ERR_LOG("sendto");
                break;
            }
        }
        else//发送聊天数据
        {
            buff_tsend[0] = 3;
            strcat(buff_tsend+1,usrname_tsend);
            strcat(buff_tsend+11,chatdata_tsend);
            if(sendto(sfd,buff_tsend,11+strlen(chatdata_tsend),0,(struct sockaddr*)&saddr,saddr_len) < 0)
            {
                ERR_LOG("sendto");
                break;
            }
        }
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    if(argc < 3)
    {
        printf("请输入服务端的IP和PORT\n");
        return 0;
    }
    //1、创建数据报式socket套接字
    sfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sfd < 0)
    {
        ERR_LOG("socket");
        return -1;
    }
    //2.填充服务器ip和端口号
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(*(unsigned short*)argv[2]);
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    saddr_len = sizeof(saddr);
    //3.注册(发送usrname给server)
    printf("请输入登录名--->");
    fgets(usrname,USRNLEN,stdin);
    usrname[strlen(usrname)-1] = 0;
    strcpy(name,usrname);
    //封装注册登录数据报并发送
    buff[0] = 1;
    strcat(buff+1,usrname);
    if(sendto(sfd,buff,1+strlen(usrname),0,(struct sockaddr*)&saddr,saddr_len) < 0)
    {
        ERR_LOG("sendto");
        return -1;
    }
    //4.等待server的ACK
    bzero(buff,BUFFLEN);
    if(recvfrom(sfd,buff,BUFFLEN,0,NULL,NULL) < 0)
    {
        ERR_LOG("recvfrom");
        return -1;
    }
    //5.登录(打印登录成功)
    if(4 == buff[0])
    {
        printf("***登录成功***\n");
        printf("(提示:输入“quit”退出登录)\n");
        printf("进入聊天界面>>>\n");
    }
    //6.创建2个线程
    pthread_t tid;
    if(pthread_create(tid,NULL,handler_1,NULL) != 0)
    {
        ERR_LOG("pthread_create");
        return -1;
    }
    //线程2:接收聊天数据
    while(1)
    {
        bzero(buff,BUFFLEN);
        bzero(usrname,USRNLEN);
        bzero(chatdata,DATALEN);
        if(recvfrom(sfd,buff,BUFFLEN,0,NULL,NULL) < 0)
        {
            ERR_LOG("recvfrom");
            return -1;
        }    
        if(9 == buff[0])//其他用户消息
        {
            time(&t); 
            tm = localtime(&t);
            strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm); 
            printf("%s",date);
            printf("%s:%s\n",buff+1,buff+11);
        }
        else if(5 == buff[0])//用户上线
        {
            time(&t); 
            tm = localtime(&t);
            strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm); 
            printf("%s",date);        
            printf(">>用户%s上线了\n",buff+1);
        }
        else if(6 == buff[0])//用户下线
        {
            time(&t); 
            tm = localtime(&t);
            strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm); 
            printf("%s",date);
            printf(">>用户%s下线了\n",buff+1);
        }
        else if(7 == buff[0])//系统消息
        {
            time(&t); 
            tm = localtime(&t);
            strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm); 
            printf("%s",date);
            printf("%s:%s\n",buff+1,buff+11);
        }
        else if(8 == buff[0])//下线
        {
            printf("***退出成功***\n");
            close(sfd);
            exit(1);
        }
    }
    close(sfd);
    return 0;
}

演示

Last modification:2021 年 04 月 19 日 13 : 57 : 26