【网络编程】聊天室

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


void* recv_msg(void* arg);
int is_my_ip(const char* ip); //判断是否是本机IP地址,如果是返回1,否则返回0

/*
char* inet_ntoa_dj()
{
    static char ip[20];

    static int i=0;

    i++;

    sprintf(ip,"128.0.34.%d",i);

    return ip;
}
*/

int main()
{
    /*
    char* p1=inet_ntoa_dj();
    printf("%s\n",p1);

    char ip[20];
    strcpy(ip,p1);

    char* p2=inet_ntoa_dj();
    printf("%s\n",p1);
    printf("%s\n",p2);

    exit(0);
    */

    int sock=socket(AF_INET,SOCK_DGRAM,0);

    int optval=1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
    setsockopt(sock,SOL_SOCKET,SO_BROADCAST,&optval,sizeof(optval));


    //被动通信方必须显式绑定
    struct sockaddr_in myaddr;
    myaddr.sin_family=AF_INET;
    myaddr.sin_addr.s_addr=INADDR_ANY;
    myaddr.sin_port=htons(8888);

    if(-1==bind(sock,(struct sockaddr*)&myaddr,sizeof(myaddr)))
    {
        perror("bind");
        exit(1);
    }


    pthread_t tid;
    pthread_create(&tid,NULL,recv_msg,(void*)sock);


    char msg[1024];

    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=inet_addr("128.0.255.255");
    addr.sin_port=htons(8888);

    while(1)
    {
        printf("\n我说:");
        fgets(msg,sizeof(msg),stdin);

        sendto(sock,msg,strlen(msg),0,(struct sockaddr*)&addr,sizeof(addr));
    }


    close(sock);

    return 0;
}


void* recv_msg(void* arg)
{
    int sock=(int)arg;

    char msg[1024];
    int ret;
    struct sockaddr_in addr;
    socklen_t addrlen=sizeof(addr);

    while(1)
    {
        ret=recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&addr,&addrlen);

        if(ret>0)
        {
            if(is_my_ip(inet_ntoa(addr.sin_addr))) continue;
            msg[ret]='\0';
            printf("%s:%d说:%s\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port),msg);
        }
    }

    return NULL;
}


int is_my_ip(const char* ip_des) //判断是否是本机IP地址,如果是返回1,否则返回0
{
    char ip[20];
    strcpy(ip,ip_des);

    //获取主机名
    char host_name[100];
    gethostname(host_name,sizeof(host_name));
    //printf("主机名:%s\n",host_name);

    //获取本机所有IP地址
    struct hostent *he=gethostbyname(host_name);

    char* ip_tmp=NULL;

    if(he!=NULL)
    {
        int i=0;

        //printf("本机IP地址如下:\n");
        while(he->h_addr_list[i]!=NULL)
        {
            //printf("%s\n",inet_ntoa(*(struct in_addr*)(he->h_addr_list[i])));
            ip_tmp=inet_ntoa(*(struct in_addr*)(he->h_addr_list[i]));

            if(strcmp(ip_tmp,ip)==0) 
            {
                return 1;
            }

            i++;
        }
    }

    return 0;
}



解决办法:ubuntu系统中缺少一个套件 ncurses devel ,把此套件安装下即可
$ sudo apt-get install libncurses5-dev

/*
功能:    在同一台服务器上ssh登录的用户可以群聊(聊天室)

原理:    1、通过roomNo.来区分不同的房间或群组;
        2、以roomNo.作为key来创建一块共享内存,来保存进入到该room的用户列表;
        3、用户以ssh(或其它方式)登录到服务器,在/dev/pts/目录下都会有一个对应文件;
        4、自己的用户名和对应的设备文件名可从环境变量(USER和SSH_TTY)中获取到;
        5、通过向用户对应的设备名中写数据,用户就可以收到;
        6、用户在发送信息时向用户列表中的用户逐个发送,即可实现群聊功能;

        实际上是对“write”命令的加强而已。

遇到的问题:
        1、显示不显示汉字受终端软件影响,自己配置下;
        2、获取自己用户名和对应的设备文件名,寻找了很多方式,最终发现通过环境变量最简单;
        3、没有权限open方式其他用户对应的设备,通过修改/dev/pts/下的文件权限为622解决,
        最好自己根目录的.bashrc脚本文件中加入“chmod 622 $SSH_TTY”,这样每次登录时会自动修
        改;暂没找到其它更好方法;
        4、fgets方式获取的字符串不能backspace,网上朋友说修改VERASE值为0x08,试验发现可以,
        但删汉字时,一个汉字要对应2个backspace;
        5、在自己输入信息到一半,但还没有回车发送便收到其它用户的信息时,自己输入的信息
        被打断,再解决这个问题就更接近完美了;

*/

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <utmp.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <signal.h>
#include <stdbool.h>
#include <termios.h>
#include <curses.h>

#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER

//同时在线的用户最大数量,可更改
#define USER_NUM 20
#define USER_LEN 30

struct shared_users_st
{
    int written;            //作为一个标志,非0:表示不可写,0表示可读写
    char users[USER_NUM][USER_LEN];    //记录写入和读取的文本, zhi.wang@/dev/pts/2
};

#endif

void utmpinfo();
void pwentinfo();
char *getuser();
char *gettty();
void msgparser(char *buffer);

int shmcreate(key_t key);
int shmdetach(void *shm);
int shmdestroy(int shmid);
int shmuseradd(struct shared_users_st *);
int shmuserclear(struct shared_users_st *);
int shmuserexit(struct shared_users_st *);
int shmuserlist(struct shared_users_st *);
int shmusermsg(struct shared_users_st *, char *);
bool userinutmp(char *);

void *shm = NULL;
int shmid;
int mylocal = -1;    //记录自己在shm中的位置,退出时方便删除

struct termios new; /* 控制终端状态的数据结构,可 man termios 查看*/
struct termios old;

//全局开关
//匿名发送消息
bool ishidden = false;
bool running = true;

char prompt = '_';

static char *help = "\
    :l        List users;\n\
    :c        Clear all users;\n\
    :q        Quit;\n\
    :help    Help;\n\
    :hide    set anonymous;\n\
    :nohide    set noanonymous;\n\
    ";

void sig_handler( int sig);
void chatroominit();
void chatroomquit();

void chatroominit()
{
    signal(SIGINT, sig_handler);

    tcgetattr(0,&old); /* 得到当前的终端状态 */
    new = old;
    //printf("old[VERASE]=0x%x\n\n", old.c_cc[VERASE]);
    new.c_cc[VERASE] = 0x08;    //实现backspace功能
    tcsetattr(0,TCSANOW,&new); /* 应用新的设置*/
}

void chatroomquit()
{
    tcsetattr(0,TCSANOW,&old);
    //shmuserclear(shm);
    shmuserexit((struct shared_users_st *)shm);
    //shmdetach(shm);
}

//处理ctrl+c
void sig_handler( int sig)
{
    //printf("%s %d \n", __FUNCTION__, sig);
    if(sig == SIGINT )
    {
        chatroomquit();
        exit(0);
    }
}

int main()
{
    char buffer[BUFSIZ + 1];//用于保存输入的文本
    char *roomno;

    //printf(" ** room NO.: %d\n", sizeof(struct shared_users_st));
    roomno = getpass("Please enter room No.:");
    //printf("  ** room NO. is : %s\n", roomno);

    chatroominit();
    //setuid(0);

    shmid = shmcreate((key_t)atoi(roomno));
    shmuseradd((struct shared_users_st *)shm);
    shmuserlist((struct shared_users_st *)shm);

    while (running)
    {
        printf("%c", prompt);
        memset(buffer, 0, BUFSIZ + 1);
        fgets(buffer, BUFSIZ, stdin);

        msgparser(buffer);

    }

    chatroomquit();
    exit(EXIT_SUCCESS);
}

//分析输入内容
void msgparser(char *buffer)
{
    char c;

    switch (c = buffer[0])
    {
    case ':':
        if (strncmp(buffer+1, "q", 1) == 0)
        {
            running = false;
            //检查是否还有在线用户,如没有则销毁
        }
        else if (strncmp(buffer+1, "help", 4) == 0)
        {
            printf("%s\n", help);
        }
        else if (strncmp(buffer+1, "clear", 5) == 0)
        {
            shmuserclear((struct shared_users_st *)shm);
        }
        else if (strncmp(buffer+1, "l", 1) == 0)
        {
            shmuserlist((struct shared_users_st *)shm);
        }
        else if (strncmp(buffer+1, "hide", 4) == 0)
        {
            ishidden = true;
        }
        else if (strncmp(buffer+1, "nohide", 6) == 0)
        {
            ishidden = false;
        }
        break;
    case '?':
        printf("%s\n", help);
        break;
    default:
        //printf("%d %d\n", strlen(buffer), buffer[0]);
        //空格和换行不发送
        if ((strlen(buffer) <= 2 && buffer[0] == 32) ||
            (strlen(buffer) <= 1 && buffer[0] == 10))
        {
            break;
        }
        //printf("%s\n", buffer);
        shmusermsg((struct shared_users_st *)shm, buffer);
    }
}

char *getuser()
{
    return getenv("USER");
}

char *gettty()
{
    return getenv("SSH_TTY");
}

static void waitwrite(struct shared_users_st *shared)
{
    //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
    //shared->written = 0;
    while(shared->written == 1)
    {
        sleep(1);
        printf("Waiting...\n");
    }
}

int shmcreate(key_t key)
{
    char buffer[BUFSIZ + 1];//用于保存输入的文本

    //创建共享内存
    shmid = shmget((key_t)key, sizeof(struct shared_users_st), 0666|IPC_CREAT);
    if(shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        //exit(EXIT_FAILURE);
    }
    //将共享内存连接到当前进程的地址空间
    shm = shmat(shmid, (void*)0, 0);
    if(shm == (void*)-1)
    {
        fprintf(stderr, "shmat failed\n");
        //exit(EXIT_FAILURE);
    }
    //printf("Memory attached at %X\n", (int)shm);

    return shmid;

}

int shmdetach(void *shm)
{
    //把共享内存从当前进程中分离
    if(shmdt(shm) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

int shmdestroy(int shmid)
{
    //printf(" ** %s\n", __FUNCTION__);

    //删除共享内存  SHM_DEST
    if(shmctl(shmid, IPC_RMID, 0) == -1)
    {
        fprintf(stderr, "shmctl(IPC_RMID) failed(%d).\n", shmid);
        exit(EXIT_FAILURE);
    }

    return 0;
}

int shmuseradd(struct shared_users_st *shared)
{
    //struct shared_users_st *shared = NULL;
    char buffer[BUFSIZ + 1];//用于保存输入的文本
    int i = 0;
    char *p = NULL;

    //设置共享内存
    //shared = (struct shared_users_st*)shm;

    //向共享内存中写入数据
    strcat(buffer, getuser());
    strcat(buffer, "@");
    strcat(buffer, gettty());
    //printf("buffer:%s\n", buffer);

    waitwrite(shared);
    //向共享内存中写入数据前先置1,其它进程不可写
    shared->written = 1;

    do
    {
        //没有找到@时,或者找到自己对应的tty时则替换
        if ((p = strchr(shared->users[i],'@')) == 0)
        {

            if (mylocal == -1)
            {
                //printf("%s %s %d\n", __FUNCTION__, buffer, i);
                strncpy(shared->users[i], buffer, USER_LEN);
                //记录下自己所在位置,退出时自己删除
                mylocal = i;
            }
            else
            {
                //printf("%s %s %d reset.\n", __FUNCTION__, shared->users[i], i);
                memset(shared->users[i], 0, USER_LEN);
            }
        }
        else
        {
            //printf("\n\nbuffer: %s != %s %d=%d\n\n", p+1, gettty(), strlen(p+1) ,strlen(gettty()));
            if (!strncasecmp(p+1, gettty(), strlen(p+1) > strlen(gettty()) ? strlen(p+1) : strlen(gettty()) ))
            {
                if (mylocal == -1)
                {
                    //printf("%s %s %d\n", __FUNCTION__, buffer, i);
                    strncpy(shared->users[i], buffer, USER_LEN);
                    //记录下自己所在位置,退出时自己删除
                    mylocal = i;
                }
                else
                {
                    //printf("%s %s %d reset.\n", __FUNCTION__, shared->users[i], i);
                    memset(shared->users[i], 0, USER_LEN);
                }
            }
            //continue;

            //检查user和tty跟当前系统登录的是否匹配(参考w命令)
            if (userinutmp(shared->users[i]) == false)
            {
                printf("  ## %s %s %d autodelete.\n", __FUNCTION__, shared->users[i], i);
                memset(shared->users[i], 0, USER_LEN);
            }
        }
        //memset(shared->users[i], 0, USER_LEN);
    } while (++i < USER_NUM);

    //写完数据,设置written使共享内存段可读
    shared->written = 0;

    if (mylocal != -1)
    {
        //告诉在线用户,我已加入
        memset(buffer, 0, BUFSIZ + 1);
        sprintf(buffer, "\n  ## Welcome %s to our room.\n", shared->users[mylocal]);
        shmusermsg(shared, buffer);
    }

    return 0;
}

//清空所有用户
int shmuserclear(struct shared_users_st *shared)
{
    //struct shared_users_st *shared = NULL;
    int i = 0;
    char buffer[BUFSIZ + 1];//用于保存输入的文本

    //设置共享内存
    //shared = (struct shared_users_st*)shm;

    //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
    waitwrite(shared);
    //向共享内存中写入数据前先置1,其它进程不可写
    shared->written = 1;

    do
    {
        //找到@时
        if (rindex(shared->users[i],'@') != 0)
        {
            memset(buffer, 0, BUFSIZ + 1);
            sprintf(buffer, "\n  ## %s has been kicked out of the room.\n\n\n",
                shared->users[i]);
            shmusermsg(shared, buffer);

            memset(shared->users[i], 0, USER_LEN);
            //break;
        }
    } while (++i < USER_NUM);

    //写完数据,设置written使共享内存段可读
    shared->written = 0;

    return 0;
}

//退出时只删除自己即mylocal所标记位置
//如果退出时没有在线用户,则销毁shm
int shmuserexit(struct shared_users_st *shared)
{
    //struct shared_users_st *shared = NULL;
    char buffer[BUFSIZ + 1];//用于保存输入的文本
    int i = 0;

    //设置共享内存
    //shared = (struct shared_users_st*)shm;

    //printf("%s %s %d\n", __FUNCTION__, shared->users[mylocal], mylocal);
    //告诉在线用户,我已离开
    memset(buffer, 0, BUFSIZ + 1);
    sprintf(buffer, "\n  ## %s exit the room.\n", shared->users[mylocal]);
    shmusermsg(shared, buffer);

    //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
    waitwrite(shared);

    //向共享内存中写入数据前先置1,其它进程不可写
    shared->written = 1;

    memset(shared->users[mylocal], 0, USER_LEN);

    do
    {
        //找到@时
        if (rindex(shared->users[i],'@') != 0)
        {
            //memset(shared->users[i], 0, USER_LEN);
            break;
        }
    } while (++i < USER_NUM);

    //写完数据,设置written使共享内存段可读
    shared->written = 0;

    shmdetach((void *)shared);

    //如果已没有用户在线,则销毁
    if (i == USER_NUM)
    {
        //此处暂未解决“不能其他用户创建的共享内存”的问题,所有注释掉
        //shmdestroy(shmid);
    }

    return 0;
}

int shmuserlist(struct shared_users_st *shared)
{
    //struct shared_users_st *shared = NULL;
    int i = 0;

    //设置共享内存
    //shared = (struct shared_users_st*)shm;

    //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
    //waitwrite(shared);

    //向共享内存中写入数据前先置1,其它进程不可写
    //shared->written = 1;

    printf("  zz%s\n", "zzzzzzzzzzzzzzzzzzzzzzzzzzzz");

    do
    {
        //找到@时
        if (rindex(shared->users[i],'@') != 0)
        {
            printf("  zz %d %s\n", i, shared->users[i]);

            //break;
        }
    } while (++i < USER_NUM);

    printf("  zz%s\n", "zzzzzzzzzzzzzzzzzzzzzzzzzzzz");

    //写完数据,设置written使共享内存段可读
    //shared->written = 0;

    return 0;
}

int shmusermsg(struct shared_users_st *shared, char *buffer)
{
    //struct shared_users_st *shared = NULL;
    int i = 0;
    char buff[BUFSIZ + 1];//用于保存输入的文本

    //设置共享内存
    //shared = (struct shared_users_st*)shm;

    //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
    //waitwrite(shared);

    //向共享内存中写入数据前先置1,其它进程不可写
    //shared->written = 1;

    if (!ishidden)
    {
        sprintf( buff, "  ** %s: %s", getuser(), buffer);
        strcpy(buffer, buff);
    }

    do
    {
        //找到@时
        if (rindex(shared->users[i],'@') != 0 ) //&& i != mylocal
        {
            char *p;
            int fd;

            //发送给其它用户时,换行\n后输出
            if (i != mylocal)
            {
                sprintf( buff, "%s", buffer);
            }
            else
            {
                //\033[1A 先回到上一行 //\033[K 清除该行
                sprintf( buff, "\033[1A\033[K%s", buffer);
            }
            //strcpy(buffer, buff);

            //printf("%s %s %d\n", __FUNCTION__, strchr(shared->users[i],'@')+1, i);

            if ((p = strchr(shared->users[i],'@')) == 0)
            {
                //perror("open error");
                continue;
            }

            //发送消息之前先判断该用户是否在线
            if ((fd = open(p+1, O_WRONLY)) < 0)
            {
                perror("open error");
                printf("%s\n", shared->users[i]);
                //exit(EXIT_FAILURE);
            }

            if (write(fd, buff, strlen(buff)+1) != strlen(buff)+1)
            {
                perror("write error");
                //exit(EXIT_FAILURE);
            }

            close(fd);

            //break;
        }
    } while (++i < USER_NUM);

    //写完数据,设置written使共享内存段可读
    //shared->written = 0;

    return 0;
}

//在utmp文件(当前在线用户,即w命令展示结果)中找到返回非0,未找到返回0
bool userinutmp(char *userinfo)
{
    struct utmp *u;
    char *p, *tty, user[USER_LEN];

    memset(user, 0, USER_LEN);
    strncpy(user, userinfo, strlen(userinfo));

    p = strtok(user, "@");
    //user = p;
    p = strtok(NULL, "@");
    tty = p;
    //printf("%s %s %s.\n", user, tty, userinfo);

    struct utmp ut;
    strcpy (ut.ut_line,tty+5);
    while ((u=getutline(&ut)))
    {
        //printf("-%d %s %s %s \n",u->ut_type,u->ut_user,u->ut_line,u->ut_host);
        //如果找到匹配
        if (!strcmp(u->ut_user, user))
        {
            endutent();
            return true;
        }
    }

    endutent();

    return false;

}

#if 1

void pwentinfo()
{
    struct passwd *user;

    if((user = getpwuid(geteuid()))!=0)
    {
        printf("%s:%d:%d:%s:%s:%s\n",user->pw_name,user->pw_uid,user->pw_gid,
        user->pw_gecos,user->pw_dir,user->pw_shell);
    }

    endpwent();

    printf("uid is %d\n",getpgid(getegid()));
    printf("egid is %d\n",getegid());
    printf("gid is %d\n",getpgrp());

}
#endif


posted @ 2024-10-28 10:27  快乐的提千万  阅读(2)  评论(0编辑  收藏  举报