서버
DNS 및 Socket 연결
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress address = ipHost.AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(address, 7777); // 최종주소
Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ipEndPoint); // 해당 ip주소로 연결 요청
Console.WriteLine($"Connect To {socket.RemoteEndPoint.ToString()}");
DNS
- www.google.com → 112.123.123.1 이런 식으로 내부적으로 ip주소로 변환되는 것
- 코딩할 때 서버 주소를 하드코딩 하면 안 되는 이유
- 서버 이전이나, IP 주소가 바뀌면 일일이 바꿔줘야 함
- DNS를 통해 자동으로 ip를 찾을 수 있어 DNS로 해두는 게 편함
AddressList
- IPHostEntry.AddressList을 호출하면 IPAddress [] 배열로 반환
- 구글같이 큰 사이트는 트래픽 관리를 위해 여러 아이피를 가지고 있기 때문
- 여기서는 그중에서 첫 번째 것만 사용
IPEndPoint
- 최종적인 주소 - 아이피 주소와 포트번호가 들어간다.
- 포트 번호는 우리가 임의로 정해줘야 함
- 포트 번호는 우리가 임의로 정해줘야 함
- 이 포트 번호는 클라이언트와 서버가 일치해야지 통신이 가능하다.
Socket
- 여기서 소켓을 선언할 때 3가지 매개변수가 들어감
- 첫 번째 매개변수 : IPv4를 사용할지 IPv6을 사용할지 등을 결정
→ 여기서는 ipEndPoint에 있는 규칙을 따라가기 위해 ipEndPoint.AddressFamily를 사용 - 두 번째 매개변수, 세 번째 매개변수 → 통신 규약
- 여기서는 tcp를 사용했기 때문에 한쌍으로 SocektType도 Stream으로 맞춰줌
- 첫 번째 매개변수 : IPv4를 사용할지 IPv6을 사용할지 등을 결정
Listen
- 백로그로 최대 대기할 수 있는 인원수를 정한다.
- 예를 들어, 백로그가 10이라면 10명만 서버에 대기할 수 있고 나머지는 접속에 실패한다.
클라이언트와 상호작용
while (true)
{
Console.WriteLine("Connect Server");
// 클라이언트 입장시키기
Socket clientSocket = listenSocket.Accept();
// 데이터 받기
byte[] recvBuff = new byte[1024]; // 1024바이트까지 받아들일 수 있는 버퍼
// 클라이언트 소켓으로부터 받아 recv에 저장
int recv = clientSocket.Receive(recvBuff);
// 받은 것은 UTF8 정책에 따라 문자열로 인코딩
string data = Encoding.UTF8.GetString(recvBuff, 0, recv);
Console.WriteLine($"[From Client] {data}");
// 데이터 보내기
// 문자열을 byte로 변환
byte[] sendBuff = Encoding.UTF8.GetBytes("Hello Client");
clientSocket.Send(sendBuff); // 클라이언트 소켓을 통해 데이터를 보냄
// 소켓 연결 끊기
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
Accept
- Accept를 통해 클라이언트를 입장시킬 수 있다.
- Accept는 반환할 때 Socket을 반환하기 때문에 이런 식으로 clientSocket을 변수로 받는다.
- 이제 해당 클라이언트와 통신하기 위해서는 해당 소켓으로 통신한다.
데이터 받기
- 데이터는 버퍼를 통해 byte로 들어오기 때문에 이를 받기 위해 recvBuff를 만들어둔다
- 들어오는 데이터가 얼마나 클지 모르기 때문에 1024byte로 정의해 둔다.
- 들어오는 데이터가 얼마나 클지 모르기 때문에 1024byte로 정의해 둔다.
- 받아들인 byte 배열을 UTF8 정책으로 String 형으로 변환하여 data로 저장한다.
- 이때 매개변수 0은 시작 인덱스의 위치를 뜻한다.
데이터 보내기
- 데이터 받기와 비슷한 원리로 보낼 데이터를 문자열로 쓰고 이를 byte로 변환하여 저장해 둔다.
- 그리고 clientSocket을 통해 send 한다.
소켓 연결 끊기
- clientSocket.Close()를 통해 연결을 끊는다. → 사실 이것만 있어도 연결은 끊긴다.
- 좀 더 안전하고 확실하게 하기 위해 Shutdown을 사용한다.
서버 전체 코드
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
public class Program
{
static void Main(string[] args) {
string host = Dns.GetHostName(); // 로컬 컴퓨터의 호스트 이름을 가져옴
// 호스트 이름을 통해 IPHostEntry를 가져옴
IPHostEntry ipHost = Dns.GetHostEntry(host);
// 해당 IPHostEntry에 있던 여러 IP 중 가장 첫 IP를 가져옴
IPAddress address = ipHost.AddressList[0];
// 최종주소, 7777은 임의로 설정해둔 번호
IPEndPoint ipEndPoint = new IPEndPoint(address, 7777);
// listen해줄 소켓을 만듦 매개변수로, AddressFamily, 통신 방법(TCP/UDP)를 정함
Socket listenSocket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(ipEndPoint); // 소켓을 해당 방식으로 바인딩시킴
// 백로그 : 최대 대기자수 10명
listenSocket.Listen(10);
// 계속해서 응답을 받음
while (true)
{
Console.WriteLine("Connect Server");
// 클라이언트 입장시키기
Socket clientSocket = listenSocket.Accept();
// 데이터 받아들이기
byte[] recvBuff = new byte[1024]; // 1024바이트까지 받아들일 수 있는 버퍼
// 클라이언트 소켓으로부터 받아 recv에 저장
int recv = clientSocket.Receive(recvBuff);
// 받은 것은 UTF8 정책에 따라 문자열로 인코딩
string data = Encoding.UTF8.GetString(recvBuff, 0, recv);
Console.WriteLine($"[From Client] {data}");
// 데이터 보내기
// 문자열을 byte로 변환
byte[] sendBuff = Encoding.UTF8.GetBytes("Hello Client");
clientSocket.Send(sendBuff); // 클라이언트 소켓을 통해 데이터를 보냄
// 소켓 연결 끊기
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
}
}
}
클라이언트
- 클라이언트 쪽은 서버 쪽 코드보다 훨씬 쉽다.
- 서버 쪽에서 설명한 것이 거의 전부기 때문에 새롭게 나온 것만 설명한다.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
public class Program
{
static void Main(string[] args)
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress address = ipHost.AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(address, 7777); // 최종주소
Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ipEndPoint); // 해당 ip주소로 연결 요청
Console.WriteLine($"Connect To {socket.RemoteEndPoint.ToString()}");
// 데이터 보내기
byte[] sendBuff = Encoding.UTF8.GetBytes("Hello Server");
socket.Send(sendBuff);
// 데이터 받
byte[] recvBuff = new byte[1024];
int recv = socket.Receive(recvBuff);
string data = Encoding.UTF8.GetString(recvBuff, 0, recv);
Console.WriteLine($"[From Server] {data}");
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
}
서버와 연결
Connect
- 매개변수로 IPEndPoint가 들어간다.
- 해당 ip 주소로 연결 요청을 하는 것이다.
- Console.WriteLine에 있는 socket.RemoteEndPoint는 현재 통신하고 있는 상대를 뜻한다.
- 여기서 현재 통신하고 있는 상대 → 서버
실행 결과
- ServerCore 쪽에서의 화면이다. 제대로 실행되는 것을 확인할 수 있다.
- Client 쪽에서도 정상적으로 실행되는 것을 확인할 수 있다.