본문 바로가기
코스웨어/15년 스마트컨트롤러

20150826 -22번-우대희-일일업무일지- 채팅프로그램2

by 알 수 없는 사용자 2015. 8. 27.
728x90
반응형

● NetWork


□ select 함수를 이용한 채팅프로그램 (3회차)

 - 종료시 한개의 client가 종료가 되도록 교체

=====================================================================================

=====================================================================================


코드 짜면서 주석으로 설명 적었습니다.

server에서의 부하를 줄이기 위해, server에서는 정보만 받고, client에서 계산 후 정보를 보낸다.

 smartsock.h

#ifndef __SMARTSOCK_H__
#define __SMARTSOCK_H__

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h> // fork 헤더

#define PORT    7773    // server PORT
#define IP      "192.168.0.122" // server IP
//#define IPc   "192.168.0.222" // client IP

#define MSG_SIZE        (BUFFER_SIZE+1) + NIC_NAME_MSG    // Message  size
#define BUFFER_SIZE     255                  // Buffer size
#define MSG_END          "\x01\x02\x03"  // 제어문자
#define MAX_USER         30                   // User counter
#define NIC_NAME_SIZE   9                 // 한글 닉네임 size
#define NIC_NAME_MSG   (9+2)             // 닉네임  Message


#endif // __SMARTSOCK_H__

=====================================================================================

=====================================================================================

server.c 

#include "smartsock.h"

int main()
{
        int isock;      // 서버 소켓 생성
        int iret;       // error 확인
        int iMsock;             // 제일 큰 소켓 번호저장
        int icsock[MAX_USER];   // client 접속 수
        unsigned int uiUser; // 접속 유저 수
        unsigned int uicnt;
        unsigned int uiTcnt;
        struct sockaddr_in st_addr; // 소켓 구조체
        char cbuffer[BUFFER_SIZE];      // 버퍼 사이즈
        char cMSG[MSG_SIZE];            // 메세지 사이즈
        char cNic[MAX_USER][NIC_NAME_SIZE];     // 한글 닉네임, 유저 수 + 9byte
        fd_set fdRead;  // 1024비트
        socklen_t uisocklen = sizeof(struct sockaddr);  //클라이언트 소켓

        isock = socket(AF_INET , SOCK_STREAM , 0);              // 서버 소켓 생성,  isock에 소켓 입력

        if(isock < 0// 소켓 생성체크
        {
                perror(" isock error");
                close(isock);
                return;
        }

        bzero(&st_addr, sizeof(st_addr));        // st_addr만큼 st_addr를 0으로 초기화
        st_addr.sin_family = AF_INET;           // IPv$ 인터넷 프론토콜
        st_addr.sin_addr.s_addr = inet_addr(IP); // 32 bit IPv4 주소
        st_addr.sin_port = htons(PORT);          // 사용할 PORT 번호

        // bind함수를 이용하여 소켓에 isock에 필요한 정보를 할당, 커널에 등록
        iret = bind(isock , (struct sockaddr *)&st_addr , sizeof(st_addr));
        // 생성한 소켓을 st_addr로 등록

        if (iret < 0// bind 실행 확인
        {
                perror(" bind error : ");
                close(isock);
                return;
        }

        iret = listen(isock, 5);  // 클라이언트 접속 요청 확인

        if (iret < 0)
        {
                perror(" listen error : "); // 대기상태 모드 설정 실패
                return;
        }

        uiUser = 0// 접속 client수 초기화.

        while(1)
        {
                FD_ZERO(&fdRead);               // 0으로 리셋
                FD_SET(0&fdRead);             // 소켓 입력 감시
                FD_SET(isock ,&fdRead); // 접속 소켓과 select를 감시

                iMsock = isock;         // 제일 큰 소켓 번호 저장

                for(uicnt = 0 ; uicnt < uiUser ; ++uicnt)       // clinet 접속자 수 카운터 for문
                {
                        FD_SET(icsock[uicnt] ,&fdRead);                 // client 감시

                        if( iMsock < icsock[uicnt] )    // 앞의 client가 나가면 맨뒤에 것을 맨앞으로 보낸다
                        {
                                iMsock = icsock[uicnt];
                        }
                }

                select( iMsock + 1&fdRead , 0 , 0 , 0 );      // 제일 높은 저수준 +1 , 읽기 주소

                if(0 != FD_ISSET( isock , &fdRead ) )   // client가 들어오는걸 체크
                {
                        // 클라이언트 접속 요청에 따라 accept으로 접속을 허락
                        icsock[uiUser] = accept(isock,(struct sockaddr *)&st_addr,&uisocklen);
                        // 접속자 정보가 들어간다

                        if (icsock[uiUser] < 0// 클라이언트 연결 실패
                        {
                                perror(" Accept error : ");
                                continue;
                        }

                        read( icsock[uiUser] , cNic[uiUser] , NIC_NAME_SIZE );  // client 닉네임 읽기
                        printf(" incoming client : [%s]님 입장\n",cNic[uiUser]); // 클라이언트 접속 연결 성공

                        // inet_ntoa 함수로 st_addr.sin_addr 구조체 안의 문자열을 출력
                        printf(" Client IP [ %s ]\n", inet_ntoa(st_addr.sin_addr));     // IP
                        write(icsock[uiUser],"[server Connect Ok]\n",sizeof("[server Connect Ok]\n"));
                        // 클라이언트에 문자 보내기

                        sprintf(cMSG,"[%s]님이 입장하였습니다.",cNic[uiUser]);  // client 접속 메세지

                        for(uiTcnt = 0 ; uiTcnt < uiUser ; ++uiTcnt)    // 소켓 메세지 전송 for문
                        {
                                //if(uicnt != uiTcnt)   // 보낸곳 제외 uiUser++ 이 밑에 있으면 없어도됨
                                write( icsock[uiTcnt] , cMSG , MSG_SIZE );      // 각 메세지를 각 client에 전송
                        }

                        uiUser++; // client가 접속하면 접속 유저수 +1
                }

                if(0 != FD_ISSET( 0 , &fdRead ) )       // 키보드 입력이  들어오는걸 체크
                {
                        iret = read( 0 , cbuffer , BUFFER_SIZE );       // 키보드입력을 버퍼에 저장
                        cbuffer[iret - 1= 0;  // 입력 끝에 Null 삽입
                        sprintf( cMSG , "공지사항 [%s]" , cbuffer);     // server에서 client에 메세지 전달

                        for(uicnt = 0 ; uicnt < uiUser ; ++uicnt)       // client 접속자 수 카운터 for문
                        {
                                write( icsock[uicnt] , cMSG , MSG_SIZE );       // 접속 client에 입력 글자 전송
                        }
                }

                for(uicnt = 0 ; uicnt < uiUser ; ++uicnt)       // client 접속자 수 카운터 for문
                {
                        if(0 != FD_ISSET( icsock[uicnt] , &fdRead ) )   // 각 소켓 정보를 들어오는걸 체크
                        {
                                read( icsock[uicnt] , cMSG , MSG_SIZE );        // 각 소켓 메세지 읽기

                                if0 == strncmp( MSG_END , cMSG , sizeof(MSG_END) ) )  // 접속 종료
                                {
                                        sprintf(cMSG , "[%s]퇴장하였습니다.",cNic[uicnt]);      // client 퇴장
                                        close(icsock[uicnt]);   // 한개의 client가 접속을 끊으면 끈긴다
                                        --uiUser;                      // 접속 유저 수 -1
                                        icsock[uicnt] = icsock[uiUser]; // 나간 client 자리에 제일 뒤 client 유저를 삽입
                                        memcpy( cNic[uicnt] , cNic[uiUser] , sizeof(NIC_NAME_SIZE) );   // 복사
                                }

                                for(uiTcnt = 0 ; uiTcnt < uiUser ; ++uiTcnt)// 소켓 메세지 전송 for문
                                {
                                                write( icsock[uiTcnt] , cMSG , MSG_SIZE );      // 각 메세지를 각 client에 전송
                                }
                        }
                }
        }

        close(isock); // 소켓을 닫는다

        for(uicnt = 0 ; uicnt < uiUser ; ++uicnt)       // client 접속자 수 카운터 for문
        {
                close(icsock[uicnt]);   // client 수 만큼 차례대로 접속 종료
        }
        return 0;
}
====================================================================================
====================================================================================

 client.c

#include "smartsock.h"

int main()
{
        int ifd;                                        // 클라이언트 소켓 생성
        int iLen;
        int iRet;
        char cBuff[MSG_SIZE];           // 버퍼 사이즈
        char cMSG[MSG_SIZE];            // 메세지 사이즈
        char cNic[NIC_NAME_SIZE];       // 닉네임 사이즈
        struct sockaddr_in stAddr;      // 소켓 구조체
        fd_set fdRead;

        ifd = socket(AF_INET, SOCK_STREAM,0);   // 클라이언트 소켓 생성,  isock에 소켓 입력

        if(-1 == ifd)
        {
                perror(" Sock() Call Error\n");
                return 100;
        }

        bzero(&stAddr, sizeof(stAddr));                         // stAddr만큼 stAddr를 0으로 초기화

        stAddr.sin_family = AF_INET;                            // IPv$ 인터넷 프론토콜
        stAddr.sin_addr.s_addr = inet_addr(IP);         // 32 bit IPv4 주소
        stAddr.sin_port = htons(PORT);                  // 사용할 PORT 번호

        iLen = sizeof(stAddr);

        while(1)
        {
                printf(" 닉네임을 입력하세요 : ");
                fflush(stdout); // 보안 때문에 scanf를 쓰지 않는다
                iRet = read( 0 , cNic , NIC_NAME_SIZE );        // 닉네임을 사이즈 만큼 읽는다

                if2 > iRet )  // 0 [Ctrl+d], 1 [Enter]  감지
                {
                         continue;
                }

                cNic[iRet - 1= 0;     // 읽어드린 닉네임 뒤에 Null
                //fflush(stdin);                // 버퍼 남은걸 비운다
                break;
        }

        iRet = connect(ifd , (struct sockaddr *) &stAddr , iLen );

        if(-1 == iRet)
        {
                perror(" Connect() Call Error\n");
                close(ifd);
                return 200;
        }

        write(ifd , cNic , NIC_NAME_SIZE );     // 닉네임을 client에 보낸다

        while(1)
        {
                FD_ZERO(&fdRead);      // 0으로 리셋
                   FD_SET(0&fdRead);         // 키보드 입력 감시
                FD_SET(ifd, &fdRead);    // 받는 소켓과 select를 감시
                select( ifd+1 , &fdRead , 0 , 0 , 0);   // 제일 높은 저수준 +1 , 읽기 주소

                if(0 != FD_ISSET( 0 , &fdRead ) ) // 키보드 입력시
                {
                        iRet = read(0 , cBuff , MSG_SIZE);      // 키보드 입력 받은 만큼 읽기

                        if(0 == iRet)   // 연결 종료
                        {
                                break;
                        }

                        cBuff[iRet - 1= 0;                            // 키보드 입력 문자 뒤 0 삽입
                        sprintf(cMSG , "[%s]%s", cNic , cBuff );        // cMsg에 저장
                        write(ifd , cBuff , MSG_SIZE);          // 키보드 입력 문자 보내기
                        printf(" [%s] MSG : %s \n",cNic , cBuff); // 보낸 문자 printf
                        //if(0 == strncmp( cBuff , MSG_END , strlen(MSG_END) ) ) // 연결 종료
                }

                if(0 != FD_ISSET( ifd , &fdRead ) )     // ifd 소켓 송신
                {
                        read(ifd , cMSG , MSG_SIZE);            // 받는 메세지 읽기
                        printf(" [Server MSG] : %s \n", cMSG);  // 받은 메세지 printf
                }
        }

        write( ifd , MSG_END , sizeof(MSG_END) ); // 세련된 연결종료
        close(ifd);
        return 0;
}

=====================================================================================

=====================================================================================

<실행 결과>

client가 종료시 밑의 표처럼 정렬된다.


안향진씨 코드 받아서 쓰는게 좋을듯 합니다.

728x90