수색…


소개

비동기식 소켓을 사용하면 서버는 들어오는 연결을 수신하고 동기식 소켓과는 대조적으로 다른 논리를 수행하여 수신 대기 중일 때 주 스레드를 차단하고 응용 프로그램이 응답하지 않게되고 클라이언트가 연결될 때까지 고정됩니다.

비고

소켓 및 네트워크

내 네트워크 외부의 서버에 액세스하는 방법? 이것은 일반적인 질문이며 질문 할 때 주로 주제로 표시됩니다.

서버 측

서버의 네트워크에서 라우터를 서버로 포트 포워드해야합니다.

서버가 실행중인 PC 예 :

로컬 IP = 192.168.1.115

서버가 포트 1234를 수신 대기 중입니다.

Port 1234 라우터에서 들어오는 연결을 192.168.1.115

고객 입장에서

변경할 필요가있는 것은 IP뿐입니다. 루프백 주소에 연결하지 않고 서버가 실행중인 네트워크의 공용 IP에 연결하려고합니다. 이 IP는 여기서 얻을 수 있습니다 .

 _connectingSocket.Connect(new IPEndPoint(IPAddress.Parse("10.10.10.10"), 1234));

이제이 끝점에서 요청을 만듭니다. 10.10.10.10:1234 : 10.10.10.10:1234 라우터를 포트 포워드로 등록하면 서버와 클라이언트가 아무런 문제없이 연결됩니다.

로컬 IP에 연결하려면 루프백 주소를 192.168.1.178 또는 그와 비슷한 것으로 변경하기 만하면됩니다.

데이터 전송 중 :

데이터는 바이트 배열로 전송됩니다. 데이터를 바이트 배열로 묶어 다른쪽에 압축을 풀 필요가 있습니다.

소켓에 익숙하다면 보내기 전에 바이트 배열을 암호화 할 수도 있습니다. 이것은 누군가가 당신의 패키지를 훔치는 것을 막을 것입니다.

비동기 소켓 (클라이언트 / 서버) 예제.

서버 측 예제

서버용 리스너 생성

연결하는 클라이언트를 처리 할 서버 작성 및 전송할 요청 시작. 그래서 이것을 처리 할 리스너 클래스를 생성하십시오.

class Listener
{
    public Socket ListenerSocket; //This is the socket that will listen to any incoming connections
    public short Port = 1234; // on this port we will listen

    public Listener()
    {
        ListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }
 }

먼저 모든 연결을 청취 할 수있는 Listener 소켓을 초기화해야합니다. 우리는 SocketType.Stream을 사용하는 Tcp 소켓을 사용할 것입니다. 또한 우리는 서버가 들어야하는 마녀 포트를 지정합니다.

그런 다음 들어오는 연결을 듣기 시작합니다.

여기에서 사용하는 트리 메소드는 다음과 같습니다.

  1. ListenerSocket.Bind ();

    이 메소드는 소켓을 IPEndPoint에 바인드합니다. 이 클래스에는 응용 프로그램이 호스트의 서비스에 연결하는 데 필요한 호스트 및 로컬 또는 원격 포트 정보가 들어 있습니다.

  2. ListenerSocket.Listen (10);

    backlog 매개 변수는 수락을 위해 대기 할 수있는 들어오는 연결 수를 지정합니다.

  3. ListenerSocket.BeginAccept ();

    서버가 들어오는 연결을 수신하기 시작하고 다른 논리로 계속 진행됩니다. 연결이있을 때 서버는이 메소드로 다시 전환하고 AcceptCallBack 메소드를 실행합니다.

    public void StartListening()
    {
        try
        {                
                MessageBox.Show($"Listening started port:{Port} protocol type: {ProtocolType.Tcp}");                    
                ListenerSocket.Bind(new IPEndPoint(IPAddress.Any, Port));
                ListenerSocket.Listen(10);
                ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);                
        }
        catch(Exception ex)
        {
            throw new Exception("listening error" + ex);
        }
    }

그래서 클라이언트가 연결될 때 우리는이 방법으로 그들을 받아 들일 수 있습니다 :

우리가 사용하는 세 가지 방법은 다음과 같습니다.

  1. ListenerSocket.EndAccept ()

    우리는 Listener.BeginAccept() 콜백을 시작했습니다. 이제는 콜백을 끝내야합니다. The EndAccept() 메서드는 IAsyncResult 매개 변수를받습니다.이 매개 변수는 비동기 메서드의 상태를 저장합니다.이 상태에서 들어오는 연결이 들어오는 소켓을 추출 할 수 있습니다.

  2. ClientController.AddClient()

    EndAccept() 에서 얻은 소켓을 사용하여 자체 제작 된 메소드 (서버 예제 아래 코드 ClientController EndAccept() 를 사용하여 클라이언트를 만듭니다.

  3. ListenerSocket.BeginAccept ()

    새 연결을 처리 할 때 소켓이 완료되면 다시 청취해야합니다. 이 콜백을 잡을 메소드를 전달하십시오. 또한 리스너 소켓을 int로 전달하여 향후 연결에이 소켓을 재사용 할 수 있습니다.

    public void AcceptCallback(IAsyncResult ar)
    {
        try
        {
            Console.WriteLine($"Accept CallBack port:{Port} protocol type: {ProtocolType.Tcp}");
            Socket acceptedSocket = ListenerSocket.EndAccept(ar);               
            ClientController.AddClient(acceptedSocket);

            ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);
        }
        catch (Exception ex)
        {
            throw new Exception("Base Accept error"+ ex);
        }
    }

이제 우리는 Listening Socket을 가지고 있지만, 클라이언트가 다음 코드가 보여주는 데이터를 어떻게 수신 할 수 있습니까?

각 클라이언트에 대해 서버 수신기 만들기

먼저 Socket을 매개 변수로받는 생성자를 사용하여 receive 클래스를 만듭니다.

    public class ReceivePacket
    {
        private byte[] _buffer;
        private Socket _receiveSocket;

        public ReceivePacket(Socket receiveSocket)
        {
           _receiveSocket = receiveSocket;
        }
    }

다음 방법에서는 버퍼에 4 바이트 (Int32)의 크기를 지정하거나 패키지에 {Lenght, 실제 데이터}를 포함하는 것으로 시작합니다. 그래서 우리는 실제 데이터를위한 나머지 데이터의 길이를 위해 우리가 예비하는 처음 4 바이트입니다.

다음으로 BeginReceive () 메소드를 사용합니다. 이 메서드는 연결된 클라이언트로부터 수신을 시작하는 데 사용되며 데이터를 받으면 ReceiveCallback 함수를 실행합니다.

    public void StartReceiving()
    {
        try
        {
            _buffer = new byte[4];
            _receiveSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
        }
        catch {}
    }

    private void ReceiveCallback(IAsyncResult AR)
    {
        try
        {
            // if bytes are less than 1 takes place when a client disconnect from the server.
            // So we run the Disconnect function on the current client
            if (_receiveSocket.EndReceive(AR) > 1)
            {
                // Convert the first 4 bytes (int 32) that we received and convert it to an Int32 (this is the size for the coming data).
                _buffer = new byte[BitConverter.ToInt32(_buffer, 0)];  
                // Next receive this data into the buffer with size that we did receive before
                _receiveSocket.Receive(_buffer, _buffer.Length, SocketFlags.None); 
                // When we received everything its onto you to convert it into the data that you've send.
                // For example string, int etc... in this example I only use the implementation for sending and receiving a string.

                // Convert the bytes to string and output it in a message box
                string data = Encoding.Default.GetString(_buffer);
                MessageBox.Show(data);
                // Now we have to start all over again with waiting for a data to come from the socket.
                StartReceiving();
            }
            else
            {
                Disconnect();
            }
        }
        catch
        {
            // if exeption is throw check if socket is connected because than you can startreive again else Dissconect
            if (!_receiveSocket.Connected)
            {
                Disconnect();
            }
            else
            {
                StartReceiving();
            }
        }
    }

    private void Disconnect()
    {
        // Close connection
        _receiveSocket.Disconnect(true);
        // Next line only apply for the server side receive
        ClientController.RemoveClient(_clientId);
        // Next line only apply on the Client Side receive
        Here you want to run the method TryToConnect()
    }

그래서 우리는 들어오는 연결을 수신하고 수신 할 수있는 서버를 설정했습니다. 클라이언트가 연결되면 클라이언트 목록에 추가되고 모든 클라이언트는 자체 수신 클래스를 갖습니다. 서버가 수신 대기하게하려면 다음을 수행하십시오.

Listener listener = new Listener();
listener.StartListening();

이 예제에서 사용하는 일부 클래스

    class Client
    {
        public Socket _socket { get; set; }
        public ReceivePacket Receive { get; set; }
        public int Id { get; set; }

        public Client(Socket socket, int id)
        {
            Receive = new ReceivePacket(socket, id);
            Receive.StartReceiving();
            _socket = socket;
            Id = id;
        }
    }

     static class ClientController
     {
          public static List<Client> Clients = new List<Client>();

          public static void AddClient(Socket socket)
          {
              Clients.Add(new Client(socket,Clients.Count));
          }

          public static void RemoveClient(int id)
          {
              Clients.RemoveAt(Clients.FindIndex(x => x.Id == id));
          }
      }

클라이언트 측 예제

서버에 연결 중

무엇보다도 우리는 서버에 연결하는 클래스를 만들고 싶습니다. 우리가 제공하는 이름은 다음과 같습니다. Connector :

class Connector
{
    private Socket _connectingSocket;
}

이 클래스의 다음 메소드는 TryToConnect ()입니다.

이 방법은 몇 가지 흥미로운 점이 있습니다.

  1. 소켓을 만듭니다.

  2. 다음으로 소켓이 연결될 때까지 반복합니다.

  3. 모든 루프는 1 초 동안 스레드를 잡고 있습니다. 우리는 DOS에 서버 XD를 원하지 않습니다.

  4. Connect () 를 사용하면 서버에 연결하려고 시도합니다. 실패하면 예외가 발생하지만 프로그램이 서버에 계속 연결됩니다. 이 경우 Connect CallBack 메서드를 사용할 수 있지만 소켓이 연결될 때 메서드 호출을 위해 이동합니다.

  5. 클라이언트가 이제 포트 1234의 로컬 PC에 연결하려고합니다.

     public void TryToConnect()
     {
         _connectingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
         
          while (!_connectingSocket.Connected)
          {
              Thread.Sleep(1000);
    
              try
              {
                  _connectingSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
              }
              catch { }
          }
          SetupForReceiveing();
         }
     }
    
     private void SetupForReceiveing()
     {
        // View Client Class bottom of Client Example
         Client.SetClient(_connectingSocket);
         Client.StartReceiving();
     }
    

서버에 메시지 보내기

이제 우리는 거의 마무리 또는 소켓 응용 프로그램을 가지고 있습니다. 우리가 제트기를 가지고 있지 않은 유일한 방법은 서버에 메시지를 보내기위한 클래스입니다.

public class SendPacket
{
    private Socket _sendSocked;

    public SendPacket(Socket sendSocket)
    {
        _sendSocked = sendSocket;
    }

    public void Send(string data)
    {
        try
        {         
            /* what hapends here:
                 1. Create a list of bytes
                 2. Add the length of the string to the list.
                    So if this message arrives at the server we can easily read the length of the coming message.
                 3. Add the message(string) bytes
            */
  
            var fullPacket = new List<byte>();
            fullPacket.AddRange(BitConverter.GetBytes(data.Length));
            fullPacket.AddRange(Encoding.Default.GetBytes(data));

            /* Send the message to the server we are currently connected to.
            Or package stucture is {length of data 4 bytes (int32), actual data}*/
            _sendSocked.Send(fullPacket.ToArray());
        }
        catch (Exception ex)
        {
            throw new Exception();
        }
    }

마지막으로 두 개의 버튼을 하나씩 연결하고 다른 하나는 메시지를 전송합니다.

    private void ConnectClick(object sender, EventArgs e)
    {
        Connector tpp = new Connector();
        tpp.TryToConnect();
    }

    private void SendClick(object sender, EventArgs e)
    {
        Client.SendString("Test data from client");
    }

이 예제에서 사용한 클라이언트 클래스

    public static void SetClient(Socket socket)
    {
        Id = 1;
        Socket = socket;
        Receive = new ReceivePacket(socket, Id);
        SendPacket = new SendPacket(socket);
    }

주의

서버의 수신 클래스는 클라이언트의 수신 클래스와 동일합니다.

결론

이제 서버와 클라이언트가 생겼습니다. 이 기본 예제를 사용할 수 있습니다. 예를 들어 서버가 파일이나 다른 노래를 수신 할 수있게하십시오. 또는 클라이언트에게 메시지를 보냅니다. 서버에서 클라이언트 목록을 얻었으므로 클라이언트로부터받은 정보를받을 때 받게됩니다.

최종 결과: 여기에 이미지 설명을 입력하십시오.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow