[Java] Java와 C#간의 소켓 통신


Development note/Java  2019. 7. 6. 09:00

안녕하세요. 명월입니다.


이 글은 Java와 C#간의 소켓 통신을 구현에 대한 설명입니다.


사실 서로 다른 언어 간의 소켓 통신에 대해서 네트워크 통신을 잘 이해하고 있다면 언어라는 차이라는 건 의미가 없습니다.

단지 소켓의 접속과 송수신, 접속 해제의 절차와 통신 간의 규약(프로토콜)에 대해 이해만 하고 있으면 언어와 관계없이 통신하는 데 문제가 없습니다.

(웹 브라우져를 실행 함에 있어서 웹 서버의 개발 언어가 무엇이냐? 이렇게 따지는 것도 이상하지 않습니까?)


저는 서버 측을 자바로 만들었고 클라이언트 측은 C#으로 구현을 했습니다. 사실 실무에서도 서버 측은 tomcat을 이용해서 서버를 구축하는 경우가 많고 클라인언트는 Window 환경이 아무래도 많으니 C#이나 C++(MFC)로 만드는 경우가 많습니다.

개인적인 생각으로 혹시 게임을 만든다고 할 때, 클라이언트 부분이야 프레임 체크나 렉에 대한 이슈가 강하니 C++, MFC로 만드는게 아무래도 대세일 수밖에 없겠네요. 예를 들면 FPS게임 중에 갑자기 GC가 작동해서 렉이 발생하면 볼만 하겠네요. ㅎㅎ

요즘 대세가 게임 엔진 Unity라는데 Unity의 지원 스크립트가 C#이지 않나? 제가 공부할 때는 SDL 라이브리였던가 사용했던 걸로 기억하는데.. C#도 이젠 게임 개발에 무난한가 보네요...솔직히 잘 모릅니다..


그러나 서버의 경우는 제 생각에는 굳이 옛날처럼 C++로 만들 필요는 없을 꺼 같네요. tomcat이나 IIS도 예전과 비교해서 많은 발전을 했고 오히려 멀티 스레드 관리 쪽은 웹 서버에 맡기는 것도 나쁘지는 않다고 생각합니다.

나중에 기회 있으면 웹 서버에 소켓 서버 구축하는 방법에 대해 글을 써봐야 겠네요.


그러다 보니 서로 다른 언어 간의 소켓 통신 개발도 예전보다는 많을 거로 생각합니다. 앞서 이야기했지만 소켓 통신 간에서 언어의 종류는 솔직히 의미가 없습니다.


그럼 자바로 소켓 서버를 만들겠습니다.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Example {
  // socket으로 부터 수신을 받으면 byte[] 형식으로 내보낸다.
  // 통신 규약으로는 데이터의 사이즈를 먼저 보내고, 그 다음에 데이터를 보내는 것으로 한다.
  // 데이터의 사이즈를 보낼 때는 리틀 엔디언 타입으로 받는다.
  private static byte[] getRecieve(InputStream stream) throws IOException {
    byte[] buffer = new byte[4];
    // 사데이터 사이즈를 받는다.
    stream.read(buffer, 0, 4);
    ByteBuffer data = ByteBuffer.wrap(buffer);
    // 리틀 앤디언 타입으로 설정
    data.order(ByteOrder.LITTLE_ENDIAN);
    // int형으로 변환
    int size = data.getInt();
    buffer = new byte[size];
    // 데이터를 받는다.
    stream.read(buffer, 0, size);
    return buffer;
  }

  public static void main(String... args) {
    // 서버용 싱글 쓰레드 풀을 생성
    ExecutorService service = Executors.newSingleThreadExecutor();
    // 수신용 쓰레드 풀을 생성
    ExecutorService clientService = Executors.newFixedThreadPool(10);
    service.submit(() -> {
      try (ServerSocket server = new ServerSocket()) {
        // 9999포트로 대기한다.
        InetSocketAddress ipep = new InetSocketAddress(9999);
        server.bind(ipep);
        System.out.println("서버 대기");

        while (true) {
          // 클라이언트가 접속 되었다.
          Socket client = server.accept();
          System.out.println("클라이언트 접속");
          clientService.submit(() -> {
            // stream을 받는다.
            try (OutputStream sender = client.getOutputStream(); InputStream reciever = client.getInputStream();) {
              // 위 getRiceve 함수로 먼저 파일 이름을 받는다.
              String filename = new String(getRecieve(reciever), Charset.forName("UTF-8"));
              System.out.println("저장할 파일 이름 - " + filename);
              // 데이터를 받는다.
              byte[] filedata = getRecieve(reciever);

              // 데이터를 저장한다.
              try (FileOutputStream stream = new FileOutputStream("d:\\work\\" + filename)) {
                stream.write(filedata, 0, filedata.length);
              }
              System.out.println("파일 저장 완료");

              // 완료시에는 byte{1}을 송신한다.
              sender.write(new byte[] { 1 }, 0, 1);
              System.out.println("완료 코드 보내기");
            } catch (Throwable e) {
              e.printStackTrace();
            } finally {
              // 수신용 socket을 닫는다.
              try {
                client.close();
              } catch (Throwable e) {
                e.printStackTrace();
              }
            }
          });
        }
      } catch (Throwable e) {
        e.printStackTrace();
      }
    });
  }
}

이번에는 클라이언트에서 C#으로 작성했습니다.

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace Example
{
  class Program
  {
    static void Main(string[] args)
    {
      // 파일을 읽어온다.
      FileInfo file = new FileInfo("d:\\work\\test.zip");
      using (FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read))
      {
        byte[] data = new byte[file.Length];
        // 파일의 바이너리를 byte[] 형식으로 변환한다.
        stream.Read(data, 0, data.Length);

        // 소켓을 연다.
        using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP))
        {
          // 로컬의 9999포트로 접속한다.
          socket.Connect(IPAddress.Parse("127.0.0.1"), 9999);
          // 데이터 송신간의 프로토콜은 먼저 데이터 사이즈를 보내고 데이터를 보낸다.
          Action<byte[]> Send = (b) =>
          {
            socket.Send(BitConverter.GetBytes(b.Length), 4, SocketFlags.None);
            socket.Send(b, b.Length, SocketFlags.None);
          };
          
          // 파일명은 Download.zip이라고 보낸다.
          Send(Encoding.UTF8.GetBytes("Download.zip"));
          // 읽어온 바이너리 데이터를 송신한다.
          Send(data);

          byte[] ret = new byte[1];
          // 서버로 부터 1이라는 데이터를 받으면 송신 완료이다.
          socket.Receive(ret, 1, SocketFlags.None);
          if(ret[0] == 1)
          {
            Console.WriteLine("Completed");
          }
        }
      }

      Console.WriteLine("Press any key...");
      Console.ReadKey();
    }
  }
}

그럼 java를 먼저 실행하고 클라이언트인 C#을 실행하곘습니다.

먼저 zip파일을 준비하고 안에는 txt타입의 데이터를 넣었습니다.

클라이언트에서 completed 결과가 나왔습니다.

결과를 보니 제대로 송수신이 된 것같습니다.

여기까지 소켓에 대해 참고한 글입니다.


링크 - [Java강좌 - 23] 소켓 통신 (Socket)

링크 - [C# 강좌 - 32] 소켓 통신 - 1


여기까지 Java와 C#간의 소켓 통신을 구현에 대한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.