I/O 멀티 플렉싱
- Blocking I/O
- 블로킹 모드에서 I/O처리는 작업이 완료될 때까지 기다려야 하므로 비동기적인 작업이 불가능
- 일대일 통신 또는 프로그램이 한가지 작업만 하면 되는 경우 블로킹 모드로 프로그램 작성 - Non - Blocking I/O
- 일반적인 서버 프로그램들은 다수의 클라이언트의 요청들을 처리해야 함
- 통신 상대가 여럿 또는 여러 작업을 병행하기 위해서는 non-blocking 또는 asynchrounous(비동기)모드를 사용
_ Non - Blocking 모드는 시스템 콜이 성공적으로 실행될 때까자ㅣ 루프를 돌면서 확인(Pollin)
Select 함수
- select 함수의 기능과 호출 순서
- Step One 에서는 관찰의 대상을 묶고, 관찰의 유형을 지정
- Step Two에서는 관찰 대상의 변화를 묻는다.
- Step Three에서는 물음에 대한 답을 듣는다.
- Select 함수
- 관찰의 대상이 되는 디스크립터의 수는 maxfd
- 2,3,4번쨰 인자를 통해서 전달된 관찰의 대상중에서 각각 입력, 출력, 또는 오류가 발생 시 select 함수 반환
- timeout을 지정해 무조건 반환이 되는 시간 지정 가능
- select 함수호출 이후의 결과확인
- select함수호출 이후에는 변화가 발생한(입력 받은 데이터가 존재하거나 출력이 가능한 상황 등), 소켓의 디스크립터만 1로 설정 , 나머지 모두 0 으로 초기화
멀티플렉싱 서버 분석
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#define BUF_SIZE 1024
void ErrorHandling(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAdr, clntAdr;
TIMEVAL timeout;
fd_set reads, cpyReads; //관찰 대상
int adrSz;
int strLen, fdNum, i;
char buf[BUF_SIZE];
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
ErrorHandling("WSAStartup() error!");
hServSock=socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family=AF_INET;
servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
servAdr.sin_port=htons(atoi(argv[1]));
if(bind(hServSock, (SOCKADDR*) &servAdr, sizeof(servAdr))==SOCKET_ERROR)
ErrorHandling("bind() error");
if(listen(hServSock, 5)==SOCKET_ERROR)
ErrorHandling("listen() error");
FD_ZERO(&reads); //select 함수의 관찰 대상이 되는 디스크립터들을 0으로 초기화
// hservSock는 리스닝 소켓이므로 연결요청이 오는지 확인학기 위함
FD_SET(hServSock, &reads); //hServScok을 관찰대상으로 read주소에 저장
while(1)
{
//select 함수의 호출이 끝나면 변화가 발생한 디스크립터를 제외한 나머지는 0이됨
//이는 삭제를 의미
//따라서 디스크립터 정보들을 복사
cpyReads=reads;
timeout.tv_sec=5;
timeout.tv_usec=5000;
//select함수 error 처리
//select함수는 block함수로 time이 만료되면 0을 리턴
//이는 윈도우에서는 소켓정보가 다 날라가기때문
if((fdNum=select(0, &cpyReads, 0, 0, &timeout))==SOCKET_ERROR)
break; //select함수의 반환값을 보고 이것이 타임아웃에 의한 반환이라면 다시 select함수를 호출
//readset 즉 어떤 디스크립터(배열 내에)에도 변화가 없음을 의미
//따라서 타임아웃 재설정하고 재호출하기 위해 continue
if(fdNum==0)
continue;
//파일 디스크립터 내에 상태가 변경되었을 경우 (연결 또는 데이터 수신)
//상태가 변경되면 변경되지 않은 디스크립터는 삭제
for(i=0; i<reads.fd_count; i++)
{
//원본의 소켓 배열변수를 비교확인하여 비어있는지 확인
if(FD_ISSET(reads.fd_array[i], &cpyReads))
{
//변경된 디스크립터가 hServScock 이라는 것은 연결요청을 받았다는 의미
if(reads.fd_array[i]==hServSock) // connection request!
{
adrSz=sizeof(clntAdr);
hClntSock=
accept(hServSock, (SOCKADDR*)&clntAdr, &adrSz);
FD_SET(hClntSock, &reads); //관찰대상 소켓으로 등록
printf("connected client: %d \n", hClntSock);
}
//hServSock이 아니라는 것은 데이터 수신을 의미
else // read message!
{
//데이터를 읽어 EOF면 관찰대상에서 삭제하고 연결 종료
strLen=recv(reads.fd_array[i], buf, BUF_SIZE-1, 0);
if(strLen==0) // close request!
{
FD_CLR(reads.fd_array[i], &reads);
closesocket(cpyReads.fd_array[i]);
printf("closed client: %d \n", cpyReads.fd_array[i]);
}
//echo 서비스
else
{
send(reads.fd_array[i], buf, strLen, 0); // echo!
}
}
}
}
}
closesocket(hServSock);
WSACleanup();
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
- 연결 요청과 일반적이 데이터 전송의 차이점은 전송되는 데이터의 종류에 있다.
- 연결요청ㅇ도 데이터의 수신으로 구분이 되어서 select 함수의 호출결과를 통해서 확인이 가능하다.
- 따라서, 리스닝 소켓도 관찰의 대상에 포함을 시켜야한다.