본문 바로가기

Programming Skill/System Programming

[System Programming] TCP/IP를 이용한 채팅 프로그램

작업 환경

 - OS : Ubuntu 14.04.5 LTS 64bit

 - Editor : vi

 - Compile : gcc



프로젝트 소개

 - 다대다 메신져가 가능한 채팅 프로그램


구현기능

1. TCP/IP을 이용한 채팅 프로그램

2. 멀티 쓰레드를 이용하여 각 PORT로 채팅방 구분

3. 귓속말의 경우 /w ID 내용 형식으로 원하는 사용자에게 귓속말 채팅 기능 추가

4. 파일전송의 경우 /f 파일이름 형식으로 open()을 이용해 파일전송 기능 추가

5. 뮤텍스를 이용하여 쓰레드들이 running time동안 쓰레드를 동기화 시켜 서로 충돌나지 않게 방지


my.h

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>



info.h

#include "my.h"

#define MAX_USER_NUM    5
#define MAX_NICK_LEN    30
#define    MAX_MESSAGE_LEN    1024

#define SERV_IP    "127.0.0.1"
#define SERV_PORT    10200

typedef struct _user{
    int usd;
    int unum;
    int rsd;
    char unick[MAX_NICK_LEN];
}User;

typedef struct _room{
    int rsd;
    int usd[5];
    int ucnt;
}Room;



serv.h

#include "info.h"

User list[5];
Room rlist[5];

int sd;
int rsd;
int rcnt = 0;
int usernum = 0;
int rusernum = 0;
pthread_mutex_t usermutex, roommutex;

void SigExit(int signo);
int ServerSetting(char *ip, int port);
void *JoinChat(void *user);
void *DeliveryMessage(void *user);
void *Notice(void *user);
void Whisp(User user, char *rbuf);
void ClientExit(User user);
void *thrdmain(void *room);
void FileIO(User user, char *rbuf);



cli.h

#include "info.h"

User list[5];

int sd;
int rsd;
int flag = 0;
int rcnt = 0;
int usernum = 0;
pthread_mutex_t usermutex;

void SigExit(int signo);
int SockSetting(char *ip, int port);
void JoinChat(int ssd);
void *RecvMsg(void *user);
void *SendMsg(void *user);
void *thrdmain(void *us);



serv.c

#include "serv.h"

int main()
{
    int room, i;

    signal(SIGINT, SigExit);

    pthread_mutex_init(&usermutex, NULL);
    pthread_mutex_init(&roommutex, NULL);

    printf("방 갯수를 입력하세요\n");
    scanf("%d",&room);
    getchar();

    pthread_t ptr[256];

    for(i=0;i<room;i++)
    {
        pthread_create(&ptr[i], NULL, thrdmain, &room);
        pthread_detach(ptr[i]);
    }

    while(1)
        pause();

    return 0;
}

void *thrdmain(void *room)
{
    pthread_mutex_lock(&roommutex);
    User user;// = *(User *)us;
    Room rm;

    int ssd;
    int port = SERV_PORT + rcnt;
    rm.ucnt = rcnt;

    ssd = socket(AF_INET, SOCK_STREAM, 0);
   
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if(bind(ssd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
    {
        perror("bind error");
        close(ssd);
    }
   
    if(listen(ssd, 5) == -1)
    {
        perror("listen error");
        close(ssd);
    }

    printf("port %d\n",port);

    rcnt++;

    pthread_mutex_unlock(&roommutex);

    struct sockaddr_in cliaddr = {0};
    int clen = sizeof(cliaddr);
    pthread_t ptr;
   
    while((user.usd = accept(ssd, (struct sockaddr *)&cliaddr, &clen)))
    {
        if(user.usd == -1)
        {
            perror("accept");
            return 0;
        }

        pthread_mutex_lock(&usermutex);
       
        user.unum = usernum;

        user.rsd = ssd;
        rm.rsd = ssd;
        rm.usd[rm.ucnt] == user.usd;
       
        rlist[rm.ucnt] = rm;
        pthread_create(&ptr, NULL, JoinChat, &user);
        pthread_detach(ptr);

        usernum++;
        rm.ucnt++;
       
        pthread_mutex_unlock(&usermutex);
    }
   
    return 0;
}

void *JoinChat(void *user)
{
    User us = *(User *)user;
    char nick[MAX_NICK_LEN]="";
    pthread_t ptr[2];

    send(us.usd, "채팅방에 오신 것을 환영합니다.\n닉네임을 입력하세요.\n", 100, 0);
    recv(us.usd, nick, sizeof(nick), 0);
    nick[strlen(nick)-1] = '\0';
    printf("%s 접속\n",nick);
   
    strcpy(us.unick, nick);

    list[us.unum] = us;

    pthread_create(&ptr[0], NULL, DeliveryMessage, &us);
    pthread_create(&ptr[1], NULL, Notice, &us);
    pthread_join(ptr[0], NULL);
    pthread_join(ptr[1], NULL);
}

void *DeliveryMessage(void *user)
{
    User us = *(User *)user;
    int i;
    char *whisp;
    char rbuf[MAX_MESSAGE_LEN];
    char sbuf[MAX_MESSAGE_LEN];

    while(recv(us.usd, rbuf, sizeof(rbuf), 0) >0)
    {
        if(!strncmp(rbuf,"/w",2))
        {
            Whisp(us, rbuf);
            continue;
        }
        else if(!strncmp(rbuf,"/f",2))
        {
            FileIO(us, rbuf);
            continue;
        }
        else
        {
            sprintf(sbuf, "%s : %s", us.unick, rbuf);
            for(i=0;i<usernum;i++)
            {
                if(list[i].usd == us.usd)
                    continue;
                if(list[i].rsd == us.rsd)
                    send(list[i].usd, sbuf, sizeof(sbuf), 0);
            }
        }

        memset(rbuf, 0, sizeof(rbuf));
        memset(sbuf, 0, sizeof(sbuf));
    }   

    ClientExit(us);
}

void FileIO(User user, char *rbuf)
{
    int fd;
    char *fio;
    char sbuf[MAX_MESSAGE_LEN];

    fio = strtok(rbuf, " ");
    fd = open(fio, O_RDONLY, 0644);
   
    while(read(fd, rbuf, sizeof(rbuf)) > 0)
    {
        if(send(user.usd, rbuf, sizeof(rbuf), 0) == -1)
        {
            perror("send");
            exit(1);
        }
    }
}

void ClientExit(User user)
{
    int i, j;
    char sbuf[MAX_MESSAGE_LEN];
   
    sprintf(sbuf, "%s님이 퇴장하였습니다.\n", user.unick);

    for(i=0;i<usernum;i++)
    {
        if(list[i].usd == user.usd)
        {
            for(j=0;j<usernum;j++)
            {
                if(list[j].usd == user.usd)
                    continue;
                if(list[j].rsd == user.rsd)
                    send(list[j].usd, sbuf, sizeof(sbuf), 0);
            }
            break;
        }
    }

    pthread_mutex_lock(&usermutex);
    for(j=i;j<usernum-1;j++)
    {
        list[j] = list[j+1];
    }
    usernum --;
    pthread_mutex_unlock(&usermutex);
}

void Whisp(User user, char *rbuf)
{
    int i;
    char *whisp;
    char sbuf[MAX_MESSAGE_LEN];

    strtok(rbuf, " ");
    whisp = strtok(NULL, " ");

    for(i=0; i<usernum; i++)
    {
        if(!strcmp(whisp,list[i].unick))
        {
            whisp = strtok(NULL, " ");
            sprintf(sbuf, "%s님의 귓속말 : %s", user.unick, whisp);
            if(list[i].rsd == user.rsd)
                send(list[i].usd, sbuf, strlen(sbuf), 0);
            break;
        }
    }
   
    if(i == usernum)
        send(user.usd, "사용자가 없습니다.\n", 30, 0);
}

void *Notice(void *user)
{
    int i;
    User us = *(User *)user;
    char sbuf[MAX_MESSAGE_LEN];
    char inbuf[MAX_MESSAGE_LEN];
   
    sprintf(sbuf, "%s님이 접속하였습니다.\n", us.unick);
    for(i=0;i<usernum;i++)
    {
        if(list[i].rsd == us.rsd)
            send(list[i].usd, sbuf, strlen(sbuf), 0);
    }

    while(1)
    {
        memset(sbuf, 0, sizeof(sbuf));
        memset(inbuf, 0, sizeof(inbuf));
        fgets(inbuf, sizeof(inbuf), stdin);
        sprintf(sbuf, "[공지] : %s\n", inbuf);
        for(i=0;i<usernum;i++)
            send(list[i].usd, sbuf, strlen(sbuf), 0);
    }
}

int ServerSetting(char *ip, int port)
{
    int ssd;
    ssd = socket(AF_INET, SOCK_STREAM, 0);
   
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(ip);

    if(bind(ssd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
    {
        perror("bind error");
        close(ssd);
        return -1;
    }
   
    if(listen(ssd, 5) == -1)
    {
        perror("listen error");
        close(ssd);
        return -1;
    }
    return ssd;
}

void SigExit(int signo)
{
    printf("서버를 종료합니다.\n");
    close(rsd);
    close(sd);
    exit(0);
}



cli.c

#include "cli.h"

int main()
{
    int port;
    char pt[MAX_MESSAGE_LEN];

    printf("포트를 입력하세요 :");
    scanf("%d", &port);
    getchar();
    signal(SIGINT, SigExit);
   
    pthread_mutex_init(&usermutex, NULL);
   
    if((sd = SockSetting(SERV_IP, port)) == -1)
    {
        perror("socket");
        return 0;
    }

    JoinChat(sd);

    return 0;
}

void JoinChat(int ssd)
{
    User user;
    pthread_t ptr[2];
    char nick[MAX_NICK_LEN]="";
    char rbuf[MAX_MESSAGE_LEN]="";

    recv(ssd, rbuf, 100, 0);
    fputs(rbuf,stdout);
    fgets(nick, sizeof(nick), stdin);
    send(ssd, nick, strlen(nick), 0);
    user.usd = ssd;
    strcmp(user.unick,nick);
   
    pthread_create(&ptr[0], NULL, RecvMsg, &user);
    pthread_detach(ptr[0]);
    pthread_create(&ptr[1], NULL, SendMsg, &user);
    pthread_detach(ptr[1]);

    while(1)
        pause();
}

void *RecvMsg(void *user)
{
    User us = *(User *)user;
    char rbuf[MAX_MESSAGE_LEN];
   
    while(1)
    {
        if(flag == 0)
        {
            recv(us.usd, rbuf, sizeof(rbuf), 0);
           
        }   
        while(recv(us.usd, rbuf, sizeof(rbuf), 0) > 0)
        {
            fputs(rbuf, stdout);
            memset(rbuf, 0, sizeof(rbuf));
        }
    }
}

void *SendMsg(void *user)
{
    User us = *(User *)user;
    char sbuf[MAX_MESSAGE_LEN];

    while(1)
    {
        fgets(sbuf, sizeof(sbuf), stdin);
        send(us.usd, sbuf, sizeof(sbuf), 0);
        if(!strncmp(sbuf, "/f", 2))
            flag = 1;

        memset(sbuf, 0, sizeof(sbuf));
    }
}

int SockSetting(char *ip, int port)
{
    int ssd;
   
    if((ssd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("sock error");
        return -1;
    }
   
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(ip);
    servaddr.sin_port = htons(port);

    if(connect(ssd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
        perror("connect");
        return -1;
    }

    return ssd;
}

void SigExit(int signo)
{
    printf("클라이언트를 종료합니다.\n");
    close(sd);
    exit(0);
}