소켓 프로그래밍의 기본 개념을 알고 있다면 구현은 2가지 방법으로 가능하다.
소켓을 사용하려면 동기 / 비동기 방식 중 하나를 선택한다.
동기 방식을 쓰면 비교적 간단하지만 요청/응답 중에는 어플리케이션의 다른 기능들이 동작하지 않는다. 말 그대로 어플리케이션의 메인쓰레드와 동기로 동작하기 때문에 메인쓰레드가 할 일을 모두 잡아먹는다.
반면에 비동기 방식은 어플리케이션의 메인쓰레드와 비동기로 동작하기 때문에 메인쓰레드는 간섭하지 않는다. 따라서 메인쓰레드로는 나머지 원하는 작업을 진행시킬 수 있고 백그라운드에서 소켓의 송수신이 이루어진다.
이번에는 우선 동기방식부터 다루려고 한다.
서버와 클라이언트 순서는 상관 없지만 일단 클라이언트 소켓에 관한 예제이다.
Client Socket
byte[] bytes = new byte[1024];
응답을 받았을 때 담을 바이트 배열을 선언해준다.
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress, 12345);
TCP/IP 소켓을 위한 기본적인 것들을 설정해준다.
ip 주소를 정해주고 소켓 연결을 위해 IPEndPoint 객체를 생성한다.
IPEndPoint() 의 두번째 인자는 포트 번호로서 본인이 사용하고자 하는 포트를 열고 적어주면 된다.
Socket sender = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp );
위에서 설정해준 값들을 가지고 소켓 객체를 생성한다.
sender.Connect(remoteEP);
서버 소켓에게 연결 요청을 보낸다.
byte[] msg = Encoding.ASCII.GetBytes("This is a test<EOF>");
int bytesSent = sender.Send(msg);
int bytesRec = sender.Receive(bytes);
소켓에 제대로 연결이 됐다면 이제 데이터 통신을 진행할 수 있다.
원하는 데이터를 위의 msg와 같이 바이트 배열로 담아 Send() 함수를 통해 보낸다.
그리고 Receive() 함수로 응답을 받고 매개변수로 처음에 선언해준 바이트 배열을 넣어줌으로써 응답 데이터를 받아낸다. 만약 이 단계에서 문제가 생기거나 응답이 오지 않는다면 동기 방식이기 때문에 프로그램은 다른 일을 하지 않고 계속 Receive() 함수에 걸려 서버로부터 응답을 기다린다.
sender.Shutdown(SocketShutdown.Both);
sender.Close();
데이터 송수신 작업이 끝났다면 소켓을 닫아 종료한다.
Shutdown() 함수로 연결을 끊을 수 있고 Close() 로 소켓을 닫는다.
클라이언트 소켓의 전체 코드는 다음과 같다.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class SyncSocketClient {
public static void StartClient() {
byte[] bytes = new byte[1024];
try {
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress,11000);
Socket sender = new Socket(ipAddress.AddressFamily,
SocketType.Stream, ProtocolType.Tcp );
try {
sender.Connect(remoteEP);
byte[] msg = Encoding.ASCII.GetBytes("This is a test<EOF>");
int bytesSent = sender.Send(msg);
int bytesRec = sender.Receive(bytes);
sender.Shutdown(SocketShutdown.Both);
sender.Close();
} catch (Exception e) {
// handle exception
}
} catch (Exception e) {
// handle exception
}
}
public static int Main(String[] args) {
StartClient();
return 0;
}
}
예외를 발생시킬 수 있는 경우가 너무 많으므로 당연히 try-catch 문으로 예외처리를 해준다.
이제 서버 소켓을 구현할 차례다.
연결되는 부분만 다르고 나머지는 똑같기 때문에 사실상 별 다를건 없다.
Server Socket
public static string data = null;
byte[] bytes = new Byte[1024];
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 12345);
Socket listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
여기까지는 완전히 같다.
포트번호를 맞춰주고 소켓 객체를 생성해 통신할 준비를 한다.
listener.Bind(localEndPoint);
listener.Listen(10);
생성한 소켓에 설정해 둔 IPEndPoint 객체를 바인드하고 Listen() 함수를 써서 클라이언트로부터 연결 요청을 기다린다. Listen() 함수의 파라미터 10은 연결 큐의 최대 길이로 요청을 최대 몇개까지 받아놓을 수 있을지를 정하는 파라미터이다.
Socket handler = listener.Accept();
조금전에 사용하던 listener는 Listen() 함수를 써서 연결을 담당하려고 생성했던 소켓이다. 그리고 그 소켓이 연결이 완료되면 Accept() 함수가 호출되며 연결된 소켓을 반환하고 반환된 소켓을 사용하면 클라이언트로부터 오는 데이터를 받을 수 있다. 이 예제에서는 그 반환된 소켓의 이름을 handler라고 한다.
while (true) {
int bytesRec = handler.Receive(bytes);
data += Encoding.ASCII.GetString(bytes,0,bytesRec);
if (data.IndexOf("<EOF>") > -1) {
break;
}
}
byte[] msg = Encoding.ASCII.GetBytes(data);
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
클라이언트로부터 받아온 데이터를 앞서 만들어두었던 바이트 배열에 담는다. 문장의 끝까지 담기 위해 반복문을 돌리고 앞서 선언해놓은 string 값에 받아오는 대로 파싱해 넣는다. 문장의 끝을 if 문에서 판별하고 끝이라면 반복문을 빠져나간다.
다 받아온 데이터를 msg라는 바이트 배열에 담아 다시 클라이언트로 보내준다. 당연히 꼭 이렇게 똑같이 보내줄 필요는 없고 본인이 원하는 작업을 수행한 후 그에 맞는 응답을 보내주면 된다. 데이터 송/수신이 끝나면 마찬가지로 Shutdown(), Close() 함수로 소켓을 닫고 통신을 종료한다.
서버 소켓의 전체 코드는 다음과 같다.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class SyncSocketListener {
public static string data = null;
public static void StartListening() {
byte[] bytes = new Byte[1024];
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 12345);
Socket listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try {
listener.Bind(localEndPoint);
listener.Listen(10);
while (true) { .
Socket handler = listener.Accept();
data = null;
while (true) {
int bytesRec = handler.Receive(bytes);
data += Encoding.ASCII.GetString(bytes,0,bytesRec);
if (data.IndexOf("<EOF>") > -1) {
break;
}
}
byte[] msg = Encoding.ASCII.GetBytes(data);
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
}
} catch (Exception e) {
// handle exception
}
}
public static int Main(String[] args) {
StartListening();
return 0;
}
}
'.NET' 카테고리의 다른 글
[C#] DataGridView 로딩 시 셀 선택 막기 (0) | 2020.01.28 |
---|---|
[C#] 소켓 프로그래밍의 기본 개념 (0) | 2020.01.21 |
[C#] 문자열 자르기 - Substring, Split, IndexOf 의 사용 (0) | 2020.01.21 |
[C#] 파일 이동 및 복사, 삭제 (0) | 2020.01.21 |
[C#] 기본적인 파일 쓰기 및 읽기 (0) | 2020.01.20 |