Server는 Accept용 Socket을 갖고 있다.
해당 Socket을 Bind한 후, Listen 및 Accept를 해서 해당 클라이언트로 보낼 Socket을 얻어내야 한다.
즉, 서버가 해야 할 작업은 다음과 같다.
1. Bind
2. Listen
3. Accept
클라이언트에서의 Socket은 간편하다 Socket 생성 후 Connect만 진행해주면 된다.
// Server.cs
using System;
using System.Net.Sockets;
using System.Net;
using System.Text;
namespace Server
{
class Program
{
static void Main(string[] args)
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7585);
Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(endPoint);
listenSocket.Listen(10);
while (true)
{
Socket clientSocket = listenSocket.Accept();
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine($"Client : {recvData}");
byte[] sendBuff = Encoding.UTF8.GetBytes(recvData);
clientSocket.Send(sendBuff);
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Dispose();
}
}
}
}
// Client.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Client
{
class Program
{
static void Main(string[] args)
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7585);
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(endPoint);
Console.WriteLine("Connect ...");
byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World");
int sendBytes = socket.Send(sendBuff);
byte[] recvBuff = new byte[1024];
int recvBytes = socket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff,0,recvBytes);
Console.WriteLine($"Server : {recvData}");
socket.Shutdown(SocketShutdown.Both);
socket.Dispose();
}
}
}
이러한 방식으로 한 프로그래밍을 블록 소켓 프로그래밍이라고 한다.
이는 정해진 순서대로만 작성할 수 있다는 단점이 존재한다.
그 이유는 Send를 보낼 경우 스레드가 wait상태가 되고 Recieve할 수 없기 때문이다.
이를 해결하기 위해서는 비동기의, 논-블록 소켓으로 만들어야 한다.
비동기를 사용하기 위해서는 SocketAsyncEventArgs를 이용해서 Socket.*Async() 함수를 이용해야 한다.
using System;
using System.Net;
using System.Net.Sockets;
namespace AsyncServer
{
class Listener
{
Socket ListenSocket;
Action action;
public void Init(IPEndPoint endPoint, Action action)
{
ListenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
this.action += action;
ListenSocket.Bind(endPoint);
ListenSocket.Listen(10);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(args);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
args.AcceptSocket = null;
bool pending = ListenSocket.AcceptAsync(args);
if (pending == false)
OnAcceptCompleted(null, args);
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.SocketError == SocketError.Success)
action.Invoke();
else
Console.WriteLine(args.SocketError.ToString());
RegisterAccept(args);
}
}
}
Async Socket의 Listenr.cs는 다음과 같이 만들 수 있다.
SocketAsyncEventArgs는 System.Net.Sockets.Socket의 비동기 패턴을 제공하는 클래스로 *Async() 함수를 통해 넘겨준다.
Completed는 C#의 이벤트로서 *Async()를 통해서 넘겨준 작업이 끝나면 Invoke 된다.
개인적으로 AcceptAsync(args)의 리턴을 받아서 false이면 OnAcceptCompleted를 호출하는 것이 가장 이해가 안 됐었다.
해당 함수의 반환 값은 다음과 같다.
다음과 같이 Completed 이벤트가 발생하지 않을 경우 수동적으로 호출해주려는 것이다.
SocketAsyncEventArgs의 몇 가지 속성 및 메서드를 작성하겠다.