상세 컨텐츠

본문 제목

(MFC/네트워크) TCP 서버 코딩하기 관련

프로그래밍 관련/네트워크, 통신

by AlrepondTech 2020. 9. 17. 06:31

본문

반응형

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://rucaus.egloos.com/2293868

 

 

(1) (MFC/네트워크) TCP 서버 코딩하기 Programming

 

클래스화가 되어있지 않은 기본적인 프로그램이다.

 

 

1. 다이얼로그 기반 MFC 클래스를 만들고 다음과 같이 꾸민다.

리스트박스 두개, 에디트컨트롤 하나.

왼쪽 리스트박스는 대화 참여자 목록에 쓰일 것이고, 오른쪽리스트박스는 채팅 내용을 보기 위해 쓰일 것이다.

변수추가 해서 다이얼로그 클래스에 연결해준다.

 

 

 

 

 

 

 

2. 가장 바깥쪽 cpp에 추가해야 할 부분이 있다.

 

나의 경우에는 프로젝트 이름이 Server이고, 젤 바깥쪽 cpp 파일은 Server.cpp이다. 

여기서 InitInstance() 에 다음과 같이 세 줄 추가.

 

 

 

WSAStartup() 함수는 윈도우 소켓 통신에 사용되는 함수들을 초기화해주는 함수이다.

책에서 찾아보니 윈도우즈 소켓 관련 함수들은 DLL 형태로 제공되는데, 이것을 사용하기 위해서는 반드시 초기화 과정인 WSAStartup() 함수가 필요하다고 한다. 만약 초기화 없이 함수를 사용하면 소켓 함수들은 모두 실패하게 된다고 함.

(네트워크 멀티스레드 프로그래밍 /지은이 유동근 을 참고.) 

 

MAKEWORD는 WSAStartup시 윈도우 버전에 따라 입출력모드가 달라지므로, 윈도 버전을 설정해 주기 위한 것이라 한다.

XP 및 윈7은 2.2 버전이라 인자값이 2,2 라 함.

 

WSADATA 형 구조체는 WSAStartup() 함수로 인해 생긴 소켓의 초기화 결과값을 저장하기 위해 쓰였다.

 

 

3. 2에서 쓴 함수(그리고 나중에 쓸 소켓함수들)을 쓸려면 DLL과 헤더 관련 추가를 해줘야 한다.

 

 

stdafx.h 같은 헤더에다가 이렇게 인클루드 해주고, 

 

프로젝트 속성-구성 속성-링커-입력-추가 종속성에서 

 

 

WS3_32.LIB을 추가한다.

 

 

 

4.

첫번째로, 서버는 다음과 같은 구조를 가져야 한다.

 

'socket→binding→listen→accept→send, recv→ closesocket'

따라서 이 구조가 구현되도록 코딩해야 한다.

 

이것이 생소하다면 TCP 서버/클라이언트 간의 기본적인 모델을 보여주는 다음 그림을 참고하라.

 

 

 

 

5.

 

socket(통신에 쓰일 소켓 생성)

bind(나의 통신정보를 서버소켓에 등록)

listen(서버소켓을 대기상태로 만들어달라고 운영체제에 요청)

accept(내 소켓에 접속을 받는 행위. 직접 받는게 아니라 사실은 뒷쪽에서 운영체제가 메시지큐에 받는것이지만)

 

위의 네 가지를 먼저 구현해 보자.

 

코딩할 위치는 다이얼로그를 초기화하는 함수 내부. (즉 다이얼로그 cpp 파일의 OnInitDialog() 함수 부분.)

 

 

 

6. 

소켓을 생성하는 부분이다.

 

 

 

SOCKET 타입의 private 멤버인 m_ServerSocket을 추가한다.

m_ServerSocket은 서버의 소켓으로 사용하기 위해 만든 객체이다.

 

socket() 함수는 소켓 객체를 생성하는 함수이다. 

socket() 함수의 첫번째 인자 : 프로토콜의 체계. AF_INET은 InterNetwok 통신 유형인 UDP, TCP이다.

두번째 인자 : 소켓이 데이터를 송수신하는 방식. SOCK_STREAM은 데이터를 연속적으로 송수신한다는 뜻.

세번째 인자 : 사용할 프로토콜 결정. 여기서는 TCP 타입이다.

 

 

7.

 

만든 소켓을 바인딩한다.

bind란 생성된 소켓에 자기 포트번호와 자기 IP를 연결하기 위한 함수이다. 이전의 socket() 단계에서는 소켓이 생성된다. 뒤이어 bind함수를 호출하면 프로토콜, 자기포트번호, 자기 ip가 결정된다. 이 이후에는 소켓을 대기소켓으로 사용할 수 있다.

 

 

 

위에서 바인딩을 위해서는 생성된 소켓에 연결할 주소(내포트번호, 내아이피) 가 필요하다. 그래서 SOCKADDR_IN 구조체가 선언되어 쓰였다. SOCKADDR_IN addr는 소켓이 사용하는 주소 구조체이다. 여기에는 나의 주소를 넣는다. 그리고 이 주소와 소켓을 bind() 함수로 바인딩한다.

 

 

8.

 

 

바인드까지의 과정으로, 소켓은 대기 소켓으로서의 조건을 갖추게 되었다. 하지만 운영체제는 아직 이 소켓이 대기소켓이 될 수 있다는 것을 모른다. 그래서 운영체제에 이 사실을 알려줘야 하는데 이것을 알려주는 함수가 listen() 이다.

listen() 함수 이후부터, 해당 소켓에 클라이언트의 요청이 있을 경우 운영체제는 백그라운드에서 접속을 받아들일 수 있게 된다. 이때 운영체제는 listen()함수의 두 번째 인자에 적힌 크기만큼 접속을 받아들인다.

SOMAXCONN은 시스템이 지원하는 최대값만큼의 크기를 쓰겠다는 것이다.

 

 

 

 

반응형

 

728x90

 

 

 

 

9.

 

accept 부분은 다음과 같이 구현.

 

 

 

이렇게 AfxBeginThread로 쓰레드를 돌리기 시작.

AcceptFunc 함수는 다음과 같이 구현한다.

 

 

 

 

AcceptFunc함수의 구현을 큰 구조로 보면

-무한반복문(while)

-accept() 함수가 반복문 내부에 있고

-accept() 함수 이후 리시브 함수인 RecvFunc 쓰레드를 부르고 있다.

 

accept() 함수의 첫 번째 인자는 대기 소켓, 즉 서버 소켓이다. accept()에 서버소켓을 인자로 넘긴 이유는, 해당 소켓에 존재하는 대기 큐에 있는 통신 소켓을 얻어오기 위해서이다. accept()는 큐에서 통신소켓을 읽어와 그 소켓을 반환하고, 반환된 소켓은 대기큐에서 삭제한다. accept()는 리턴값이 세개이다. 첫번째로 함수의 리턴값인 통신소켓, 그리고 accept()의 두번째와 세번째 인자이다. 두번째와 세번째 인자의 출력값으로 주소 정보와 크기가 반환된다.

 

일반적으로 accept() 함수는 반복문에서 호출된다. 왜냐면 대기 큐에는 계속해서 클라이언트들의 통신 소켓이 쌓일 것이고, 서버에서는 반복적으로 쌓이는 통신소켓들을 꺼내서 처리해야 하기 때문이다. 만약 accept() 함수가 호출되었을 때 대기 큐에 통신소켓이 남아있지 않다면, 새로운 접속이 올 때까지 accept() 함수는 대기하게 된다.

 

여기까지의 결론덕분에 accept() 부분을 따로 빼내서 쓰레드로 만든 이유를 알게 된다.

주 쓰레드 (Primary Thread) 에서 accept() 함수가 실행된다면, 더 이상 메세지를 처리할 것이 없을때 accept()는 무한대기하게 될 것이다. 이것은 사용자가 프로그램을 돌릴 때, 누군가 메세지를 보낼 때까지 아무것도 조작하지 못하고 무한정 기다려야 한다는 것을 뜻한다.

 

그러므로 accept() 부분을 따로 쓰레드로 빼낸 것이다.

 

얻어온 통신소켓은 Recv()에서 처리한다.

여기부터는 다음 포스팅에서..

'네트워크 멀티스레딩 프로그래밍'  책을 많이 참고했다.

 

 

 

 

=======================

=======================

=======================

 

 

 

출처: http://rucaus.egloos.com/2294263

 

(2) (MFC/네트워크) TCP 서버 코딩하기

 

 

 

저번 포스팅에서는 서버를 모델링하는 과정중

 

Create Socket -> Bind-> Listen -> Accept 까지 했었다.

 

 

이번 포스팅에서 할 것은 Accept에 이어 Recv 및 Send이다.

 

1.

 

 

Accept 함수는 Recv함수의 일을 하는 쓰레드인 RecvFunc를 호출하며 끝나고 있다.

보면 RecvFunc함수에 던져주는 인자가 CRecvParam 타입인 것이 보인다.

이것은 윈도우즈에서 제공하는 자료형이 아니다. 내가 쓰레드에 인자들을 하나로 모아 던지기 위해 만든 임시 클래스이다.

 

쓰레드는 인자를 하나만 받는다. 만약 내가 여러개의 인자들을 쓰레드에 던져 쓰레드가 그 인자들로 뭔가 작업을 하고 싶을때는, 그 인자들을 하나의 구조체로 모아서 던져야 한다. 그래서 Accept 쓰레드 바로 위에 임시로 만든 클래스이다.

 

 

요렇게 생겼다. ^^

 

 

2.

 

RecvFunc에 대해 알아보자.

 

 

RecvFunc 쓰레드가 하는 일을 보면, 먼저 RecvParam 타입으로 받은 인자를 다시 다이얼로그 포인터와 소켓으로 뜯어낸다.

 

그리고 반복문 while() 안에서 iRecvLen에 recv() 함수의 리턴값을 넣고 있다. 

 

recv() 함수는, 소켓에서 데이터를 읽어 그 값을 temp에 저장하고 있다.

recv() 함수의 리턴값은 얼마만큼의 값을 읽었는지 그 읽은 길이를 리턴한다. 따라서 0 또는 에러메시지(-1) 을 체크할 수 있다.

만약 읽은값이 없거나 에러인 경우 ( if (iRecvLen <=0) 이면 서버 다이얼로그에 있던 유저보이기 목록에서 그 소켓을 지워낸다.

 

268~269 줄은 채팅 서버 스스로가 그 메세지를 보게 해주는 것 (가장 최근 메세지를 리스트박스에서 맨 아래에 뿌려지게 하는 것)이다.

 

또한 코드에서 SendToAll() 을 호출하는것을 볼 수 있다. 채팅 서버는 클라이언트가 보낸 채팅메세지(현재는 temp에 저장되어 있는)을 모두에게 뿌려 주어야 하므로, SendToAll()로 모든 클라이언트들에게 다시 보내준다.

 

 

3.

 

그렇다면 SendToAll() 에서 어떻게 모든 클라이언트에게 메세지를 보내는지 보도록 하자.

 

 

 

 

-Dlg 클래스는 ClientSocket들을 링크드 리스트로 갖고 있다.

 

Dlg 클래스의 헤더에 아래처럼 멤버로 만들어서 갖고 있음. (하나의 서버에 여러개의 클라이언트 소켓들이 매달려 있는 구조이므로, 이렇게 링크드리스트로 관리.)

 

 

-pos는 STL의 링크드리스트에서 쓰이는 것으로, 리스트 자료구조상에서의 노드 위치를 나타낸다.

-리스트에 매달린 노드를 하나하나 돌면서, 노드의 소켓에게 send() 함수를 통해 메세지를 보낸다.

 

 

여기까지가 기본적인 TCP 서버 코딩이다.

이 코드에서 클래스화 + 패킷붙여서 보내기 + 패킷헤더 떼서 읽기 등등의 처리를 더 해주어야 한다. 

 

다음 포스팅은 클라이언트의 구현에 대해서 올리도록 한다. ^^

 

 

 

=======================

=======================

=======================

 

 

반응형


관련글 더보기

댓글 영역