콘솔 환경으로 구현한 에코 서버&클라이언트는 블로킹 모드여도 불편함이 없었지만
GUI 윈도우즈 환경에서는 사용자의 이벤트에 따라 독립적으로 실행되어야만 한다.
별다른 기능이 없는 프로그램인지라 멀티스레드로도 간단히 구현되겠지만,
비동기소켓을 이용하는 것이 흐름이 매끄럽다.
.NET의 비동기소켓을 이용한 TCP 서버 & 클라이언트
블로킹 모드와 비교해서 크게 달라지는 방식은
Begin메서드에 AsyncCallback 함수를 달아주고 콜백함수가 처리할 몫으로 End메서드를 추가해준다.
간단히 해서
"Begin메서드에 신호가오면 지정해준 메서드를 수행해라" 라고 위임해 버리는 것이다.
(프로젝트는 마법사로 생성하고 콘트롤 배치는 생략했다.)
서버 :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace TcpSrvr
{
public partial class Form1 : Form
{
private byte[] data = new byte[1024];
private int size = 1024;
private Socket server;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9090);
server.Bind(iep);
server.Listen(5);
server.BeginAccept(new AsyncCallback(AcceptConn), server);
}
private void button1_Click(object sender, EventArgs e)
{
Close();
}
private void AcceptConn(IAsyncResult iar)
{
Socket oldserver = (Socket)iar.AsyncState;
Socket client = oldserver.EndAccept(iar);
textBox1.Text = client.RemoteEndPoint.ToString() + "의 연결 요청 수락";
string welcome = "서버 메세지: Welcome to my server";
byte[] message1 = Encoding.UTF8.GetBytes(welcome);
client.BeginSend(message1, 0, message1.Length, SocketFlags.None, new AsyncCallback(SendData), client);
}
private void SendData(IAsyncResult iar)
{
Socket client = (Socket)iar.AsyncState;
int sent = client.EndSend(iar);
client.BeginReceive(data, 0, size, SocketFlags.None, new AsyncCallback(ReceiveData), client);
}
private void ReceiveData(IAsyncResult iar)
{
Socket client = (Socket)iar.AsyncState;
int recv = client.EndReceive(iar);
if (recv == 0)
{
client.Close();
textBox1.Text = "연결요청 대기중...";
server.BeginAccept(new AsyncCallback(AcceptConn), server);
return;
}
string recvData = Encoding.UTF8.GetString(data, 0, recv);
textBox2.Text += recvData + Environment.NewLine;
byte[] message2 = Encoding.UTF8.GetBytes(recvData);
client.BeginSend(message2, 0, message2.Length, SocketFlags.None, new AsyncCallback(SendData), client);
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
textBox2.SelectionStart = textBox2.Text.Length;
textBox2.ScrollToCaret();
}
}
}

1. 폼이 로드되면
소켓생성 -> IEP생성 -> 소켓에 IEP바인드 -> 소켓 리슨 -> 소켓 BeginAccept(요청 대기)
2. BeginAccept에 신호가 들어오면(요청이 들어오면) 수락메서드 실행
소켓 재생성 -> EndAccept(수락) -> 환영 메세지 보내기 BeginSend
3. BeginSend에 신호가 들어오면 보내기 메서드 실행
소켓 재생성 -> EndSend(전송) -> 메세지 BeginReceive
4. BeginReceive에 신호가 들어오면 받기 메서드 실행
소켓 재생성 -> EndReceive(수신) -> 수신된 메세지 출력 -> 수신된 메세지 돌려주기 BeginSend (3번으로)
클라이언트 :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace TcpClient
{
public partial class Form1 : Form
{
private Socket client;
private byte[] data = new byte[1024];
private int size = 1024;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "접속중......";
Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9090);
newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock);
}
private void button2_Click(object sender, EventArgs e)
{
client.Close();
textBox1.Text = "접속 종료";
button1.Enabled = true;
button2.Enabled = false;
button3.Enabled = false;
}
private void button3_Click(object sender, EventArgs e)
{
byte[] message = Encoding.UTF8.GetBytes(textBox2.Text);
textBox2.Clear();
client.BeginSend(message, 0, message.Length, SocketFlags.None, new AsyncCallback(SendData), client);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (client != null)
{
client.Close();
}
}
private void Connected(IAsyncResult iar)
{
client = (Socket)iar.AsyncState;
try
{
client.EndConnect(iar);
textBox1.Text = client.RemoteEndPoint.ToString() + "에 연결 완료";
client.BeginReceive(data, 0, size, SocketFlags.None, new AsyncCallback(ReceiveData), client);
button1.Enabled = false;
button2.Enabled = true;
button3.Enabled = true;
}
catch(SocketException)
{
textBox1.Text = "연결 실패";
}
}
private void ReceiveData(IAsyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
int recv = remote.EndReceive(iar);
string stringData = Encoding.UTF8.GetString(data, 0, recv);
textBox3.Text += stringData + Environment.NewLine;
}
private void SendData(IAsyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
int sent = remote.EndSend(iar);
remote.BeginReceive(data, 0, size, SocketFlags.None, new AsyncCallback(ReceiveData), remote);
}
private void textBox3_TextChanged(object sender, EventArgs e)
{
textBox3.SelectionStart = textBox3.Text.Length;
textBox3.ScrollToCaret();
}
}
}

1. 연결 버튼이 눌러지면
소켓 생성 -> IEP 생성 -> IEP에 연결 BeginConnect(연결 요청전)
2. BeginConnect에 신호가 오면 연결 메서드 실행
소켓 재생성 -> EndConnect(연결 요청) -> 메세지 수신 BeginReceive
3. BeginReceive에 신호가 오면 받기 메서드 실행
소켓 재생성 -> EndReceive(수신) -> 수신된 메세지 출력
4. 보내기 버튼이 눌러지면
BeginSend(송신 전)
5. BeginSend에 신호가 오면 보내기 메서드 실행
소켓 재생성 -> EndSend(송신) -> BeginReceive (3번으로)
실행 화면 :

초기화면

연결 버튼 클릭

메세지 입력