251020

lililllilillll·2025년 10월 20일

개발 일지

목록 보기
330/350

✅ 한 것들


  • 윤성우의 열혈 TCP/IP 소켓 프로그래밍


📖 윤성우의 열혈 TCP/IP 소켓 프로그래밍


Chapter 12 IO 멀티플렉싱 (Multiplexing)

12-1 IO 멀티플렉싱 기반의 서버

멀티 프로세스

  • 요구 연산량과 메모리 공간이 큰 편이다.
  • 프로세스 간 데이터 주고 받으려면 IPC 같은 복잡한 방법 써야 한다

멀티플렉싱 : 프로세스 하나가 여러 클라에게 데이터 전송

12-2 select 함수의 이해와 서버의 구현

select()

  • 여러 파일 디스크립터 동시 관찰 가능
    • 변화를 관찰할 디스크립터를 1로 설정한다
    • 변화가 발생한 파일 디스크립터 비트만 1로 남아있다
  • 관찰 가능한 이벤트들
    • 수신한 데이터를 지니고 있는 소켓이 존재하는가?
    • 블로킹되지 않고 데이터 전송 가능한 소켓은 무엇인가?
    • 예외상황이 발생한 소켓은 무엇인가?
  • 호출 과정
    • Step 1
      • 파일 디스크립터 설정
      • 검사 범위 지정
      • 타임아웃 설정
    • Step 2 : select() 호출
    • Step 3 : 호출 결과 확인

파일 디스크립터의 설정

파일 디스크립터를 모을 땐 관찰항목(수신, 전송, 예외)에 따라서 구분해서 모아야 한다.
이를 위해 fd_set형 변수를 사용한다.

fd_set 값 편집 매크로 함수

  • FD_ZERO() : 모든 비트 0 초기화
  • FD_SET() : 파일 디스크립터 정보 등록
  • FD_CLR() : 파일 디스크립터 정보 삭제
  • FD_ISSET() : 파일 디스크립터 정보 있으면 양수 반환

검사(관찰) 범위 지정과 타임아웃 설정

관찰 범위 : 가장 큰 파일 디스크립터 값에 1 더함

타임아웃 설정

  • 타임아웃 시간 : timeval 자료형으로 지정
  • 타임아웃 안 하면 값 반환될때까지 무한정 블로킹
  • 타임아웃 반환은 0
  • 타임아웃 설정 안 하고 싶으면 인자에 NULL 전달

select 함수 호출 예제

int main(int argc, char *argv[])
{
	fd_set reads, temps;
	int result, str_len;
	char buf[BUF_SIZE];
	struct timeval timeout;

	FD_ZERO(&reads);
	FD_SET(0, &reads); // 0 is standard input(console)
	
	/*
	timeout.tv_sec=5;
	timeout.tv_usec=5000;
	*/

	while(1)
	{
		temps=reads;
		timeout.tv_sec=5;
		timeout.tv_usec=0;
		result=select(1, &temps, 0, 0, &timeout);
		if(result==-1)
		{
			puts("select() error!");
			break;
		}
		else if(result==0)
		{
			puts("Time-out!");
		}
		else
		{
			if(FD_ISSET(0, &temps))
			{
				str_len=read(0, buf, BUF_SIZE);
				buf[str_len]=0;
				printf("message from console: %s", buf);
			}
		}
	}
	return 0;
}

  • 표준 입력을 받으면 값을 읽어서 출력하는 예제
  • reads를 temps에 복사해두는 이유 : select 호출 끝나면 변화 생긴 파일 디스크립터 제외하면 다 0 되니까
  • 주석친 부분에서 timeout 설정하면 안되는 이유 : select 함수 호출 끝나면 타임아웃 발생하기까지 남은 시간으로 바뀌기 때문

멀티플렉싱 서버 구현

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	struct timeval timeout;
	fd_set reads, cpy_reads;

	socklen_t adr_sz;
	int fd_max, str_len, fd_num, i;
	char buf[BUF_SIZE];
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));

	if(bind(serv_sock,(struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

	FD_ZERO(&reads);
	FD_SET(serv_sock, &reads);
	fd_max=serv_sock;

	while(1)
	{
		cpy_reads=reads;
		timeout.tv_sec=5;
		timeout.tv_usec=5000;

		if((fd_num=select(fd_max+1,&cpy_reads,0,0,&timeout))==-1)
			break;
		if(fd_num==0)
			continue;

		for(i=0; i<fd_max+1; i++)
		{
			if(FD_ISSET(i, &cpy_reads))
			{
				if(i==serv_sock) // connection request!
				{
					adr_sz=sizeof(clnt_adr);
					clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
					FD_SET(clnt_sock, &reads);
					if(fd_max<clnt_sock)
						fd_max=clnt_sock;
					printf("connected client: %d \n", clnt_sock);
				}
				else // read message
				{
					str_len=read(i, buf, BUF_SIZE);
					if(str_len==0) // close request;
					{
						FD_CLR(i, &reads);
						close(i);
						printf("closed client: %d \n", i);
					}
					else
					{
						write(i, buf, str_len); // echo!
					}
				}
			}
		}
	}
	close(serv_sock);
	return 0;
}

  • select()의 세번째와 네번째 인자는 필요 없으므로 비어있다.
  • for문 안 첫 if문 : FD_ISSET으로 확인하려는 파일 디스크립터가 맞는지 확인한다
    • if문 : 서버 소켓이 변했으면 accept()하고 클라 소켓도 새로 관찰 시작한다
    • else문 : EOF면 소켓 종료하고, 아니면 echo

12-3 윈도우 기반으로 구현하기

윈도우의 fd_set은 비트 배열이 아니라
소켓 핸들 수 기록할 fd_count와 소켓 핸들 저장할 fd_array로 이루어졌다.
윈도우 소켓 핸들은 0부터 시작하지도 않고, 정수 값 사이에도 관계성 없기 때문이다.

profile
너 정말 **핵심**을 찔렀어

0개의 댓글