linux 聊天室

Linux下的shell聊天室:

里程碑0:

1、基于C/S架构的聊天室,分为客户端和服务器。

2、客户端登陆时输入服务器IP、port、 昵称不能重复

里程碑1:

3、可以群发消息或指定接收人

4、admin账号可以踢人

5、新加入用户可以看到聊天历史记录

里程碑2:

5、可以互传文件

6、保存聊天信息

里程碑3:

7、第一次登录时,需要注册。


技术设计:

1、在shell下的图形库:curses

2、多线程:pthread

3、网络:socket

4、消息结构:

<span style="font-size:18px;">struct msg_st
{
    char name[256];
    char to[256];
    char data[4096];
    char cmd[256];
};</span>

其中,关于cmd内容:

//login 登陆
//offline 服务器即将踢人
//user 服务发送的用户列表,数据在data里
//chat 表示这是一条消息,数据在data里

5、服务器用了epoll来监听多个fd


登陆界面:

技术分享

聊天界面:

技术分享

以下是源代码(里程碑1):

//client.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<sys/select.h>
#include<pthread.h>
#include<ncurses.h>
#include <sys/epoll.h>

#include "shared_vals.h"
#include"chat_net.h"
#include"chat_ui.h"

char promote[256]= {0};

int main(int argc, char * argv[])
{

    ui_init();


    do
    {
        ui_login(ip,&port,name);
        if(0==net_login(ip,&port,name))
        {
            ui_alert("Login ok!");
            break;
        }
        else
        {
            ui_alert("Login failed!");
        }
    }
    while(1);

    ui_init_wins();

    ui_refresh_wins();
    wprintw(win[1],"IP:%s\nPORT:%d\nNAME:%s",ip,port,name);
    ui_refresh_wins();

//    int i=0;
//    for(i=0; i<100; i++)
//        ui_print_msg("This is a test message!");




    //generate promote as [from] [to]
    sprintf(promote,"[%s]:", name);
    //start recev process

    pthread_t thread;
    int res=pthread_create(&thread,NULL,(void *)net_getmsg,(void*)NULL);
    if(res!=0)
    {
          ui_alert("Error");
          getch();
    }
    while(1)
    {
        ui_promote(promote);

        int c=0;
        struct msg_st msg_client;
        memset((void *)&msg_client,0,sizeof(msg_client));
        ui_getmsg(msg_client.data);
        strcpy(msg_client.name,name);
        strcpy(msg_client.cmd,"chat");
        write(sockfd,&msg_client,sizeof(msg_client));


    }

    net_end();
    ui_end();


    exit(0);
}



//chat_net.h
#ifndef _CHAT_NET_H_
#define _CHAT_NET_H_

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<sys/select.h>
#include<pthread.h>
#include<ncurses.h>
#include <sys/epoll.h>
#include <time.h>
#include "shared_vals.h"
#include  "chat_ui.h"
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ip[256];
int  port;
char name[256];
int net_login(char *ip,int * port,char *name)
{


    sockfd=socket(AF_INET,SOCK_STREAM,0);
    address.sin_family=AF_INET;
    address.sin_addr.s_addr=inet_addr("127.0.0.1");
    address.sin_port=htons(*port);
    len=sizeof(address);
    result=connect(sockfd,(struct sockaddr *)&address,len);
    if(result==-1)
    {
        printf("ERROR:%s\n",strerror(errno));
        close(sockfd);
        return -1;
    }

    struct msg_st msg;
    memset((void *)&msg,0,sizeof(msg));
    sprintf(msg.cmd,"login");
    strcpy(msg.name,name);
    //  fgets(msg.name,256,stdin);
    //printf("checking name [%s]...\n",msg.name);
    write(sockfd,& msg,sizeof( msg));

    usleep(100000);
    int nread=0;
    ioctl(sockfd,FIONREAD,&nread);
    if(nread!=0)
    {
        read(sockfd,&msg,sizeof(msg));
        if(strcmp("ok",msg.data)==0)
        {
            return 0;//login ok
        }
        else
        {

            // printf("Login failed!\n");
            close(sockfd);
            return -1;//login failed
        }
    }
//   printf("Login failed!\n");
    close(sockfd);
    return -1;//login failed

}

void net_getmsg()
{

    while(1)
    {
        usleep(100000);
        int nread=0;
        ioctl(sockfd,FIONREAD,&nread);
        if(nread!=0)
        {
            struct msg_st msg_read;
            read(sockfd,&msg_read,sizeof(msg_read));
            if(strncmp(msg_read.cmd,"chat",4)==0)
            {
                struct tm *tm_ptr;
                time_t the_time;
                (void)time(&the_time);
                tm_ptr=gmtime(&the_time);
                char buff[256]={0};
                sprintf(buff,"\n[%02d:%02d:%02d]",tm_ptr->tm_hour,tm_ptr->tm_min,tm_ptr->tm_sec);
                strcat(buff,msg_read.name);
                strcat(buff,":");
                ui_print_msg(buff);
                ui_print_msg(msg_read.data);
            }
            else if(strncmp(msg_read.cmd,"user",4)==0)
            {
                 ui_list_users(msg_read.data);
                 ui_input_win_active();
            }
            else if(strncmp(msg_read.cmd,"chat",4)==0)
            {
            }
            else if(strncmp(msg_read.cmd,"chat",4)==0)
            {
            }
            else
            {
            }
            //debug

            //todo

        }
    }
    pthread_exit(NULL);

}

void net_end()
{

    close(sockfd);

}
#endif



//chat_ui.h
#ifndef _CHAT_UI_H_
#define _CHAT_UI_H_
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<curses.h>
#define WIN_MAX  10
#define USR_MAX  20l

//need from other files
//char ip[256];
//int port;
//char name[256];

//char *usr_list[USR_MAX]={"Shenyu",
//                            "Wanglm",
 //                           NULL};


////
WINDOW * win[WIN_MAX ]={0};//max open win = 10
int cur_x,cur_y;



void ui_refresh_wins() //refresh all open wins
{
    int i=0;
    for(i=0;i<WIN_MAX ;i++)
    {
        if(win [i]!=NULL)
        {
            touchwin(win [i]);
            wrefresh(win [i]);
        }
    }
    //the input always active

}

void ui_input_win_active()
{
      touchwin(win [2]);
      wrefresh(win [2]);
}
int ui_init()
{
    initscr();
    refresh();

    start_color();

    init_pair(1,COLOR_BLACK,COLOR_CYAN);
    init_pair(2,COLOR_YELLOW,COLOR_BLUE);
    init_pair(3,COLOR_YELLOW,COLOR_RED);


}
int ui_init_wins()
{
    win[0]=newwin(20,10,0,0);
    win[1]=newwin(20,50,0,10);
    win[2]=newwin(1,60,20,0);


    wbkgd(win[0],COLOR_PAIR(1));
    wbkgd(win[1],COLOR_PAIR(2)|A_BOLD);
    wbkgd(win[2],COLOR_PAIR(3)|A_BOLD);
    scrollok(win[0],TRUE);
    scrollok(win[1],TRUE);


    keypad(win[0],TRUE);
    keypad(win[1],TRUE);
    keypad(win[2],TRUE);
    keypad(stdscr, TRUE);
}
int ui_login(char *ip,int * port,char *name)
{
    win[3]=newwin(10,40,5,10);
    wbkgd(win[3],COLOR_PAIR(3)|A_BOLD);

    wrefresh(win[3]);
    mvwprintw(win[3],3,3,"Server   IP:\n");
    mvwprintw(win[3],4,3,"Server port:\n");
    mvwprintw(win[3],5,3,"User name  :\n");
    wrefresh(win[3]);

    wmove(win[3],3,16);
    wrefresh(win[3]);
    wgetnstr(win[3],ip,100);

    wmove(win[3],4,16);
    wrefresh(win[3]);
    wscanw(win[3],"%d", port);

    wmove(win[3],5,16);
    wrefresh(win[3]);
    wgetnstr(win[3],name,100);
    delwin(win[3]);

}


void ui_list_users(char * data)
{
int i=0;
wclear(win[0]);
 wprintw(win[0],"--users--\n");
 wprintw(win[0],"%s\n",data);
wrefresh(win[0]);

}

void ui_print_msg(char *msg)
{

   wprintw(win[1],"%s\n",msg);
   wrefresh(win[1]);
}

void ui_alert(char *msg)
{
    win[4]=newwin(10,40,5,10);
    wbkgd(win[4],COLOR_PAIR(3)|A_BOLD);
    wrefresh(win[4]);
    mvwprintw(win[4],5,3,"%s\n   Press any key to continue...",msg);
    wrefresh(win[4]);
    getch();
    delwin(win[3]);


}

void ui_end()
{
    endwin();
}
void ui_promote(char *msg)
{
 ui_input_win_active();
 wclear(win[2]);
 wprintw(win[2],"%s",msg);
 wrefresh(win[2]);
}
void ui_getmsg(char * buff)
{
 ui_input_win_active();
 wgetnstr(win[2],buff,4096);

}
//sample programs for UI
//int main(int argc, char * argv[])
//{
//
//
//    ui_init();
//    ui_login(ip,&port,name);
//
//    ui_init_wins();
//    ui_list_users( usr_list );
//    ui_refresh_wins();
//    wprintw(win[1],"IP:%s\nPORT:%d\nNAME:%s",ip,port,name);
//    ui_refresh_wins();
//
//int i=0;
//for(i=0;i<100;i++)
//    ui_print_msg("This is a test message!");
//
//
//    ui_refresh_wins();
//    getch();
//}

#endif // _CHAT_UI_H_



#ifndef _SHARED_VALS_H_
#define _SHARED_VALS_H_

struct msg_st
{
    char name[256];//发送信息人
    char to[256];  //接收人
    char data[4096];//数据
    char cmd[256];//消息的类型
};

//login 登陆
//offline 服务器即将踢人
//user 服务发送的用户列表,数据在data里
//chat 表示这是一条消息,数据在data里

#endif



#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<sys/select.h>
#include<pthread.h>
#include <sys/epoll.h>
#define MAXEVENTS 64

struct msg_st
{
    char name[256];
    char to[256];
    char data[4096];
    char cmd[256];
};
#define MAX_CLIENTS 256
int chat_clients_sets[MAX_CLIENTS]={0};
char *chat_clients_names [MAX_CLIENTS] = {0};
int chat_clients_count=0;


int chat_clients_add(int fd,char * _name)
{
    int i=0;
    for(i=0; i<MAX_CLIENTS; i++)
    {
        if(chat_clients_sets[i]==0)
        {
            chat_clients_sets[i]=fd;

            int len=strlen(_name)+1;
            char *ptr=malloc(len*sizeof(char));
            memset(ptr,0,sizeof(ptr));
            strcpy(ptr,_name);
            *(chat_clients_names+i)=ptr;

            chat_clients_count++;
            return 0;
        }
    }
    return -1;
}
int chat_clients_del(int fd)
{
    int i=0;
    for(i=0;i<MAX_CLIENTS;i++)
    {
        if(chat_clients_sets[i]==fd)
        {
            chat_clients_sets[i]=0;
            free(*(chat_clients_names+i));
            *(chat_clients_names+i)=NULL;
            chat_clients_count--;
            return 0;
        }
    }
    return -1;
}
int chat_user_list(struct msg_st * m)
{
    memset( m,0,sizeof(struct msg_st));
    strcpy(m->cmd,"user");
    int i=0;
    for(i=0;i<MAX_CLIENTS;i++)
    {
        if(*(chat_clients_names+i)!=NULL)
        {
            char buff[4096];
            memset(buff,0,sizeof(buff));

            sprintf(buff,"%s\n",*(chat_clients_names+i));
            strcat(m->data,buff);

        }

    }


}

int chat_clients_sendto_all(struct msg_st * mymsg)
{

    int j=0;
    for (j = 0; j < MAX_CLIENTS; j++)
    {
        if(chat_clients_sets[j]!=0)
        {

            printf("send to %s\n",(*mymsg).name);
          int    s = write (chat_clients_sets[j] , mymsg, sizeof(struct msg_st));
            if (s == -1)
            {
                perror ("write");
                abort ();
            }

        }



    }

}
static int
create_and_bind (char *port)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int s, sfd;

    memset (&hints, 0, sizeof (struct addrinfo));
    hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
    hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
    hints.ai_flags = 0x0001;  /* All interfaces */

    s = getaddrinfo (NULL, port, &hints, &result);
    if (s != 0)
    {
        fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
        return -1;
    }

    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;
        s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
        if (s == 0)
        {
            /* We managed to bind successfully! */
            break;
        }

        close (sfd);
    }
    if (rp == NULL)
    {
        fprintf (stderr, "Could not bind\n");
        return -1;
    }
    freeaddrinfo (result);
    return sfd;
}

static int
make_socket_non_blocking (int sfd)
{
    int flags, s;

    flags = fcntl (sfd, F_GETFL, 0);
    if (flags == -1)
    {
        perror ("fcntl");
        return -1;
    }

    flags |= O_NONBLOCK;
    s = fcntl (sfd, F_SETFL, flags);
    if (s == -1)
    {
        perror ("fcntl");
        return -1;
    }

    return 0;
}

    int sfd, s;
    int efd;
    struct epoll_event event;
    struct epoll_event *events;

void init(int argc, char * argv[])
{

  if (argc != 2)
    {
        fprintf (stderr, "Usage: %s [port]\n", argv[0]);
        exit (EXIT_FAILURE);
    }

    sfd = create_and_bind (argv[1]);
    if (sfd == -1)
        abort ();

    s = make_socket_non_blocking (sfd);
    if (s == -1)
        abort ();

    s = listen (sfd, SOMAXCONN);
    if (s == -1)
    {
        perror ("listen");
        abort ();
    }

    efd = epoll_create1 (0);
    if (efd == -1)
    {
        perror ("epoll_create");
        abort ();
    }

    event.data.fd = sfd;
    event.events = EPOLLIN | EPOLLET;
    s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
    if (s == -1)
    {
        perror ("epoll_ctl");
        abort ();
    }

    /* Buffer where events are returned */
    events = calloc (MAXEVENTS, sizeof event);
}

void server_accept_clients()
{
 /* We have a notification on the listening socket, which
                   means one or more incoming connections. */
                while (1)
                {
                    struct sockaddr in_addr;
                    socklen_t in_len;
                    int infd;
                    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                    in_len = sizeof in_addr;
                    infd = accept (sfd, &in_addr, &in_len);
                    if (infd == -1)
                    {
                        if ((errno == EAGAIN) ||
                                (errno == EWOULDBLOCK))
                        {
                            /* We have processed all incoming
                               connections. */
                            break;
                        }
                        else
                        {
                            perror ("accept");
                            break;
                        }
                    }

                     int s = getnameinfo (&in_addr, in_len,
                                     hbuf, sizeof hbuf,
                                     sbuf, sizeof sbuf,
                                     NI_NUMERICHOST | NI_NUMERICSERV);
                    if (s == 0)
                    {
                        printf("Server: accepted connection on descriptor %d "
                               "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                    }

                    /* Make the incoming socket non-blocking and add it to the
                       list of fds to monitor. */
                    s = make_socket_non_blocking (infd);
                    if (s == -1)
                        abort ();

                    event.data.fd = infd;
                    event.events = EPOLLIN | EPOLLET;
                    s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
                    //add to clients sets
                   // chat_clients_add(infd);
                    if (s == -1)
                    {
                        perror ("epoll_ctl");
                        abort ();
                    }
                }
}
int
main (int argc, char *argv[])
{

    init(argc,argv);

    /* The event loop */
    while (1)
    {
        int n, i;

        n = epoll_wait (efd, events, MAXEVENTS, -1);
        for (i = 0; i < n; i++)
        {
            if ((events[i].events & EPOLLERR) ||
                    (events[i].events & EPOLLHUP) ||
                    (!(events[i].events & EPOLLIN)))
            {
                /* An error has occured on this fd, or the socket is not
                   ready for reading (why were we notified then?) */
                fprintf (stderr, "epoll error\n");
                close (events[i].data.fd);
                continue;
            }

            else if (sfd == events[i].data.fd)
            {
                server_accept_clients();
                continue;
            }
            else
            {
                /* We have data on the fd waiting to be read. Read and
                   display it. We must read whatever data is available
                   completely, as we are running in edge-triggered mode
                   and won't get a notification again for the same
                   data. */
                int done = 0;

                while (1)
                {
                    ssize_t count;
                    //char buf[512]= {0};
                    struct msg_st mymsg;
                    memset((void *)&mymsg,0,sizeof(mymsg));
                    count = read (events[i].data.fd, (void *)&mymsg, sizeof(mymsg));

                    if (count == -1)
                    {
                        /* If errno == EAGAIN, that means we have read all
                           data. So go back to the main loop. */
                        if (errno != EAGAIN)
                        {
                            perror ("read");
                            done = 1;
                        }
                        break;
                    }
                    else if (count == 0)
                    {
                        /* End of file. The remote has closed the
                           connection. */
                        done = 1;
                        break;
                    }
                    /* do a little work here*/

                    /* Write the buffer to standard output */
                    printf("Server: msg from client [%s]-[%s]\n",mymsg.name,mymsg.data);

                    if(strncmp(mymsg.cmd,"login",5)==0)
                    {
                        printf("return login ok!\n");
                        strcpy(mymsg.data,"ok");
                        chat_clients_add(events[i].data.fd,mymsg.name);
                          chat_clients_sendto_all(&mymsg);
                          continue;
                    }



                    strcpy(mymsg.cmd,"chat");
                    chat_clients_sendto_all(&mymsg);
                    usleep(100000);
                    chat_user_list(&mymsg);
                    printf("==user==\n%s\n",mymsg.data);
                    chat_clients_sendto_all(&mymsg);

                }

                if (done)
                {
                    printf ("Server: closed connection on descriptor %d\n",
                            events[i].data.fd);

                    /* Closing the descriptor will make epoll remove it
                       from the set of descriptors which are monitored. */
                    close (events[i].data.fd);
                }
            }
        }
    }

    free (events);

    close (sfd);

    return EXIT_SUCCESS;
}


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。