후배에게서 두 가지 질문을 받았는데, 첫번째 질문은 Interprocess communication에 관한 내용이었다. 프로세슷 간의 통신을 뭐라고 부르는지 몰라서 검색을 못한 모양이었다. 나도 IPC에 대해 아는 바가 거의 없어서 키워드를 알려준 것으로 만족했다.
두번째 질문과 그에 대한 답변은 다음에 제시되어 있다.
이건 다른 질문인데요. socket을 써서 client-server가 연결하는 상황이구요
이 두 프로그램에서 연결된 뒤에 가장 먼저 하려고 하는 일이 둘다 write이거든요?
server는 client에게 메세지를 보내려 하고, client는 server에 메세지를 보내려 하고….
이런 상황이 되면 socket은 deadlock 처럼 되는건가요? ^^;
알려주세요~~
소켓 통신의 개념을 정확히 모르고 있는 것 같네. 나도 처음에는 똑같이 생각했지. 하하.
아마도 서버나 클라이언트가 [보내기-받기-보내기-받기] 방식으로 작동해야 한다고 생각하는 것 같아. 동기소켓(비동기 소켓도 있는데, 네가 사용하는 것은 동기라고 생각하면 돼) 프로그래밍은 받기나 보내기를 할 때 Blocking(네가 말하는 Deadlock이 Blocking을 의미하는 것 같은데…)이 될거야. 동기소켓에도 Nonblocking 기능이 있지만, 대체로 동기는 Blocking 방식을 사용하지. 흠…. 복잡해지는 것 같은데 우선 정리 좀 해볼까?
-
Blocking 동기소켓
-
Nonblocking 동기소켓
-
비동기소켓
비동기소켓과 Nonblocking 동기소켓은 개념이 비슷해서 혼동하기 쉬운데, 어차피 Blocking 동기소켓을 사용할테니 나머지는 무시하자. 자세한 내용은 소켓의 넌블럭킹 (non-blocking) 모드란…를 읽어보면 알 수 있을거야. 아까 하던 이야기로 돌아가서 Blocking 동기소켓이라고 해서 꼭 [보내기-받기-보내기-받기] 방식으로 작동할 필요는 없어. 다음 코드를 볼까? (열혈강의 TCP/IP 소켓 프로그래밍에서 베낀 다음 보기 쉽게 약간 수정했어.)
// 전방 선언
void* send_message(void* arg);
void* recv_message(void* arg);
int main()
{
void* thread_result;
if(connect(sock, (struct sockaddr*)&serv_Addr, sizeof(serv_add))==-1) {
error_handling("connect() error");
}
pthread_create(&snd_thread, NULL, send_message, (void*)sock);
pthread_create(&rcv_thread, NULL, recv_message, (void*)sock);
pthread_joiin(snd_thread, &thread_result);
pthread_joiin(rcv_thread, &thread_result);
close(sock);
return 0;
}
// 메시지 전송 쓰레드 실행 함수
void* send_message(void* arg)
{
int sock = (int)arg;
char message[BUFSIZE];
while(1) {
// 화면에서 메시지를 입력 받는다.
fgets(message, BUFSIZE, stdin);
// 'q' 입력 시 종료
if(!strcmp(message, "q
")) {
close(sock);
exit(0);
}
// 화면에서 입력 받은 메시지를 전송한다.
write(sock, message, strlen(message));
}
}
// 메시지 수신 쓰레드 실행 함수
void* recv_message(void* arg)
{
int sock = (int)arg;
char message[BUFSIZE];
int str_len;
while(1) {
// 메시지를 수신 받는다.
str_len = read(sock, message, BUFSIZE-1);
if(str_len == -1) { return 1; }
message[str_len] = 0;
// 수신 받은 메시지를 화면에 출력한다.
fputs(message, stdout);
}
}
위의 코드는 채팅 클라이언트지. 서버에 연결이 되면 바로 쓰레드 두개를 생성해. 하나는 메시지 전송용 쓰레드고, 다른 하나는 메시지 수신용 쓰레드지. 이렇게 하나의 연결에 대해 전송/수신 쓰레드로 나누는 것은 비교적 쉽기 때문에 많이 사용하지. 하나의 연결에 쓰레드가 하나 밖에 없는 구조에서는 보내기/받기가 끝나야 받기/보내기를 할 수 있었지. 하지만 이렇게 두 개의 쓰레드로 나누면 보내기와 받기를 동시에 할 수 있어서 Blocking 된 시간 동안 아무것도 못하고 기다리는 일이 없어지지.
잘못된 개념
클라이언트 서버 [송신] -------------> [수신] [수신] <------------- [송신] [송신] -------------> [수신]
제대로 된 개념
클라이언트 서버 [송신 쓰레드] -------> [수신 쓰레드] [수신 쓰레드] <------- [송신 쓰레드]