소켓 프로그래밍 입문 #2

CJB_ny·2022년 2월 19일
0

Unity_Server

목록 보기
23/55
post-thumbnail

이제 네트워크 프로그래밍을 해보도록 하겠다.

이상태에서 시작을 해보도록 하자.

락도 딱히 사용할 일이(지금은 없기때문에) 삭제를 해주도록 하자.

솔루션 > 속성 >

여러개의 시작 프로그램으로 맞춰 주어라.

그리고 Client 역할은 더미클라가, 서버 역할은 ServerCore가 할 것이다.

그다음에 이제 코드를 만들 때

클라부터 만들어도 되고

서버부터 만들어도 되는데

크게 상관은 없다.

일단 서버 부터 만들어보도록 하겠다.

그래서 아까 서버를 만들 때 뭐부터 했는지 생각해보면

문지기를 만들어 줬었는데

Listen 소켓을 먼저 만들주도록 하자.

그래서

이렇게 새로 만들어주는데

지금당장 소켓을 사용을 할 수는 없고

이거해주고

그리고 보면은

인자를 여러개를 받아주고 있는데

1. DNS (IP주소 관리하기 위한것)

address, socketType, protocolType을 받고있는 것을 볼 수 있다.

addressFamily가 첫인자로써 네트워크 주소를 넣어줘야 되는데

우리가 사용을 할 것은

DNS를 사용하도록 하겠다.

DNS == Dmain Name System 이라는 것인데

이녀석이 뭐하는것이냐 하면은

cmd창에다가 ping www.google.com을 치게되면은

이렇게

막 뭐가 나오는데

우리는 지금 "www.google.com"을 쳤는데

내부적으로는 지금

이렇게 해당하는 IP를 찾아내가지고 요청을 한것을 볼 수 있다.

그래서 DNS는 결국 이런 역할을 해주는 것인데

만약 우리가 서버주소를 172.1.2.3 이런식으로 하드코딩을 해서 넣어주면(지금 Socket의 첫인자로 넣을 네트워크 주소를)

문제가 되는게 뭐냐하면은

혹시라도 게임을 출시를 하게되서 서버를 다른 곳으로 이전을 하게 된다고 가정을 해보자.

그런데 경우에 따라서 IP주소도 바뀔 수 있을 것이다.

근데, 만약 이런식으로 172.1.2.3 하드코딩된 상태라면 자동으로 처리가 안될 텐데

"www.namyeong.com" 이런식으로 도메인을 등록을 한다음에

이녀석에 해당하는 IP가

"www.namyeong.com" => "123.123.321.12" 이런식으로 찾아 낼 수 있게 된다면은

"www.namyeong.com" 이 이름으로 이 "123.123.321.12" 주소를 찾게끔 만들어 주면은

관리가 훨씬더 쉬워 질 것이다.

using System.Net;

이거 해주고

Dns.찍어서 GetHostName(); 해주자

그리고

        Dns.GetHostEntry(host);

해서 방금 우리의 host이름을 넣어주자.

        Dns.GetHostEntry(host);

이녀석이 뱉어주는거는

이런식으로 뱉어 줄 것이다.


이 녀석을 보면은 이제

우리가 원하는 IP주소를 뱉어 주는 것을 볼 수 잇는데

또 배열로 되어있는 것을 볼 수 있다.

아까 우리가 "www.google.com"을 했을 때,

사실 그 도메인 주소에 해당하는 IP가 하나만 있을 수도 있지만 여러개가 있을 수도 있다.


무슨말이지? 기게하나에는 IP주소가 하나밖에 없는거 아닌가..??


그러니까 여러개를 관리를 할 수도 있을 수도 있다.

구글 같이 트래픽이 어마어마하게 많이 걸리는 사이트같은 경우에는

어떤 사람한테는 1번 주소를 알려주고 다른사람한테는 2번 주소를 알려주고

이런식으로 부화 분산을 하는 경우가 있기 때문에

그래서 이런식으로 배열로 IP주소를 뱉어주어서 경우에 따라서 5~10개 이런식으로 뱉어 줄 수도 있다.

참고만 하시고

우리는 무조건

        ipHost.AddressList[0];

첫번째로 알려주는 주소만 사용을 하도록 하겠다.


그런데 어떻게 Dns라는 녀석이 도메인 주소로 이런 123.123.12.12 이런 주소를

얻어오는지는 또 굉장히 복잡한 내용인데

일단, Dns서버라는 애가 우리 네트워크 망안에 하나 더있는데

그녀석이 찾아 준다고 생각하면 된다


그래서 이런식으로 얻어왔다고 가정을 해보도록 하자.

그리고 마지막으로 처리를 해줘야 할 부분이

IpEndPoint라는 부분인데

        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

이렇게 해주자

이게 뭐냐하면은

첫인자는 아까 식당으로 비유하면 ipAddr == 식당 주소 이고, 7777은 이게 식당의 정문인지 후문인지 문의 번호를 나타낸다고 보면된다.

7777 == 포트 번호라는 것인데

이렇게 하면 우리가 지금 7777번에다가 문지기를 배치를 했는데,

만약 클라가 엉뚱한 번호로 접근을 하려면 입장을 못한다.

그래서 7777이라는 번호랑

클라가 입장할 주소를 똑같이 맞춰 줘야한다.

이렇게 했으면 어떤 식으로든 우리의 IP주소를 만들어 주었을 것이다.

그다음 작업이 문지기 작업인데

2. 문지기 생성

정확히는 문지기를 만드는 것이 아니라

문지기가 들고있는 휴대폰을 만드는 것이다.

listenSocket에다가 endPoint.AddressFamily를 넣어주는데

이게 IPv4를 사용을 할 것인지 IPv6를 사용을 할 것인지를 넣어 주는 부분이다.

근데 우리는 여기서 자동으로 Dns에서 만들어 주었기 때문에

자동으로 만든 AddressFamily를 넣어주면 되는 부분이다.

그다음은 이제

우리가

  • TCP 를 사용을 할 것인지

  • UDP 를 사용을 할 것인지

를 골라줘야한다.

우리는 TCP로 작업을 할 것이다.

그래서 TCP로 할 경우에는

SocketType == stream으로

ProtocolType == Tcp로 넣어주면 된다.

이렇게 하면 문지기 (정확히는 문지기의 휴대폰)이 생성이 된것이다.

그다음에 해야 할일은

3. 문지기 교육

지금 이 문지기가 들고있는데 휴대폰에

우리가 찾은

이주소를 연동을 시켜 줘야하는데

여기서 Bind라는 것을 하면 된다고 했었다.

endPoint를 받고있는데 여기다가 우리가 찾은 endPoint를 넣어주면 된다.

이렇게 식당 주소 (endPoint)와 포트 번호 7777(식당입구 번호)를 교육을 시켜 줄 것이다.

4. 영업 시작

여기 이렇게 listenSocket에다가 Listen으로 해주면 여기 backlog라는 것을 받고 있는데

그냥 일단 10이라는 숫자를 넣어주도록 하자.

backlog가 뭐냐하면은

"최대 대기 수" 라고 생각하면된다.

예를 들어 게임이 ㅈㄴ 흥해가지고 갑자기 1000명의 사람들이 이 식당에 몰린다고 가정을 해보자.

그러면 실제로 문지기가 안내를 해주기 전까지 몇명이 대기를 할 것인지 나타내 주는 것이고

만약 이 숫자를 초과하게 되면 다른 손님은 입장이 가능한지 문의를 주었을 때,

바로 fail이 뜨게 된다는 것이다.

(당장은 크게 중요한 수치는 아니다)

그리고 지금 영업을 시작을 했으니까

손님을 한번만 받고 끝낼게 아니니까

무한 루프를 돌 것이다.

이렇게 cw taptap을 하고

이제 만약에 입장이 가능한지 문의가 와 가지고

들어오고 싶다면은

입장시키면된다.

입장은 Accept를 해주면 된다.

그런데,

Accept에 마우스 갖다대면

이렇게 Socket을 뱉어 주는데

이게 아까 손님의 대리인

즉, 세션의 소켓이 되는 것이다.

즉, 세션(대리인)의 소켓(휴대폰)이 되는 것이다.

이렇게 받아주도록 하고

이제 방금 입장한 손님과 대화를 하고싶다면은

무조건

            Socket clientSocket = listenSocket.Accept();

clientSocket을 통해서 대화를 해주면 된다는 것이다.

그렇다면 이제 입장을 시켯으니 이제부터 대화가 가능하다는 것인데

그리고 이제

이런식으로 받고 보내고 가 있을텐데

손님이 입장하고 하고싶은 말이 있을 테니까

그 얘기를 들어 볼 것인데

받는것은 Receive사용하면 되는데

받을 수 있기는 한데 여기서 받을 buffer를 하나 넣어줘야 하는것을 볼 수 있다.

그리고 int로 몇 byte를 받았는지 뱉어 줄 것인데

우선 간단하게 할 것이기 때문에

            byte[] recvBuff = new byte[1024];

이렇게 만들어 주고

몇개를 보낼지 모르니까 크게 1024바이트로 만들어 주고

보내준 데이터는 이제 recvBuff에 저장이 될 것이다.

그리고 receivBytes가 얼마였는지 이렇게 받아줄것인데

recvBuff에다가 data를 넣어주기는 할텐데,

몇 byte를 받아왔는지는 recvBytes에다가 넣어 줄 것이다.

5. 받는 부분

우리가 이제 테스트를 할 때는 문자열을 통해서 왔다갔다 할 것이니까

EnCoding을 통해서 "규약"을 UTF8로 정해주도록 하자 일단.

이거는 이제

문자열을 사용을 할 때, 영문자열이든 한글이든,

이런 "문자열"을 보낼 때 규약이 여러가지가 있는데

이것을 어떻게 인코딩을 할 것인지

그거에 따라서 Encoding. 을 맞춰 줘야하는데


Encoding관련된 부분 나중에 또 다룰 것이다


어쨋든

            Console.WriteLine("Listening. . . .( 영업중이야 . . . )");

이런 문자열이랑

            byte[] recvBuff = new byte[1024];

이런 byte 데이터로 변환하는 작업이라고 생각하면된다.

이렇게 GetString을 하게되면 우리가 받아온 recvBuff를 이제 string으로 변환을 해주게 된다.


그런데

지금 recvBuff 이녀석이 배열인데

시작부터 데이터가 있을 거라는 보장이 없다.

지금은 당연히 시작부터 있게 만들었지만,

경우에 따라서 recvBuff이녀석의 중간부터 데이터를 받을 수도 있으니까

recvBuff의 시작인덱스를 두번째 인자에 넣어 준것이고

그리거 이 문자열이 몇개짜린지는

이렇게 넣어주면된다.

지금은 이렇게 간단하게 했는데

지금같은 경우는 문자열을 받는다고 가정을 하고 만든것이기 때문에 이렇게 가능한것인데

나중에 실제 게임을 만들면 이렇게 문자열로는 처리를 못한다.

            string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);

그래서 데이터를 이렇게 뽑아 와 줄 것이다.

            Console.WriteLine($"[ FROM CLIENT ] {recvData}");

그리고 클라한테서 온 메세지니까 이렇게 해주도록 하자.

그래서 이게 받는 부분이였다.

6. 보내는 부분

그래서 받았으면 답장을 보내주기는 해야 된다.

이제는 반대로 클라리언트 메세지를 보내주도록 하자.

이녀석도 마찬가지고 buffer가 필요하기는 한데

이녀석은

            byte[] recvBuff = new byte[1024];

이렇게 1024짜리 buffer를 만들 것이 아니라

몇개짜리를 보낼 지 알 수 있으니까 바로 만들도록 하자.


어떻게 뭘 몇개짜리를 만들 수 있다는 것인가?


이렇게 해주는데

아까 했던거와

반대 순서로 간다고 보면된다.

이렇게 환영메세지만 입력을 하고

sendBuff 이 녀석을 돌려보낼 것이다.

"Welcom To MMORPG Server !" 그래서 이 문자열은 buffer(sendBuff)로 만들어 준것이다.

이것을 이제

이렇게 Send 해주고

그 다음에 이제 메세지를 받고 답장도 해줬으니

이제는 쫒아 내도록 하자.

쫒아내면 이제 핸드폰 연결이 뚝 끊어진 셈이 된다.

조금 우아하게 종료하기

그런데 이것을 조금더 우아하게 하기 위해서는

"나...이제 할말없어..."라고 예고를 해주는 것이다.

            clientSocket.Shutdown(SocketShutdown.Both);

이렇게 하면

더이상 듣기도 싫고 말하기도 싫다 라는것을 명시를 해주면

조금 더 우아하게 종료가 된다.

이게 뭔지는 TCP의 이론을 알아볼때 조금더 자세히 알아볼 것이다.

            clientSocket.Shutdown(SocketShutdown.Both);

이것을 근데 굳이 안 넣어도 동작은 똑같이 할 것이다.

그래서 이렇게 일단 만들어 주고


7. 혹시라도 에러 체크

중간에 에러가 났는지 체크를 하기 위해서

t & catch구문으로 감싸주도록 하자.

이렇게 해주고

그리고 문지기 교육부터 다 짤라서

try { } 안에다가 넣어 주도록 하자.

그래서 혹시라도 문제가 일어났다면

이렇게 log가 찍힐 것이다.


어쨋든 이렇게해서 서버는 완료가 된 것이다.

그런데 우리가 여기서

                Socket clientSocket = listenSocket.Accept();

이렇게 입장을 해도된다고 했는데

만약, 손님이 입장을 안하면 어떻게 될지 궁금하다.

지금 우리가 하는 것은 가장 기본적인 네트워크 프로그래밍의

기초중에 기초이기 때문에 이렇게 하는거지

"실제로"는 이렇게 하지 않을 것이다.

블로킹 함수라고해가지고 모든 실행이

                Socket clientSocket = listenSocket.Accept();

여기서 딱 멈추게 된다.

클라가 입장을 안하면

여기 아랫단계는 아예 넘어가지도 않을 것이고

만약 클라이언트 한명이 접속이 되었다면

Accept가 자동으로 완료가 되면서

clientSocket부분을 반환을 해주고 밑에 부분이 실행이 될 것이다.


어쨋든 Blocking, noneBlocking에 대한 얘기도 계속 할 것이다.


어쨋든 흐름 자체는

이렇게 위에서 아래로 읽으면 되는데

이녀석이 만약 다음단계로 바로 넘어 갈 수 없으면

계속 이부분에서 대기를 한다고 생각하면 될 것이다.


Lock이 Accept안에 구현이 되어있는 것인가...??


이런 Send도 마찬가지고 보냈는데 안 받으면 대기를 할 것이다.

8. 클라 처리

클라 처리는 서버 보다 훨씬더 간단하다고 했었는데

일단,

이부분을 복붙하도록 하겠다.

이렇게 붙여주자.

그리고 아까 그 식당 주소를 찾는 부분은 뭐...똑같다.

ipAddr == 루키스 중화요리 서초점, 7777 == 문 번호같은거( == 포트 번호)

이거 두개는 서버랑 똑같이 맞춰 줘야 한다.

그다음

8-1. 휴대폰 생성 == 소켓 생성

이렇게 똑같이 소켓 만들어 주자.

인자들도 서버와 똑같이 맞춰주고


TCP로 할꺼면

자동으로 이렇게 두개가 결정이 된다.


그러면 이제 socket == 휴대폰을 만들었으니

문지기한테 입장이 가능한지 물어봐야 한다.

8-2. 문지기한테 입장문의

이제 문지기 한테 연락을 해야되니까

Connect로 물어보도록 하겠다.

그래서 안에다가는 상대방의 "주소"를 넣어 줄 것이고

"문지기"가 받으면 Connect에서 나와서 이어지게 되는 것이고,

아니라면 대기를 할것이다.

그래서 빠져나왔다 라는 것 (== cw 이 실행됨) 이니까 서버의 정보를 여기다가 출력을 해보도록 하자.

이녀석은 우리가 연결한 반대쪽 대상 (==대리인) 이라고 생각하면된다.

.ToString으로 누구한테 연락했는지 출력을 해주도록 하자.

서버는 받는다 -> 보낸다 순인데 클라는 거꾸로 한다.

8-3. 보낸다

서버에서는 나중에 만들었었는데 클라는 먼저 만들어 주도록 하겠다.

그래서 이렇게 Hello World! 를 서버한테 보내 주도록 하겠다.

그리고

이부분도 똑같은데

Send(sendBuff);를 해서 sendBuff에 있는 내용을 전체다 보내겠다 라는 의미이다.

8-4. 받는다

이어서 받는 부분에서는

서버가 나한테 몇바이트를 보낼지 모르기 때문에

1024바이트로 만들어주고

socket을 통해서 Receive를 해줄 것인데

        socket.Receive(recvBuff);

이렇게 받아주고,

이녀석이 뱉어 주는 것은 몇 byte인지 뱉어 준다고 했었다.


int == 32 byte

8 bit == 1 byte


이렇게 해주고

이제 이것을 Byte -> String으로 변환하는 작업을 해줄 것이다.

그리고

recvBuff통 째로 받고 0번째부터 recvBytes크기만큼 까지를 받겠다 -> 그리고String으로 바꾸겠다 라는말이다.

        string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);

이렇게 recvData받고 서버가 뭐라고 했는지 궁금하니까

이렇게 해주자.

8-5. 나간다

서버쪽에 먼저 끊어주기는 했지만

혹시 모르니까

이렇게 ShutDown 해주도록 하자.

그리고 혹시라도 문제가 있을 수도 있으니까

try & catch구문으로 문제를 잡아주도록 하자.

하고 밑에부분 try로 밀어 넣도록 하자.

이렇게 해주자.


이렇게해서

클라, 서버 둘다 완성이 되어서 둘다 동시에 실행을 해보면 된다.


오른쪽이 서버 콘솔창,

왼쪽이 클라 콘솔창인데

서버는 Lostening . . . 하다가

클라가 메세지를 Hollo World라고 보낸 것이고

반대로 서버도 클라한테 메세지를 보냈었는데

Welcome MMORPG Server!라고,

이렇게 클라도 메세지를 잘 받아준 것을 볼 수 있다.

그런데 클라같은 경우에는

이렇게 받고 바로 종료를 했으니까 프로그램이 바로 끝난 것이고


이렇게 서버 같은 경우에는 while문을 돌면서 다른 클라이언트들이 접속을 하고 있는 지 받고 있는 것이다.

그래서 서버는 항상 이렇게 실행되는 상태로 남아있을 것이다.


그리고 이상태에서 클라를 다시 켜볼 것인데

DummyClient -> 우클릭 -> 파일 탐색기에서 폴더 열기해서

bin폴더 -> Debug폴더

이런식으로 파일 타고 가다보면 exe 파일이 있다.

이상태에서 exc파일 강제로 실행을 시켜보면

이렇게 뜬다.

이렇게 시작하자마자 끝나기 때문에 실행하면 이렇게 뜬다.


9. 주의 해야 할 점들

그래서 이렇게해서 소켓 프로그래밍을 해보았는데

이것을 이제 발전 시켜 나가면된다.

그리고 특히나 지금 가장 위험한 부분중 하나는

게임에서는

이런식으로 블로킹 계열을 쓰면 안된다는 말이다.

            socket.Connect(endPoint);

만약에 내가 Connect를 했는데

서버가 못 받아주면 이녀석은 상당히 긴 시간동안

여기서 대기를 타게되는 것이고

마찬가지로

socket.Send랑

socket.Receive같은 부분도

send했는데 서버에서 안 받아 주게되면 멍때릴 것이고

Receive를 했는데 서버에서 아무 데이터도 안보내 주면은

여기서 "Hang"하는 상태로 서버가 보내줄 때까지

기다리게 될 것이다.


10. 좋아보이는 질문글들

읽어보셈요!


->


이 질문글을 1. DNS 다시 복습을 하면 조금 이해가 갈 것이다.


11. 소켓#2 전체코드


클라쪽 코드

휴대폰 만들고

보내고, 받고 작업


서버 코드

네트워크 주소 만들고

문지기 만들고

문지기 교육 > 영업시작 > 손님입장 (대리자 입장) > 클라가보낸것 "받는다" > "서버"에서 "클라"로 보낸다 > 쫒아낸다.

profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글