#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; // 식별자 값들을 저장할 변수
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;
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], "q", 2); // q(종료명령)를 클라이언트에게 보냄
}
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 으로 바꿔줌
if( MAXUSER <= uiUser ) // 접속자수 제한
{
close(iTempSock);
continue;
}
clntSock[uiUser] = iTempSock; // 클라이언트 소켓번호 배열에 넣음
++uiUser; // 현재 접속자수 1 증가
printf("현재 접속자수 : %d 명\n",uiUser);
}
else if( 1 == FD_ISSET(0, &fsStatus)) // 0번 식별자(키보드)로 읽기 가능할때
{
iRet = read(0, ucBuff, 500); // 키보드로 받은 값을 버퍼(ucBuff)에 저장
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);
//해당 커뮤니케이션 소켓에서 값을 읽어와 버퍼에 넣음
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) 값을 클라이언트에게 보냄
// 서버와 연결된 다른 클라이언트에게 전달됨
}
if('q' == ucBuff[0])
{
break;
}
}
}
if(ucBuff[0] == 'q') // 버퍼(ucBuff)의 첫번째 값이 'q' 이면 탈출
{
break;
}
}
}
close(servSock);
for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2)
{
close(clntSock[iCnt2-1]); //소켓모두 닫고 프로그램 종료
}
return 0;
}