#include "smart.h"
int main() {
unsigned char ucBuff[500]; int servSock; // 랑데뷰 소켓번호를 받기위한 변수 int clntSock[MAXUSER]; // 커뮤니케이션 소켓번호를 받기위한 배열 int iMaxSock; //고려할 최대 식별자수(제일 큰 소켓번호+1) 저장을 위한 변수 unsigned int uiUser; //접속자 수를 카운트하기 위한 변수 int iTempSock; //소켓번호를 임시로 담기 위한 변수 int iCnt; int iCnt2; int iRet; struct sockaddr_in echoServAddr; // 랑데뷰 소켓 정보를 넣기 위한 구조체 변수 struct sockaddr_in echoClntAddr; // 커뮤니케이션 소켓 정보를 넣기 위한 구조체 변수 unsigned short echoServPort; // Port 번호를 넣기 위한 변수 unsigned int clntLen;
fd_set fsStatus; //fd_set : 128바이트 크기의 자료형이고 //식별자리스트를 저장하기 위한 변수를 선언할때 사용 echoServPort = 9999; // Port 번호
servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 소켓 생성하여 소켓번호를 변수에 저장 (랑데뷰 소켓), 랑데뷰 : 만남 // AF_INET(Address family) = PF_INET(Protocol family) = 2 (인터넷 Protocol family) // SOCK_STREAM : 신뢰성있는 바이트 스크림 전송을 위한 소켓, 속도느림, TCP // SOCK_DGRAM : 최선형의 데이터그램 소켓, 속도빠름, 파일깨질수 있음(신뢰성낮음), UDP
if( servSock < 0 ) // socket이 생성되지 못하면 -1 반환하므로 에러메시지 출력하고 종료 { printf("socket() failed\n"); return 0; }
memset( &echoServAddr, 0, sizeof(echoServAddr)); // echoServAddr를 0으로 초기화 // memset(메모리 셋팅), 특정 메모리를 1바이트 단위로 원하는 데이터로 채워 넣을때 echoServAddr.sin_family = AF_INET; // 2, Internet protocol echoServAddr.sin_addr.s_addr = htonl (INADDR_ANY); // BIG Endian으로 바꿔서 넣어줌 // 0 또는 INADDR_ANY를 넣으면 자동으로 찾아서 IP 넣어줌, 아님 직접 자신의 IP를 넣어주어야 함. //htonl : host to network long(자료형크기) , 네트워크 표준은 Big Endian 임 // INADDR_ANY = 0 echoServAddr.sin_port = htons (echoServPort); // Port 번호 Big Endian 으로 바꿔서 넣어줌 // htons : host to network short(자료형크기)
iRet = bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)); /* 클라이언트가 서버에 접속하기 위해서 서버의 소켓은 지정된 내부 주소(서버 자신의 IP주소) 및 포트 를 가지고 있어야 하는데 이일을 하는 함수가 bind() 임.*/ // 구조체변수에 넣어 놓은 소켓 값들(포트번호)을 생성해 놓은 소켓과 결합
if(iRet < 0) { close(servSock);//bind 실패하면 열어 놓은 소켓 닫음 printf("bind() failed\n"); return 0; }
iRet = listen(servSock, MAXPENDING); // 대기할소켓, 대기자수(MAXPENDING) // 클라이언트들로 부터의 연결 요구를 기다리도록 // TCP 연결 요구들이 프로그램에 의해 처리되고 받아들여 질수 있도록 대기자 수만큼 저장함 if(iRet < 0) { close(servSock); printf("listen() failed\n"); return 0; }
clntLen = sizeof(echoClntAddr); // 구조체 변수의 크기저장 iMaxSock = servSock+1;// 가장 큰 소켓식별번호 + 1 = 고려해야할 최대 식별자수
uiUser = 0; // 현재 접속자수 0으로 초기화
while(1) {
FD_ZERO(&fsStatus); // 전체 0으로 clear FD_SET(0, &fsStatus); // 해당 식별자(키보드) set(1) 함. FD_SET(servSock, &fsStatus); // 해당 식별자(소켓) set(1) 함. for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2) { FD_SET(clntSock[iCnt2-1], &fsStatus); // 해당 식별자(소켓) set(1) 함. if(iMaxSock<=clntSock[iCnt2-1]) // 소켓 번호가 기존 식별자번호 보다 크면 고려해야할 식별자수 변경시킴 { iMaxSock = (clntSock[iCnt2-1])+1; } } iRet = select(iMaxSock, &fsStatus, 0, 0, 0); //식별자 갯수(제일큰소켓번호+1), read 이벤트 발생?, write이벤트 발생?, 에러이벤트 발생?, 대기시간(time out) //select() 는 식별자 리스트들을 수정하여 준비된(이벤트가 발생한) 식별자들에 해당하는 위치만을 지정함(set) //NULL 을 넘기면 그 형의 입출력을 무시하도록 만듬, timeout : NULL(이벤트가 발생할때까지 무한대기) //select()는 성공시 : 입출력 준비가 된 전체 식별자 들의 개수를 반환, 에러시:-1, 타임아웃시 0 반환 /* 대기시간은 struct timeval tv; 구조체 변수를 만들어서 tv.tc_sec = 5; 이런 식으로 값을 넣은 뒤 구조체변수의 주소를 인자(&tv)로하여 줄수있음.*/ if(0 > iRet) //select() 에러 발생시 { for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2) { write(clntSock[iCnt2-1], ENDMSG, sizeof(ENDMSG)); // ENDMSG(종료 메시지) 값을 접속된 모든 클라이언트에게 보냄 } break; }
if( 1 == FD_ISSET(servSock, &fsStatus)) // 랑데뷰소켓 식별자 read 이벤트 발생할 때 {
iTempSock = accept(servSock, (struct sockaddr *) &echoClntAddr, &clntLen ); // echoClntAddr은 클라이언트의 주소가 저장됨 // 소켓이 만들어져 iTempSock 변수에 소켓번호 저장됨 (커뮤니케이션 소켓)
if(iTempSock < 0) { printf("accept() failed\n"); continue; }
printf("Handling client IP %s\n", inet_ntoa(echoClntAddr.sin_addr)); // 클라이언트의 IP를 출력 //inet_ntoa 는 network to ascii로 인자(변수)값을 아스키 코드 값으로 바꿔서 //IP주소값 형태의 문자열로 만들어 문자열 주소값 반환 // char *inet_ntoa(struct in_addr in);
printf("Handling client Port %d\n", ntohs(echoClntAddr.sin_port) ); // 클라이언트의 Port 출력 , ntohs로 다시 little Endian 으로 바꿔줌 printf("Socket Number %d\n", iTempSock); //소켓번호 출력 if( MAXUSER <= uiUser )//접속자수 제한 { close(iTempSock); continue; } clntSock[uiUser] = iTempSock; // 새로 접속한 소켓(식별자)번호 배열에 저장 ++uiUser; // 현재 접속자수 1 증가 printf("현재 접속자수 : %d 명\n",uiUser);
} if( 1 == FD_ISSET(0, &fsStatus)) // 0번 식별자(키보드)로 Read 이벤트 발생할때 {
iRet = read(0, ucBuff, 500); // 키보드로 받은 값을 버퍼(ucBuff)에 저장 // 키보드의 입력이 발생했을때 그 값들이 운영체제 버퍼에 들어가고 // 키보드이벤트가 발생하면 read()가 키보드 입력을 받지 않아도 // 운영체제의 버퍼값을 바로 읽어들인다. if(CLTEND == ucBuff[0]) // 키보드로 첫번째 값을 ctrl + E 입력했을때 {
for(iCnt = uiUser; 0 < iCnt ; --iCnt) { write(clntSock[iCnt-1], ENDMSG, sizeof(ENDMSG)); // 접속된 모든 클라이언트에게 ENDMSG(클라이언트를 종료시키는 값)을 보냄 }
break; // 반복문 탈출하여 소켓 닫고 프로그램 종료
} for(iCnt = uiUser; 0 < iCnt ; --iCnt) { write(clntSock[iCnt-1], ucBuff, iRet); // 버퍼의(ucBuff) 값을 클라이언트에게 보냄 } } else {
for(iCnt = uiUser; 0 < iCnt ; --iCnt) {
//어떤 클라이언트소켓 식별자로부터 이벤트 발생했는지? if( 1 == FD_ISSET(clntSock[iCnt-1], &fsStatus))
{ iRet = read(clntSock[iCnt-1], ucBuff, 500); // 해당 커뮤니케이션 소켓에서 값을 읽어와 버퍼에 넣음 if(0 == strcmp(LOGOUT,ucBuff))// 클라이언트로 부터 로그아웃 메시지가 왔을때 { close(clntSock[iCnt-1]);// 로그아웃 메시지를 보내온 소켓번호 닫음 --uiUser;// 현재 접속자수 -1 clntSock[iCnt-1] = clntSock[uiUser]; // 제일 뒤의 소켓 번호를 빈(로그아웃된) 배열 자리에 채움 for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2) { write(clntSock[iCnt2-1],OUTMSG, sizeof(OUTMSG)); // 아웃메시지를 모든 클라이언트에게 보냄 } printf("["OUTMSG"]\n");
// 아웃메시지 출력 , "" 하면 개행문자가 붙었더라도 붙어서 출력된다.
continue; } printf("[client%d: ",iCnt-1); // 클라이언트 번호 출력 fflush(stdout);// 버퍼 비움
write(1, ucBuff, iRet-1); // 버퍼의(ucBuff) 값을 모니터에 출력 printf("]\n"); for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2) { write(clntSock[iCnt2-1], ucBuff, iRet); // 버퍼의(ucBuff) 값을 모든 클라이언트에게 보냄 } } } }
} close(servSock); for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2) { close(clntSock[iCnt2-1]); //소켓모두 닫고 프로그램 종료 } return 0;
}

|