-
C.Sharp 서버 공부#1카테고리 없음 2021. 12. 17. 13:19
string host = Dns.GetHostName(); 내 컴퓨터의 호스트이름 반환
IPHostEntry hostIP = Dns.GetHostEntry(host); String변수로 참조해둔 호스트의 IP를 저장
IPAddress myIP = hostIP.AddressList[0]; hostIP는 배열형태로 여러주소를 포함가능, 0번쨰는 위의 주소
IPEndPoint endPoint = new IPEndPoint(myIP, 7777); 생성자로 최종 IP반환, 인자두개 (내아이피, 포트번호)
1. 호스트이름 찾기 + 스트링변수에 참조
2. Dns.GetHostName() + Dns.GetHostEntry() 기억, 호스트이름 찾기와 호스트입장
3. IPAddress와 IPEndPoint객체 생성자로 생성
서버
서버코어의 메인
class ServerCore
{
// 문지기, 서버의 입장관리자 리스너클래스객체 생성
static Listener listener = new Listener();
// 리스너가 패킷처리를 비동기처리시, 후에 처리할 함수대리자가 필요한데 그것이 이것
static void OnAcceptHandler(Socket clientSocket)
{
try
{
//클라가 보낼 데이터를 담을 그릇 생성
byte[] receiveBuff = new byte[1024];
// 클라가 보낸 패킷의 크기를 가늠, Length개념
int recBytes = clientSocket.Receive(receiveBuff);
// 클라가 서버에게 보낸 내용
// 서버가 해석할수 있게 문자열로 또 인코딩+반환
string recData = Encoding.UTF8.GetString(receiveBuff, 0, recBytes);
Console.WriteLine($"클라에게 받은 데이터 : {recData}");
// 클라가 서버에게 내용을 보냈으니,
// 서버가 클라에게 답장을 한다, 서버가 하고픈 의사를 BYTE배열에 인코딩+담기
byte[] sendBuff = Encoding.UTF8.GetBytes("내 이름은 서버,어서와요 나의 서버에!");
// 클라에게 보낸다
clientSocket.Send(sendBuff); // Accept와 같이 보내지 않고있다면, 여기서 대기
// 클라를 다시 내보내기
clientSocket.Shutdown(SocketShutdown.Both); // 클로즈로 닫기전, 클라의 의사를 차단
clientSocket.Close();
}
catch (Exception e) { Console.WriteLine(e); }
}
static void Main(string[] args)
{
Console.WriteLine("서버");
// CMD
string host = Dns.GetHostName(); // DNS ip주소 추출, 변수로 담아서 잔에러 방지
IPHostEntry hostIP = Dns.GetHostEntry(host); //추출한 ip주소를 인자로, 호스트를 저장
IPAddress myIP = hostIP.AddressList[0]; // 저장해둔 호스트는 배열형태, 여러개가 존재할수도잇어서,
// 방금 저장한 내 ip주소를 반환해주기
// 나의 최종 정리된 주소 개념
// 포트번호는 내 서버의 비밀번호개념
IPEndPoint endPoint = new IPEndPoint(myIP, 7777);
listener.Init(endPoint, OnAcceptHandler);
Console.WriteLine("서버 대기중");
while (true)
{
// 그저 콘솔이 끝나지않게 걸어둔 와일문
}
}
서버코어 정리
클라가 보낼 소켓을 처리할것인데
처리는 리스너클래스가 담당,
서버코어는 서버클래스가 처리를 할떄 비동기의 경우를 포함해서, 대리자로 처리할것인데 ACTION
액션에 함수 넣음
서버는 리시브버퍼 리시브사이즈 인코딩용String변수(이건 그저 임시) 센드버퍼(클라이언트에게 보낼 데이터)서버코어의 리스너 클래스
class Listener
{
Socket _listenSocket;
Action<Socket> _OnAcceptHandle; //소켓 전달이 완료되면 , 어떤 함수를 실행시켜 처리할지 정하는 대리자
public void Init(IPEndPoint endPoint, Action<Socket> OnAcceptHandler)
{
// 문지기, 서버가 클라를 받아들이고 걸르는 부분
// 인자 3가지 1.나의 최종 정리된 ip.어드레스속성, 2.소켓타입.스트림 3.티씨피유디피 속성
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
// 리스너 클래스에서의 액션에 인자함수를 넣고, 처리시기에 함수동작
_OnAcceptHandle += OnAcceptHandler; // OnCompletedAccept에서 액션에 추가한 함수 실행
// 위에서 만든 서버의 소켓문지기를 교육
_listenSocket.Bind(endPoint); //엔드포인트 ip주소 다시 삽입
_listenSocket.Listen(10); // 인자backLog -> 대기리스트 인원수 최대치
// 비동기처리를 수행할 객체 생성
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
// 비동기처리객체에 또 새로운 객체,이벤트 핸들러 대리자함수를 삽입
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnCompletedAccept);
// 동기화되었을떄 호출할 함수를 넣엇으면, 생성과 동시에, 소켓을 등록
RegisterAccept(args);
}
#region 비동기 처리위한 함수
void RegisterAccept(SocketAsyncEventArgs args) // 먼저 실제로 데이터 송수신이 이뤄지기전, 예약잡기
{
// 처리할 소켓 등록전, 기존의 잔재가 있다면 처리해주자
args.AcceptSocket = null;
bool pending = _listenSocket.AcceptAsync(args); // 당장 데이터를 주고받거나 처리가 아닌, 그저 등록
// true 현재 데이터 받기를 대기중, false 비동기처리가 이제 완료
if (pending == false)
{ OnCompletedAccept( null, args ); }
// false면 바로 OnCompleted 함수 실행
// true여도 나중에 false가 된다면, 이미 위에서 호출해둘 함수를 넣어놨기에 args.Completed 자동 실행
}
void OnCompletedAccept(object sender, SocketAsyncEventArgs args ) // 실제로 클라가 데이터를 보냈다면, 이제 예약에서 완료나머지처리
{
// 소켓 에러 확인
if (args.SocketError == SocketError.Success) //소켓 에러가 없다면 실행
{
// 소켓을 뜯어서 해석, args는 이벤트소켓클래스인데 여기서 내장함수 AcceptSocket을 인보크
// 서버코어클래스의 OnAcceptHandler(Socket clientSocket) 함수실행, 인자는 아래 args.AcceptSocket
_OnAcceptHandle.Invoke(args.AcceptSocket);
}
else //소켓에 에러가 있다면?
{
Console.WriteLine(args.SocketError.ToString()); // 항목 출력해서 확인
}
// 위에것을 모두 처리했다면, 당장은 처리할 소켓이 없기에 다시 예약으로 잡아두기
RegisterAccept(args);
}
#endregion
}
리스너 클래스 정리
서버코어에서 Listener를 정적으로 생성후
Listener의 Init함수 생성함
Init에서는 인자가 2개, IPEndPoint 아이피주소와 액션<소켓타입> 핸들러를 사용
핸들러는 당장에 처리되지않아 비동기로 후에 처리될떄 어느함수를 실행할지의 대리자
Init자체에서
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnCompletedAccept);
RegisterAccept(args);
레지스터를 했음에도 핸들러에 후처리함수를 넣은 이유는
레지스터에서 pending이 트루라면 레지스터 함수는 그저 종료될텐데
이떄 레지스터가 끝나고 함수가 실행되어야 한다면
레지스터어셉트함수 이전에 미리 넣어둔 이벤트핸들러 함수가 인보크되기에 비동기를 맞출수있다
클라
while (true)
{
// 클라가 소통할수 있는 소켓이 필요함
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
#region
// 위에서 만든 클라전용 소켓을 통해, 서버에게 의사 전달할차례
socket.Connect(endPoint); //연결시도
//리무트엔드포인트는 현재 연결하는 ip주소
Console.WriteLine($"클라가 서버로 접속중 : {socket.RemoteEndPoint}");
// 클라가 서버로 보낸다, 1. 보낼 내용 바이트배열에 담기 2. Send함수로 보냄과 동시 사이즈 추출
byte[] sendBuff = Encoding.UTF8.GetBytes("서버야 안녕 난 클라이언트야");
int sendBytes = socket.Send(sendBuff);
// 서버가 클라에게 보낸 내용을 받아서 해석
byte[] receiveBuff = new byte[1024]; // 서버에게 받을 데이터를 담을 그릇
int receiveBytes = socket.Receive(receiveBuff); // 위에서 만든 클라 소켓의 리시브함수( 서버에게 받을 그릇)
// 서버에게 받을 그릇 만들고, 생성해두었던 클라소켓의 리시브함수로 + 데이터받을 그릇으로
// 받은 데이터를 다시 인코딩으로 문자열로 풀어주기
string OpenServersAnswer = Encoding.UTF8.GetString(receiveBuff, 0, receiveBytes);
Console.WriteLine($"서버가 클라에게 \"{OpenServersAnswer}\"");
// 클라도 서버로부터 나가기
socket.Shutdown(SocketShutdown.Both);
socket.Close();
#endregion
}
catch (Exception e) { Console.WriteLine(e); }
Thread.Sleep(100);
}
스레드 슬립 100은 0.1초의 쉬는 시간을 가진다
1초는 1000밀리세컨드
수정사항, 잘못된 사항 지적 수정 환영