[게임 서버] 3.6 논블록 소켓
카테고리: GameServer
태그: GameServer
📦 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를 조합하여 효율적인 연결 처리가 가능하다
댓글남기기