[게임 서버] 3.6 논블록 소켓

게시:     수정

카테고리:

태그:

📦 3. 소켓 프로그래밍

👉🏻 항목 6: 논블록 소켓

🔒 블로킹 소켓 (기존 방식)

void BlockSocketOperation() {
	s = socket(TCP);
	...;
	s.connect(...);
	...;
	while(true) {
		s.send(data);
	}
}

특징:

  • 이전까지 위와 같이 구현해 왔으며, 이는 블로킹 소켓이다

🔓 논블록 소켓의 개념

기본 원리:

  • 블로킹이 발생할 상황에서도 즉시 리턴하여 프로그램 흐름을 멈추지 않는다

📤 논블록 소켓 - 송신

void NonBlockSocketOperation() {
	s = socket(TCP);
	...;
	s.connect(...);
	// 논블록 소켓으로 변경
	s.SetNonBlocking(true);

	while(true) {
		r = s.send(dest, data); // 1
		if(r==EWOULDBLOCK) continue;

		if(r==OK) { ... } // 보내기 성공 처리
		else { ... } // 보내기 실패 처리
		// 2
	}
}

동작 방식:

  • 논블록 소켓을 사용한 코드이다
  • 1에서 송신 함수는 즉시 리턴되며, 값을 반환한다
    • EWOULDBLOCK: 블로킹 걸릴 상황이었으며, 아무것도 하지 않았다
    • OK: 보내기 성공했다
    • etc: 다른 문제가 발생했다

장단점:

  • 한 스레드에서 여러 소켓을 처리할 수 있다
    • 그러나 루프를 도는 동안 블로킹이 난무할 것이다
    • 2에서 CPU 사용량이 증가한다

📥 논블록 소켓 - 수신 (여러 소켓)

List<Socket> sockets;

void NonBlockSocketOperation() {
	foreach(s in sockets) {
		// 논블록 수신
		(result, data) = s.receive();
		if(data.length > 0) { // 수신 성공
			print(data);
		}
		else if(result != EWOULDBLOCK) {
			// EWOULDBLOCK이 아니면 오류!
			...
		}
	}
}

특징:

  • 수신할 데이터가 없다면 would block을 즉시 리턴한다
    • 잠시 후 한번 더 논블록 수신 함수를 호출해보면 된다

🔌 논블록 connect 함수

연결 상태 확인:

  • connect 함수를 논블록으로 사용하였을 때, would block이 반환되면 소켓은 연결 과정이 진행 중인 상태에 들어간다
  • TCP 소켓이 어떤 상태인지 확인하기 위해서는 0바이트를 송신하면 된다
    • OK를 리턴하면: TCP 연결 성공
    • ENOTCONN을 리턴하면: TCP 연결 진행 중
    • 기타 오류 코드를 리턴하면: TCP 연결 실패

구현 예시:

void NonBlockSocketOperation() {
	result = s.connect();
	if(result == EWOULDBLOCK) {
		while(true) { // 1
			byte emptyData[0]; // 길이 0 배열
			result = s.send(emptyData);
			if(result == OK) // 연결 성공 처리
			else if(result == ENOTCONN) // 연결 진행중
			else // 연결 실패 처리
		}
	}
}

문제점:

  • 하지만 result가 EWOULDBLOCK일 때 루프를 돌게 된다
  • 클라이언트는 괜찮을지 몰라도, 서버는 상황이 좋지 않다

⚠️ 논블록 수신의 문제점

List<Socket> sockets;

void NonBlockSocketOperation() {
	while(true) {
		foreach(s in sockets) {
			// 1. 논블럭 수신
			(result, data) = s.receive();
			if(data.length > 0) print(data);
			else if(result != EWOULDBLOCK) { ... } // 소켓 오류 처리
		}
		// 2
	}
}

문제점:

  • 무한 루프로 인한 CPU 사용량 증가

✅ select 함수를 이용한 개선

List<Socket> sockets;

void NonBlockSocketOperation() {
	while(true) {
		select(sockets, 100ms); // 1
		foreach(s in sockets) {
			(result, data) = s.receive();
			if(data.length > 0) { // 수신 성공
				print(data);
			}
			else if(result != EWOULDBLOCK) {
				// EWOULDBLOCK이 아니면 오류!
				...
			}
		}
		// 2
	}
}

select 함수의 동작:

  • 1에서 100ms 동안 sockets에 I/O 처리가 가능한 소켓이 하나라도 있다면 즉시 리턴한다. 없다면 100ms까지 블로킹한다
  • select 함수가 리턴되면 sockets 각각에 대해 논블록 I/O 처리 함수 호출

🎧 논블록 accept

void NonBlockSocketOperation() {
	s = socket(TCP);
	s.SetNonBlocking(true);
	s.listen(5000);

	while(true) {
		(socket, result) = s.accept();

		if(result == EWOULDBLOCK) continue;
		if(result == OK) { ... }
		else { ... }
	}
}

동작 방식:

  • 리스닝 소켓이 논블록 소켓이며, TCP 연결이 되지 않았으면 accept()는 블로킹 대신 would block 오류 코드를 준다
    • 그런 경우 select()를 사용하여 I/O 이벤트가 감지되면 accept()를 호출하면 된다

🧐 정리

구분 블로킹 소켓 논블록 소켓
동작 방식 작업 완료까지 대기 즉시 리턴
반환 값 작업 결과만 OK, EWOULDBLOCK, 기타 오류
CPU 사용 낮음 (대기 중) 높음 (계속 확인)
멀티 소켓 처리 어려움 가능

핵심 개념:

  • 논블록 소켓은 SetNonBlocking(true)로 설정한다
  • EWOULDBLOCK은 블로킹이 필요한 상황에서 반환되는 코드이다
  • select() 함수를 사용하면 CPU 낭비를 줄이면서 여러 소켓을 효율적으로 처리할 수 있다
  • 0바이트 송신으로 TCP 연결 상태를 확인할 수 있다
  • 서버에서는 논블록 accept와 select를 조합하여 효율적인 연결 처리가 가능하다

GameServer 카테고리 내 다른 글 보러가기

댓글남기기