네트워크 프로그래밍
TCP/IP와 소켓 통신
TCP/IP는 피드백이 없으면 연결을 끊어버림
이동이 잦은 모바일 환경에서 문제가 될 수 있음
서버의 역할 및 소켓 통신 순서
통신 방식
턴 동기, 키 입력 동기, 비동기
통신 제약 : 온라인 게임에서는 아래의 사항에 대한 대처를 고려해둬야한다.
통신 지연, 데이터 소실, 회선 끊김
통신의 구조
통신 시 데이터를 담아 보내는 일종의 그릇을 패킷이라고 한다.
패킷은 케이블과 라우터를 거쳐 통신 상대의 단말에 전달된다.
패킷에는 실제 데이터 뿐 아니라 IP주소와 포트번호가 포함된다.
패킷이 여러 이유(기기 고장, 서버 사양 초과)로 사라지거나 파괴되는 경우를 패킷 유실이라고 한다.
패킷은 실제 데이터 부분과 명령 부분으로 나뉘는데 명령 부분은 최초 4비트이다.
ICP와 UDP 프로토콜
ICP : 패킷 전달 전 패킷 전달을 미리 고지하고 완료 시엔 완료 사실을 송신 단말기에 전달한다. 온전한 전달 사실과 순서를 보증하지만 속도가 느리다.
UPD : 송신할 패킷이 있다면 바로 송신한다. 패킷이 유실되더라도 다음 패킷을 송신한다. 처리가 단순하고 빠르지만 온전한 전달과 순서를 보증하지 못한다.
간단한 소켓통신 C# 앱 콘솔로 구현
서버측
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace TestServerLearn
{
class TCP_Server
{
string _ip = "127.0.0.1";
short _port = 0;
Socket _socketListen;
Socket _connectSocket;
bool _isRelease;
// TcpListener : TCP 통신 전용 Listen
public TCP_Server(short port)
{
_port = port;
try
{
// 소켓 생성
_socketListen = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 아이피에 묶기
EndPoint ep = new IPEndPoint(IPAddress.Any, _port);
_socketListen.Bind(ep);
// Listen 상태로 기다리기
_socketListen.Listen(1);
Console.WriteLine("소켓 통신 대기중");
}
catch (Exception ex)
{
Console.WriteLine("에러!");
Console.WriteLine(ex);
}
}
~TCP_Server()
{
// 통신 종료 시
Release();
}
public void Release()
{
//통신 종료 시
if (_isRelease == false)
{
_isRelease = true;
}
}
public bool MainLoop()
{
if(_socketListen.Poll(0, SelectMode.SelectRead))
{
// 클라이언트가 소켓에 최초로 붙은 경우
_connectSocket = _socketListen.Accept();
Console.WriteLine("클라이언트 커넥트");
byte[] buffer = Encoding.UTF8.GetBytes("서버 연결 완료");
_connectSocket.Send(buffer);
}
if(_connectSocket != null && _connectSocket.Poll(0, SelectMode.SelectRead))
{
// 클라이언트에서 메세지를 보내온 경우
byte[] buffer = new byte[1024];
try
{
int recvLen = _connectSocket.Receive(buffer);
if(recvLen > 0)
{
string msg = Encoding.UTF8.GetString(buffer);
Console.WriteLine(msg);
}
}
catch (Exception ex)
{
Console.WriteLine("에러!");
Console.WriteLine(ex);
}
}
return true;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestServerLearn
{
class Program
{
static void Main(string[] args)
{
TCP_Server server = new TCP_Server(666);
// 메인 프로세싱
while (server.MainLoop())
{
// 소켓이 닫힐 떄의 프로세스 실행
}
server.Release();
}
}
}
클라이언트 측
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace TestClientLearn
{
class TCP_Client
{
const string _ip = "127.0.0.1";
const short _port = 666;
Socket _server;
bool _isRelease;
~TCP_Client()
{
// 통신 종료 시
Release();
}
public void Release()
{
//통신 종료 시
if (_isRelease == false)
{
_isRelease = true;
}
}
public bool Connect()
{
try
{
_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_server.Connect(_ip, _port);
}
catch(Exception ex)
{
Console.WriteLine("에러!");
Console.WriteLine(ex);
return false;
}
return true;
}
public bool MainLoop()
{
// 신호가 들어올 때의 처리
if (_server != null && _server.Poll(0, SelectMode.SelectRead))
{
byte[] buffer = new byte[1024];
int recvLen = _server.Receive(buffer);
if(recvLen > 0)
{
string msg = Encoding.UTF8.GetString(buffer);
Console.WriteLine(msg);
}
SendMessage("클라이언트가 붙었습니다.");
// 들어온 신호를 분해 후 확인
// 종료 신호에 따라 종료
}
return true;
}
void SendMessage(string msg)
{
if (msg.Equals(string.Empty))
{
return;
}
byte[] buffer = Encoding.UTF8.GetBytes(msg);
_server.Send(buffer);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestClientLearn
{
class Program
{
static void Main(string[] args)
{
TCP_Client client = new TCP_Client();
if (client.Connect())
{
while (client.MainLoop())
{
// 종료 확인
//ConsoleKeyInfo info = Console.ReadKey();
//if(info.Key == ConsoleKey.Escape)
//{
// break;
//}
}
}
}
}
}