- 서버측에서의 Binding까지는 UDP 서버 소켓 사용하는 방법과 동일함.
- TCP/IP 통신을 할 때 Socket의 생성자는 아래와 같이 지정한다.
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- TCP/IP 서버는 아래와 같이 동작함.
Binding -> Listen(연결 받을 수 있는 상태) -> Accept(클라이언트 접속 큐에서 하나 꺼내옴)
- TCP/IP에서는 Send / Receive를, UDP에서는 SendTo / ReceiveFrom을 사용하여야 한다. 이미 Accept로 클라이언트 소켓 정보를 알고있으므로, Send / Receive에는 접점 정보를 알아내기 위한 IPEndPoint 인자가 없다.
[ TCP 서버측 소켓 ]
private static void serverFunc(object obj) { using (Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { IPEndPoint clientEP = new IPEndPoint(IPAddress.Any, 10200); serverSocket.Bind(clientEP); serverSocket.Listen(10); // 10 : 클라이언트 연결 큐 크기 while(true) { Socket clientSocket = serverSocket.Accept(); byte[] receiveByte = new byte[1024]; int nRecv = clientSocket.Receive(receiveByte); string txt = Encoding.UTF8.GetString(receiveByte, 0, nRecv); byte[] sendByte = Encoding.UTF8.GetBytes("Hello:" + txt); clientSocket.Send(sendByte); clientSocket.Close(); // Close 꼭 해줄 것. } } }
[ TCP 클라이언트측 소켓 ]
- UDP와의 차이점은, Connect 함수가 추가된다는 것.
private static void clientFunc(object obj) { using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { EndPoint serverEP = new IPEndPoint(IPAddress.Loopback, 10200); socket.Connect(serverEP); // TCP/IP에서, Client가 Server 연결시 필요 byte[] buf = Encoding.UTF8.GetBytes(DateTime.Now.ToString()); socket.Send(buf); byte[] receiveBytes = new byte[1024]; int nRecv = socket.Receive(receiveBytes); string txt = Encoding.UTF8.GetString(receiveBytes, 0, nRecv); Console.WriteLine(txt); } Console.WriteLine("TCP/IP Client Closed."); }
[ TCP 서버 개선 - 다중 스레드와 비동기 통신 ]
- 그러나 이러한 방식은 Send/Receive에서 blocking되므로 빠르게 Accept 할 수 없다는 단점이 있다. 개선을 위해, Accept로 받은 클라이언트 처리를 별도의 스레드에 위임할 수 있다. 클라이언트 하나 당 하나의 스레드가 필요하므로 부하가 가중될 수도 있다.
private static void serverFunc(object obj) { using (Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { EndPoint clientEP = new IPEndPoint(IPAddress.Any, 10200); serverSocket.Bind(clientEP); serverSocket.Listen(10); while (true) { Socket clientSocket = serverSocket.Accept(); ThreadPool.QueueUserWorkItem(clientSocketProcess, clientSocket); } } } private static void clientSocketProcess(object state) { Socket clientSocket = state as Socket; byte[] receiveBytes = new byte[1024]; int nRecv = clientSocket.Receive(receiveBytes); string txt = Encoding.UTF8.GetString(receiveBytes, 0, nRecv); clientSocket.Send(Encoding.UTF8.GetBytes(txt)); clientSocket.Close(); }
- 위 단점은 비동기 통신으로 해결할 수 있다.
using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; class Program { static void Main(string[] args) { Thread serverThread = new Thread(serverFunc); serverThread.IsBackground = true; serverThread.Start(); Thread.Sleep(500); // 소켓 서버용 스레드가 실행될 시간을 주기 위해 Thread clientThread = new Thread(clientFunc); clientThread.IsBackground = true; clientThread.Start(); Console.WriteLine("종료하려면 아무키나 누르세요."); Console.ReadLine(); } public class AsyncStateData { public byte[] buffer; public Socket socket; } private static void serverFunc(object obj) { using (Socket srvSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 10200); srvSocket.Bind(endPoint); srvSocket.Listen(10); while(true) { Socket clientSocket = srvSocket.Accept(); AsyncStateData data = new AsyncStateData(); data.buffer = new byte[1024]; data.socket = clientSocket; // public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state) clientSocket.BeginReceive(data.buffer, 0, data.buffer.Length, SocketFlags.None, asyncReceiveCallback, data); } } } public static void asyncReceiveCallback(IAsyncResult asyncResult) { AsyncStateData receiveData = asyncResult.AsyncState as AsyncStateData; int nRecv = receiveData.socket.EndReceive(asyncResult); string txt = Encoding.UTF8.GetString(receiveData.buffer, 0, nRecv); byte[] sendBytes = Encoding.UTF8.GetBytes("Hello:" + txt); receiveData.socket.BeginSend(sendBytes, 0, sendBytes.Length, SocketFlags.None, asyncSendCallback, receiveData.socket); } public static void asyncSendCallback(IAsyncResult asyncResult) { Socket socket = asyncResult.AsyncState as Socket; socket.EndSend(asyncResult); socket.Close(); } private static void clientFunc(object obj) { using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { EndPoint serverEP = new IPEndPoint(IPAddress.Loopback, 10200); socket.Connect(serverEP); // TCP/IP에서, Client가 Server 연결시 필요 byte[] buf = Encoding.UTF8.GetBytes(DateTime.Now.ToString()); socket.Send(buf); byte[] receiveBytes = new byte[1024]; int nRecv = socket.Receive(receiveBytes); string txt = Encoding.UTF8.GetString(receiveBytes, 0, nRecv); Console.WriteLine(txt); } Console.WriteLine("TCP/IP Client Closed."); } }
- 인터넷이 갑자기 끊기거나 등의 상황이 있을 수 있다. Receive/Send를 호출할 때는 try/catch로 SocketException을 처리할 수 있도록 구현해야 한다.
출처 : 시작하세요! C# 7.1 프로그래밍(위키북스, 정성태님 저)
'Programming > C#' 카테고리의 다른 글
MSSQL Database 연동(2) (0) | 2019.03.11 |
---|---|
MSSQL Database 연동(1) (0) | 2019.03.05 |
app.config (0) | 2019.03.05 |
[네트워크 프로그래밍] Http 통신 (0) | 2019.03.02 |
[네트워크 프로그래밍] UDP 예제 (0) | 2019.02.27 |