728x90
300x250

[네트워크(Networks)] C#, C++, Java에서의 소켓 프로그래밍

이번에 소개할 것은 C#, C++, Java에서 소켓 프로그래밍을 사용하는 방법에 대해서 소개한다.


1. 소스코드

C#에서의 소켓 프로그래밍 작성 예시이다.
IPv4 환경에서 확인함.

운영체제: Microsoft Windows 10
소프트웨어: Visual Studio Community 2015

 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;

namespace Example
{
    class ServerSide
    {
        public string data = null;

        public void listenSocket()
        {
            byte[] buf;
            Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 8088);

            server.Bind(ipEndPoint);
            server.Listen(10);

            System.Console.WriteLine("연결 요청(Connection Request)");

            // 클라이언트 연결 받음.
            Socket client = server.Accept();

            // 클라이언트의 데이터 - 보내기 / 받기
            buf = new byte[1024];
            client.Receive(buf);

            System.Console.WriteLine(Encoding.Default.GetString(buf));

        }
    }

    class ClientSide
    {
        public void listenSocket()
        {
            //IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
            //IPAddress ipAddress = ipHostInfo.AddressList[0];
            // IPEndPoint ipep = new IPEndPoint(ipAddress, 8088);

            IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8088);
            String buf;
            Byte[] data;
           
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            client.Connect(ipep);

            Console.WriteLine("소켓 연결(Socket connect)");

            // 데이터 자료 바이트 형으로 받아오기
            data = new byte[1024];
            client.Receive(data);

            // 형 변환 -> getString() 형으로 인코딩
            buf = Encoding.Default.GetString(data);

            Console.WriteLine(buf);

            buf = "소켓 접속 확인 됐습니다.";
            data = Encoding.Default.GetBytes(buf);

            client.Send(data);
            client.Close();

            Console.WriteLine("아무 키나 눌러주세요.");
            Console.ReadLine();

        }
    }
}
 

 NetworkSide.cs

 

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;

 namespace Example
 {
       class Program
       {
               static void Main(string[] args)
               {
                       ServerSide serverside = new ServerSide() ;
                       serverside.listenSocket();
               }
       }
 } 

Program.cs

[첨부(Attachment)]
network_csharp.7z


2. (Gpp)C++에서의 소켓 프로그래밍 작성 예시입니다. (리눅스에서만 가능)

C언어로 구현된 소켓 프로그래밍을 C++에서도 지원이 가능한 형태로 제작하였다.
IPv4 기반에 맞춰 작성함.

 

 

 

운영체제: 우분투(Ubuntu 16.04)
소프트웨어: Gpp, Gcc

 

 

/*
 *   Network.h
 */

 #include <iostream>
 #include <unistd.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <arpa/inet.h>

 using namespace std;

 #ifndef SERVER_H_
 #define SERVER_H_

 // 0.
 class INetwork{
       virtual void listenSingleSocket() = 0;
 };

 // 1. 서버사이드(Server-Side)
 class Server : public INetwork{

 protected:

 private:
     int welcomeSocket, newSocket;

 public:
     Server(){
            this->welcomeSocket = 0;
            this->newSocket = 0;
     }

     void listenSingleSocket(){

           char buffer[1024];
           struct sockaddr_in serverAddr;
           struct sockaddr_storage serverStorage;
           socklen_t addr_size;

           welcomeSocket = socket(PF_INET, SOCK_STREAM, 0);

           serverAddr.sin_family = AF_INET;
           serverAddr.sin_port = htons(8000);
           serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

           int i = 0;

           while(i < sizeof(serverAddr.sin_zero) ){
                 serverAddr.sin_zero[i] = '\0';
                 i++;
           }

           bind(welcomeSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr) );

           if ( listen(welcomeSocket, 5) == 0 ){
                  cout << "Listening\n";
           }
           else
          {
                 cout << "Error\n";
          }

           addr_size = sizeof(serverStorage);
           newSocket = accept(welcomeSocket, (struct sockaddr *) &serverStorage, &addr_size);

           buffer[0] = 'H';
           buffer[1] = 'e';
           buffer[2] = 'l';
           buffer[3] = 'l';
           buffer[4] = 'o';

           send(newSocket, buffer, 13, 0);

     }

 };

 class Client : public INetwork{

 private:
         int clientSocket;
         char buffer[1024];
         struct sockaddr_in serverAddr;
         socklen_t addr_size;

 public:
         Client(){
               this->clientSocket = 0;
         }

         void listenSingleSocket(){

              clientSocket = socket(PF_INET, SOCK_STREAM, 0);

               serverAddr.sin_family = AF_INET;
               serverAddr.sin_port = htons(8000);

               serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

               int i = 0;
               while (i < sizeof(serverAddr.sin_zero)){
                      serverAddr.sin_zero[i] = '\0';
                       i++;
               }

                addr_size = sizeof(serverAddr);

                connect(clientSocket, (struct sockaddr *) &serverAddr, addr_size);

                // 문자열 전달 받음 - (unsigned) char[1024]
                recv(clientSocket, buffer, 1024, 0);
                cout << "데이터 전송 받은 문자:" << buffer << endl;
          }

};

#endif /* SERVER_H_ */

 Network.h

 #include <iostream>
 #include "Network.h"

 using namespace std;

 int main() {
        /*
        Server *server = new Server();
        server->listenSingleSocket();
        */

       Client *client = new Client();
       client->listenSingleSocket();
       return 0;
 }

 Program.cpp

[첨부(Attachment)]
Network-gpp.zip


3. 자바에서의 소켓 프로그래밍

싱글 쓰레드 타입으로 작성함. IPv4 환경에서 확인하였다.
IPv6는 확인하지 못함.

 

 

 

운영체제: Microsoft Windows 10 
소프트웨어: Eclipse Luna, JDK 1.81

 

 
 package Network;

 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.net.Socket;
 import java.net.UnknownHostException;

 public class ClientSide implements IHost{
 
        private Socket socket;
        private Host host;
 
        public ClientSide(){
            socket = null;
            host = null;
        }
 
        public Host getHost(){
            return host;
        }
 
        public void setHost(Host host){
            this.host = host;
        }
 
        public void listenSocket(){
  
            InputStream in = null;
            OutputStream out = null;
  
            BufferedReader br = null;
            BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter pw = null;
  
            String myMsg = null;  // 전달 메시지
            String echo = null;  // 받은 메시지
  
            try{
                 socket = new Socket( host.getIP() , host.getPort() );
    
                 in = socket.getInputStream();
                 out = socket.getOutputStream();
   
                 pw = new PrintWriter(new OutputStreamWriter(out));
                 br = new BufferedReader(new InputStreamReader(in));
   
                while((myMsg = input.readLine()) != null){
    
                      if ( myMsg.equals("/q")){
                            break;
                      }
    
                      pw.println(myMsg);
                      pw.flush();
    
                      echo = br.readLine();
                      System.out.println( "서버사이드 받은 메시지:" + echo );
     
                }
   
                pw.close();
                br.close();
                socket.close();
   
        }
        catch(UnknownHostException e){
                System.out.println("미확인 호스트: " + host.getIP() + ":" + host.getPort() );
                System.exit(-1);
        }
        catch(IOException e){
                System.out.println("입출력 안됨.");
                System.exit(1);
        }

    }
 
}

 ClientSide.java

 
 package Network;

 interface IHost{
        void setHost(Host host);
        Host getHost();
 }

 public class Host{
 
        private String ip;
        private int port;
 
        public Host(int port){
              ip = "";
              this.port = port;
        }
 
        public Host(String ip, int port){
                this.ip = ip;
                this.port = port;
        }
 
        public String getIP(){
                return this.ip;
        }
 
        public int getPort(){
                return this.port;
        }
 
 }

 Host.java

 
 package Network;

 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.net.ServerSocket;
 import java.net.Socket;

class ClientWorker implements Runnable{

         private int id;
         private Socket client;
         private String line;
 
         public ClientWorker(Socket client, int id){

              this.client = client;
              this.id = id;
         }
 
         public void run() {
  
             InputStream in = null;
             OutputStream out = null;
  
             PrintWriter pw = null;
             BufferedReader br = null;
  
             String recvStr = "";

             try{
                 in = client.getInputStream();
                 out = client.getOutputStream();
   
                 pw = new PrintWriter(new OutputStreamWriter(out));
                 br = new BufferedReader(new InputStreamReader(in));
   
             }catch(IOException e){
                 System.out.println("읽기 실패(Read Failed)");
                 System.exit(-1);
             }

             try {
   
                  while( (recvStr = br.readLine() ) != null ){
    
                        appendText(recvStr);
                        System.out.print( "쓰레드(Thread)" + id + ":" );
                        System.out.println(line);
                        pw.println(recvStr);
                        pw.flush();
                  }
   
                  client.close();
   
             } catch (IOException e) {
                   System.out.println("읽기 실패(Read Failed");
                   System.exit(-1);
             }
  
       }
 
        // 동기화 추가
        public synchronized void appendText(String strText){
  
              if ( line == null )
              {
                   line = strText;
              }
              else{
                   line = line + strText;
              }
        }
 
 }

 public class ServerSide implements IHost{

         private ServerSocket server;
         private Socket client;
         private Host host;
         private static int clientID;
 
         public ServerSide(){
                server = null;
                client = null;
                host = null;
                clientID = 0;
         }
 
         public Host getHost(){
                return host;
          }
 
          public void setHost(Host host) {
                 this.host = host;
          }
 
          // 싱글(Single)
          public void listenSingleSocket(){

                String line;
   
                InputStream in = null;
                OutputStream out = null;
  
                PrintWriter pw = null;
                BufferedReader br = null;
  
                String recvStr = null;
  
                try{   
                      server = new ServerSocket( host.getPort() ); 
                }
                catch(IOException e){
                      System.out.println("포트 " + host.getPort() + "번은 응답하지 않습니다.");
                      System.exit(-1);
                }
  
                try{
                      client = server.accept();
                }
                catch(IOException e){
                      System.out.println("응답 실패: 포트 " + host.getPort() + "번");
                      System.exit(-1);
                }
  
                try{
                      in = client.getInputStream();
                      out = client.getOutputStream();
   
                      pw = new PrintWriter(new OutputStreamWriter(out));
                      br = new BufferedReader(new InputStreamReader(in));
   
                }catch(IOException e){
                      System.out.println("읽기 실패(Read Failed)");
                      System.exit(-1);
                }

                try {
   
                     while( (recvStr = br.readLine() ) != null ){
     
                            System.out.print( "싱글" );
                            pw.println(recvStr);
                            pw.flush();
                    }
   
                   client.close();
                   server.close();
   
              } catch (IOException e) {
                     System.out.println("읽기 실패(Read Failed");
                     System.exit(-1);
              }
  
         }

        // 멀티(Multi-Threading)
        public void listenMutliSocket(){
   
              ClientWorker w = null;

              try{
                  server = new ServerSocket( host.getPort() ); 
              }
              catch(IOException e){
                  System.out.println("포트 " + host.getPort() + "번은 응답하지 않습니다.");
                  System.exit(-1);
              }
  
              try{
                   while(true){
   
                        w = new ClientWorker( server.accept(), clientID++ );
  
                        Thread t = new Thread(w);
                        t.start();
                   }
   
             }
             catch(IOException e){
                  System.out.println("응답 실패: 포트 " + host.getPort() + "번");
                  System.exit(-1);
             }
  
       }
  
       protected void finalize(){
  
             try{
                  server.close();
             }catch(IOException e){
                  System.out.println("소켓을 종료할 수 없습니다.");
                  System.exit(-1);
             }
  
       }

 }

 ServerSide.java

 package Network;

 public class Example{
 
        public static void main(String[] args){
  
             ServerSide server = new ServerSide();
             Host host = new Host(10001);
             server.setHost(host);
             server.listenMutliSocket();
        }
 }

 Example.java

[예제 - 첨부(Attachment)]
Network.zip


4. IPv4와 IPv6

 

네트워크와 관련된 주제로 IPv4와 IPv6에 대해서 소개한다.

 4-1. IPv4 

 IPv4는 인터넷 프로토콜의 4번째 판이며, 전 세계적으로 사용된 첫 번째 인터넷 프로토콜이다.

 과거에 인터넷에서 사용되는 유일한 프로토콜이였으나 오늘날에는 IPv6이 대중화되었다. IETF RFC 791(1981년 9월)에 기술되어 있다.

 IPv4는 패킷 교환 네트워크 상에서 데이터를 교환하기 위한 프로토콜이다. 데이터가 정확하게 전달될 것을 보장하지 않고,
 중복된 패킷을 전달하거나  패킷의 순서를 잘못 전달할 가능성도 있다. 데이터의 정확하고 순차적인 전달은 그보다 상위 프로토콜인
 TCP에서(그리고 UDP에서도 일부) 보장한다.

 IPv4의 주소체계는 총 12자리이며 네 부분으로 나뉜다. 각 부분은 0~255까지 3자리의 수로 표현된다. IPv4 주소는 32비트로 구성되어 있으며,
 현재 인터넷 사용자의 증가로 인해 주소공간의 고갈에 대한 우려가 높아지고 있다. 이에 따라 대안으로 128비트 주소체계를 갖는 IPv6가 등장하였다.

 중국의 경우 주소공간 고갈을 우려하여 일부에서 독자적으로 IPv9(십진제 인터넷 주소체계)과 숫자도메인(Digital Domain Name System, DDNS)이
 결합된 개념인 IP 주소와 도메인 이름이 동일한 네트워크 체제인 All-Digital-Domain-Address (ADDA)를 사용하기도 한다.

 2011년 2월 4일부터 모든 IPv4 주소가 소진되어 IPv4의 할당이 중지되었다.

 

CLASS 구성 범위
A 클래스 xxx.xxx.xxx.xxx 1.0.0.1 ~ 126.255.255.254 61.211.123.22
B 클래스 xxx.xxx.xxx.xxx 128.0.0.1 ~ 191.255.255.254 181.123.211.33
C 클래스 xxx.xxx.xxx.xxx 192.0.0.1 ~ 223.255.255.254 221.23.222.222
D 클래스 224.0.0.0 ~ 239.255.255.255
E 클래스 240.0.0.0 ~ 254.255.255.254

 그림 4-1-1. 쿼드 점으로 구분 된 IPv4 주소 표현을 이진 값으로 분해.

 이건 조금 해결할 수는 있을 듯하다. 다만 이것도 계산이 더럽다고 볼 수 있다.
 아래는 172에 대해서 해결한 것이다.

10 |  10  |  110  | 0  

  10 = | Flag |
  128번 = 000000
  129번 = 000001

   .........
  172번 = 101100

 

 4-2. IPv6

 인터넷 프로토콜 버전 6 (IPv6)은 네트워크상의 컴퓨터에 대한 식별 및 위치 시스템을 제공하고 인터넷을 통해 트래픽을 라우팅하는
 통신 프로토콜 인 IP (인터넷 프로토콜)의 최신 버전이다.

 
IPv6는 오랫동안 예상되었던 IPv4 주소 고갈 문제를 해결하기 위해 IETF (Internet Engineering Task Force)에서 개발되었다.
 
IPv6은 IPv4를 대체하기 위한 것이다. IPv6는 1998 년 12 월에 초안 표준이되었고, 2017 년 7 월 14 일에 인터넷 표준이 되었다.

 인터넷상의 모든 장치에는 식별 및 위치 정의를 위해 고유 한 IP 주소가 할당된다.
 
1990 년대의 상용화 이후 인터넷의 급속한 성장으로 인해 IPv4 주소 공간보다 장치를 연결하는 데 훨씬 많은 주소가 필요할 것이다.

 
1998 년까지 IETF (Internet Engineering Task Force)는 후임 프로토콜을 공식화했다.
 
IPv6은 128 비트 주소를 사용하며 이론적으로 2128 개 또는 약 3.4 × 1038 개 주소를 허용한다.

 
여러 개의 범위가 특수 용도로 예약되었거나 완전히 사용되지 않도록 실제 숫자가 약간 더 작다.
 
가능한 IPv6 주소의 총 수는 32 비트 주소를 사용하고 약 43 억 개의 주소를 제공하는 IPv4의 7.9 × 1024 배이다.
 
 
이 두 프로토콜은 상호 운용성을 위해 설계되지 않았기 때문에 IPv6 로의 전환이 복잡해졌다.
 
그러나 IPv4와 IPv6 호스트 간의 통신을 허용하기 위해 몇 가지 IPv6 전환 메커니즘이 고안되었다.

 IPv6은 더 큰 주소 지정 공간 외에도 다른 기술적 이점을 제공한다.
 
특히 인터넷을 통한 경로 집계를 용이하게 하는 계층적 주소 할당 방법을 허용하므로 라우팅 테이블의 확장을 제한한다.
 
멀티 캐스트 주소 사용은 확장되고 단순화되며 서비스 제공을위한 추가 최적화를 제공한다.

 장치의 이동성, 보안 및 구성 측면이 프로토콜 설계시 고려되었다.

 IPv6 주소는 그룹이 콜론으로 구분 된 네 개의 16 진수 8 개의 그룹으로 표시된다.
 (예: 2001:0db8:0000:0042:0000:8a2e:0370:7334).이 전체 표기법을 줄이는 방법이 있다.

 

그림 4-2-1. IPv6 주소 표현을 이진 형식으로 분해

 IPv6를 사람 손으로 푼다고 하면, 매우 많은 노가다가 필요하다.
 각 옥탯에 있는 16진수를 일일이 하나 하나씩 해결해야 한다.

4-3. IPv6의 패킷헤더

 



그림 4-3-1. IPv6의 패킷해더

 IPv6의 패킷해더이다. IPv6로 데이터를 송수신하면 이런 형태로 전송이 되는 것을 관찰할 수 있다.
 리눅스에 패킷 분석 툴킷이 있는데 한번 패킷해더를 추적해봐도 좋을 것이다. 

 이 패킷해더를 관찰하는 대표적인 툴킷(Tool-kits)으로 WireShark가 있다.

  
 그림 4-3-2. WireShark 시연 예

 [IPv4 to IPv6에 관한 이야기]

 IPv6를 실제로 사용하려면 많은 조건이 필요하다.
 IPv4에서 IPv6로 변환해줄 수 있는 변환장치 등이 필요하다.
 터널링도 하나의 기법이 될 수 있다.


5. 참고자료(Reference)

 

1. IPv4, Wikipedia, https://ko.wikipedia.org/wiki/IPv4, Accessed by 2018-07-27
2. IPv6, Wikipeida, https://en.wikipedia.org/wiki/IPv6, Accessed by 2018-07-27
3. IPv6 OSPFv3 ESP Packets and Decrypting with Wireshark, https://packetpushers.net/ipv6-ospfv3-esp-packets-and-decrypting-with-wireshark/, Accessed by 2018-07-27

반응형

+ Recent posts