==================================Outline====================================
Select 함수를 사용한 채팅프로그램
- 초과 접속자 발생 시 차단
- ctrl + d 입력 시 정상종료
- 포트 설정
- ctrl + c 정상종료
----------------------------------------------------------------------------
초과 접속자 발생 시 차단
지난 시간에 이어...
접속자 제한을 3으로 하자. 서버에 접속자가 초과 된 상황을 만들기 위한 작업이다.
** 코딩내용을 수정할 때 우선 코딩의 전체 흐름을 살펴본다.
수정한 코드가 전체 프로그램에 미치는 영향을 파악한 후 작업을 진행해야 한다.
눈에 실핏줄 터지기 싫으면...
smartsock.h에서 MAX_USER 값을 30에서 3으로 줄인다.
![](https://t1.daumcdn.net/cfile/tistory/26522F3B55DEC7392B)
접속 제한 숫자를 초과하는 클라이언트의 접속을 막아야 한다. 하지만 영문도 모르고 접속이 안되는 접속자의 속상함 방지를 위해, 일단 접속은 허가한 후 넌 팅길거라는 메시지를 보내준다. 초과 접속자의 접속 처리를 위해 접속자의 대화 소켓을 담는 icSock변수를 icSock[MAX_USER+1]로 처리해준다.
![](https://t1.daumcdn.net/cfile/tistory/2108B83B55DEC73B03)
accept후 조건문을 만들어 uiUser의 크기가 MAX_USER와 크거나 같을 때 사용자의 접속을 끊고 안내 메시지를 보내준다.
![](https://t1.daumcdn.net/cfile/tistory/254E7C3B55DEC73C2F)
컴파일 후 클라이언트를 접속시키면 서버와 제한 숫자 내 클라이언트는 정상 작동하지만, 초과 접속한 클라이언트에게 무한반복으로 버퍼에 있는 메시지가 출력된다. 초과인원이 출입 시 동작하는 if문으로 간다. 초과 접속된 클라이언트에게 종료 메시지를 보내주고 클라이언트에서는 해당 메시지를 받으면 접속이 끊어지도록 코딩하자.
![](https://t1.daumcdn.net/cfile/tistory/217AA93B55DEC73E0C)
<server.c>
![](https://t1.daumcdn.net/cfile/tistory/2449BE3B55DEC73F32)
<client.c>
** 캡쳐된 코드 소속이 궁금할 때 구분법: iFd쓰면 client, icSock/iSock쓰면 server
Ctrl + d 입력 시 정상종료
서버에서 Ctrl + D를 누르면 모든 클라이언트에게 종료 메시지를 출력하고 서버를 종료하도록 처리해주자.
![](https://t1.daumcdn.net/cfile/tistory/2179D33B55DEC7410E)
<client.c>
포트 설정
서버or클라이언트 실행 시 포트번호를 설정할 수 있도록 해주자.
우선 실행파일이 인자를 받을 수 있도록 메인함수를 고쳐준다. 서버부터.
![](https://t1.daumcdn.net/cfile/tistory/2307E13B55DEC74204)
실행 시 포트번호를 인자로 받게 되고 이것이 유효한 숫자일 경우 포트번호를 변경해준다.
![](https://t1.daumcdn.net/cfile/tistory/2202F83955DEC74425)
bind를 위해 접속정보를 세팅하는 구조체로 가서 PORT번호를 수정해준다.
![](https://t1.daumcdn.net/cfile/tistory/211FD03955DEC74510)
클라이언트에서도 똑같이 작업해준다.
![](https://t1.daumcdn.net/cfile/tistory/2108733955DEC74719)
Ctrl + c 정상종료
서버나 클라이언트에서 정상종료가 아닌 ctrl+c를 눌렀을 경우에도 클라이언트가 고통 받지 않고 정상종료 하도록 코딩해주자.
ctrl+c로 눌렀을 경우, 대화소켓으로 받은 read의 반환 값은 0이 된다. 이를 활용하여 read값이 0일 경우 정상종료 시켜주자.
![](https://t1.daumcdn.net/cfile/tistory/241BED3955DEC74913)
<server.c>
![](https://t1.daumcdn.net/cfile/tistory/2715B53955DEC74B17)
<client.c>
채팅 서버, 클라이언트 만들기는 여기서 끝낸다. 지금까지 코딩한 채팅 프로그램이 성에 안차면 멀티룸 채팅에 도전해 보시씨요.
** 멀티룸 채팅 : 채팅 방을 여러 개 만들어서 유저가 선택적으로 접속
/*** 소스 ***/
#include "smartsock.h"
int main(int iRtn, char *cpCMD[]) { int iFd; struct sockaddr_in stAddr; int iLen; char cBuf[BUF_SIZE]; fd_set fdRead; char cMSG[MSG_SIZE]; char cNick[NIC_NAME_SIZE]; unsigned short usPORT=PORT; //PORT == 7777
if(iRtn == 2) { iFd = atoi(cpCMD[1]); if(1024 < iFd) { if(65535 > iFd) { usPORT=iFd; printf("PORT no. %d\n", usPORT); } } }
/*** Nick Name in ***/ while(1) { printf("Please Input Nick Name\n"); fflush(stdout); iRtn = read(0, cNick, NIC_NAME_SIZE); if(iRtn < 2)//그냥 엔터키를 눌렀을 경우. **ctl + d 누르면 iRtn = 0 continue;
cNick[iRtn-1] = 0; break;
} /*** socket ***/
iFd = socket(AF_INET, SOCK_STREAM, 0); if(-1 == iFd) { perror("socket:"); return 100; }
/*** structure setting ***/ stAddr.sin_family = AF_INET; stAddr.sin_addr.s_addr = inet_addr(IP); stAddr.sin_port = htons(usPORT);
iLen = sizeof(struct sockaddr_in);
/*** connect ***/ iRtn = connect(iFd, (struct sockaddr *)&stAddr, iLen); if(-1 == iRtn) { perror("connect:"); close(iFd); return 200; }
write(iFd, cNick, NIC_NAME_SIZE);
while(1) { FD_ZERO(&fdRead); FD_SET(0, &fdRead); FD_SET(iFd, &fdRead); select(iFd+1,&fdRead, 0, 0, 0);
if(0 != (FD_ISSET(0, &fdRead) ) ) { iRtn = read(0, cBuf, BUF_SIZE); if(iRtn == 0) { write(iFd, MSG_END, sizeof(MSG_END)); break; } cBuf[iRtn - 1] = 0; sprintf(cMSG, "[%s]%s ", cNick, cBuf); write(iFd, cMSG, MSG_SIZE);
printf("[Send MSG]: [%s]\n", cBuf); } if(0 != (FD_ISSET(iFd, &fdRead) )) { iRtn = read(iFd, cMSG, sizeof(cMSG)); if(iRtn == 0) { printf("Server does not respond\n"); break; } if( 0 == strncmp(cMSG, MSG_END, sizeof(MSG_END) )) { break;
} printf("[%s]\n", cMSG); } }
/*** read & write ***/ //memset(cBuf, 0, BUF_SIZE); //iRtn = read(0, cBuf, BUF_SIZE);
close(iFd); return 0; }
|
<client.c>
#include "smartsock.h" #include <unistd.h>
int main(int iRtn, char *cpCMD[]) { int iSock; //소켓 함수의 반환 값을 받는 변수 int icSock[MAX_USER+1]; //accept의 반환 값을 받는 변수 struct sockaddr_in stAddr; socklen_t uiSockLen=sizeof(struct sockaddr); char cBuf[BUF_SIZE]; const char * cP; fd_set fdRead; unsigned int uiUser; unsigned int uiCnt, uiCnt2; int iMSock; //store greatest number in file descriptors char cNick[MAX_USER][NIC_NAME_SIZE]; char cMSG[MSG_SIZE]; unsigned short usPORT=PORT; //PORT == 7777
if(iRtn == 2) { iSock = atoi(cpCMD[1]); if(1024 < iSock) { if(65535 > iSock) { usPORT = iSock; printf("PORT no. %d\n", usPORT); } } }
iSock = socket(AF_INET, SOCK_STREAM, 0); //AF_INET = 2, if(0 > iSock) { perror("socket : "); return -1; } // stAddr구조체에 socket연결을 위한 필수 정보 입력 setting bzero(&stAddr, sizeof(stAddr)); //구조체 비우기(0으로 채우기) stAddr.sin_family = AF_INET; //#define AF_INET 2 /* IP protocol family. */ stAddr.sin_addr.s_addr = inet_addr(IP); //IP와 PORT값은 헤더파일에 정의되어 있다. stAddr.sin_port = htons(usPORT);
iRtn = bind(iSock, (struct sockaddr *)&stAddr,sizeof(stAddr)); if(iRtn < 0) { perror("bind : "); close(iSock);
return -2; } iRtn = listen(iSock, 5); if(iRtn != 0) { perror("listen : "); close(iSock);
return -3; } uiUser = 0; while(1) { // setting fd_set FD_ZERO(&fdRead); FD_SET(0, &fdRead); FD_SET(iSock, &fdRead); iMSock = iSock;
for(uiCnt = 0; uiCnt < uiUser; ++uiCnt) //사용자가 접속하면 해당 Fd를 set해주고 가장 높은 fd값을 저장 { FD_SET(icSock[uiCnt], &fdRead); //FD_SET(iSock, &fdRead); if(iMSock < icSock[uiCnt]) { iMSock = icSock[uiCnt]; } } select((iMSock+1), &fdRead, 0, 0, 0);//select 함수를 사용하여 감시해준다.(입력이 있을때까지 무한대기) if( 0 != FD_ISSET(iSock, &fdRead) ) //지정된 소켓 번호가 있으면 대화 소켓 번호를 받아저장하고 user+ { icSock[uiUser] = accept(iSock, (struct sockaddr *)&stAddr, &uiSockLen); //접속자의 정보가 stAddr에 입력된다. if(icSock[uiUser] < 0) { perror("Accept : "); continue; } if(uiUser >= MAX_USER) { read(icSock[uiUser], cBuf, sizeof(cBuf)); sprintf(cMSG, "Server is not vacant"); write(icSock[uiUser], cMSG, sizeof(cMSG)); write(icSock[uiUser], MSG_END, sizeof(MSG_END)); close(icSock[uiUser]);
printf("Server is not vacant [%s]\n", cBuf); printf("Client iP :%s\n", inet_ntoa(stAddr.sin_addr)); continue; } read(icSock[uiUser], cNick[uiUser], NIC_NAME_SIZE); printf("Incoming Client :[%s] \n", cNick[uiUser]); printf("Client IP :%s\n", inet_ntoa(stAddr.sin_addr)); write(icSock[uiUser], "Welcome :)", sizeof("Welcome :)")); sprintf(cMSG, "[%s]님이 입장하셨습니다.",cNick[uiUser] ); for(uiCnt2 = 0; uiCnt2 < uiUser; ++uiCnt2) { write(icSock[uiCnt2] ,cMSG, MSG_SIZE); } ++uiUser;
} if(0 != FD_ISSET(0, &fdRead)) //서버에서 키보드 입력 받은 내용 클라이언트에게 보내기 { iRtn = read(0, cBuf, BUF_SIZE); if(iRtn == 0) { for(uiCnt = 0; uiCnt < uiUser; ++uiCnt) { sprintf(cMSG, "Server is aborted"); write(icSock[uiCnt], cMSG, sizeof(cMSG)); write(icSock[uiCnt], MSG_END, sizeof(MSG_END)); } break; } sprintf(cMSG, "[공지사항]:[%s]", cBuf); for(uiCnt = 0; uiCnt < uiUser; ++uiCnt) { write(icSock[uiCnt], cMSG, MSG_SIZE);//모든 사용자에게 보낸다. } } for(uiCnt = 0; uiCnt < uiUser; ++uiCnt) { if( 0 != FD_ISSET(icSock[uiCnt], &fdRead)) { iRtn = read(icSock[uiCnt], cMSG, MSG_SIZE); if( ( 0 == strncmp(cMSG, MSG_END, sizeof(MSG_END) )) || 0 == iRtn) { sprintf(cMSG, "[%s]님이 퇴장하셨습니다.", cNick[uiCnt]); close(icSock[uiCnt]); --uiUser; icSock[uiCnt] = icSock[uiUser]; memcpy(cNick[uiCnt], cNick[uiUser], sizeof(NIC_NAME_SIZE)); } for(uiCnt2 = 0; uiCnt2 < uiUser; ++uiCnt2) { write(icSock[uiCnt2] ,cMSG, MSG_SIZE); } } }
} for(uiCnt = 0; uiCnt < uiUser; ++uiCnt) { close(icSock[uiCnt]); } close(iSock); return 0; }
|
<server.c>
#ifndef __SMARTSOCK_H__ #include <sys/select.h> #define __SMARTSOCK_H__
#include <stdio.h> #include <string.h> #include <strings.h> // socket & bind & listen & accept & connect #include <sys/types.h> #include <sys/socket.h>
// sockaddr_in #include <netinet/in.h>
// read & write #include <unistd.h>
// htonl #include <arpa/inet.h>
// errno, perror #include <errno.h>
// open #include <fcntl.h> #include <sys/stat.h>
//select #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/select.h>
#define PORT 7777 #define IP "192.168.0.173"
#define MAX_USER 3 #define NIC_NAME_SIZE 9 #define NIC_NAME_MSG (9+2)
#define BUF_SIZE 255 //only message #define MSG_SIZE (BUF_SIZE+1+NIC_NAME_MSG) //message + Nick Name #define MSG_END "\x01\x02\x03"
#endif /* __SMARTSOCK_H__ */
|
<smartsock.h>
/*** 파일 ***/
client.c
server.c
smartsock.h