안녕하세요. 명월입니다.
이 글은 Python과 Java와의 소켓 통신입니다.
이전에 Python과 C#과의 소켓 통신에 대해 설명했었습니다.
링크 - [Python] Python과 C#에서의 소켓 통신
그럼 뭐 Java와도 Socket 통신에 대해 작성해야하지 않나 싶어서 작성합니다.
저의 경우는 Java는 거의 100의 99는 톰켓을 이용한 웹 프로젝트입니다. 그런데 톰켓으로 작성하니 웹 프로젝트이기는 합니다만, 내부에서 Thread하나 만들어서 소켓 서버를 두고 안드로이드나 IOS의 소켓 서버던가 각종 어플케이션 서버(게임 등등)를 개발하기도 합니다.
그니깐 내부 주요 프로그램 흐름은 소켓 서버입니다만 그 소켓 서버를 제어하기 위한 프로그램은 웹 서비스로 개발합니다.
그러므로 Java에서도 소켓을 사용하는 경우가 많이 있으니 그에 대한 모의 서버 혹은 클라이언트가 필요하는 경우가 있습니다.
python에서의 소켓 서버는 똑같습니다.
참조 - [Python] Socket 통신
# 소켓을 사용하기 위해서는 socket을 import해야 한다.
import socket, threading;
# binder함수는 서버에서 accept가 되면 생성되는 socket 인스턴스를 통해 client로 부터 데이터를 받으면 echo형태로 재송신하는 메소드이다.
def binder(client_socket, addr):
# 커넥션이 되면 접속 주소가 나온다.
print('Connected by', addr);
try:
# 접속 상태에서는 클라이언트로 부터 받을 데이터를 무한 대기한다.
# 만약 접속이 끊기게 된다면 except가 발생해서 접속이 끊기게 된다.
while True:
# socket의 recv함수는 연결된 소켓으로부터 데이터를 받을 대기하는 함수입니다. 최초 4바이트를 대기합니다.
data = client_socket.recv(4);
# 최초 4바이트는 전송할 데이터의 크기이다. 그 크기는 little big 엔디언으로 byte에서 int형식으로 변환한다.
# C#의 BitConverter는 big엔디언으로 처리된다.
length = int.from_bytes(data, "little");
# 다시 데이터를 수신한다.
data = client_socket.recv(length);
# 수신된 데이터를 str형식으로 decode한다.
msg = data.decode();
# 수신된 메시지를 콘솔에 출력한다.
print('Received from', addr, msg);
# 수신된 메시지 앞에 「echo:」 라는 메시지를 붙힌다.
msg = "echo : " + msg;
# 바이너리(byte)형식으로 변환한다.
data = msg.encode();
# 바이너리의 데이터 사이즈를 구한다.
length = len(data);
# 데이터 사이즈를 little 엔디언 형식으로 byte로 변환한 다음 전송한다.
client_socket.sendall(length.to_bytes(4, byteorder='little'));
# 데이터를 클라이언트로 전송한다.
client_socket.sendall(data);
except:
# 접속이 끊기면 except가 발생한다.
print("except : " , addr);
finally:
# 접속이 끊기면 socket 리소스를 닫는다.
client_socket.close();
# 소켓을 만든다.
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
# 소켓 레벨과 데이터 형태를 설정한다.
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
# 서버는 복수 ip를 사용하는 pc의 경우는 ip를 지정하고 그렇지 않으면 None이 아닌 ''로 설정한다.
# 포트는 pc내에서 비어있는 포트를 사용한다. cmd에서 netstat -an | find "LISTEN"으로 확인할 수 있다.
server_socket.bind(('', 9999));
# server 설정이 완료되면 listen를 시작한다.
server_socket.listen();
try:
# 서버는 여러 클라이언트를 상대하기 때문에 무한 루프를 사용한다.
while True:
# client로 접속이 발생하면 accept가 발생한다.
# 그럼 client 소켓과 addr(주소)를 튜플로 받는다.
client_socket, addr = server_socket.accept();
th = threading.Thread(target=binder, args = (client_socket,addr));
# 쓰레드를 이용해서 client 접속 대기를 만들고 다시 accept로 넘어가서 다른 client를 대기한다.
th.start();
except:
print("server");
finally:
# 에러가 발생하면 서버 소켓을 닫는다.
server_socket.close();
데이터 전송 바이트 배열은 little 엔디언을 사용합니다.
먼저 클라이언트를 자바로 작성하겠습니다. java의 소켓 통신에 대해서는 이전에 설명한 적이 있는데 참고하기 바랍니다.
링크 - [Java강좌 - 23] 소켓 통신 (Socket)
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Client {
public static void main(String... args) {
// 소켓을 선언한다.
try (Socket client = new Socket()) {
// 소켓에 접속하기 위한 접속 정보를 선언한다.
InetSocketAddress ipep = new InetSocketAddress("127.0.0.1", 9999);
// 소켓 접속!
client.connect(ipep);
// 소켓이 접속이 완료되면 inputstream과 outputstream을 받는다.
try (OutputStream sender = client.getOutputStream(); InputStream receiver = client.getInputStream();) {
// 메시지는 for 문을 통해 10번 메시지를 전송한다.
for (int i = 0; i < 10; i++) {
// 전송할 메시지를 작성한다.
String msg = "java test message - " + i;
// string을 byte배열 형식으로 변환한다.
byte[] data = msg.getBytes();
// ByteBuffer를 통해 데이터 길이를 byte형식으로 변환한다.
ByteBuffer b = ByteBuffer.allocate(4);
// byte포멧은 little 엔디언이다.
b.order(ByteOrder.LITTLE_ENDIAN);
b.putInt(data.length);
// 데이터 길이 전송
sender.write(b.array(), 0, 4);
// 데이터 전송
sender.write(data);
data = new byte[4];
// 데이터 길이를 받는다.
receiver.read(data, 0, 4);
// ByteBuffer를 통해 little 엔디언 형식으로 데이터 길이를 구한다.
ByteBuffer b = ByteBuffer.wrap(data);
b.order(ByteOrder.LITTLE_ENDIAN);
int length = b.getInt();
// 데이터를 받을 버퍼를 선언한다.
data = new byte[length];
// 데이터를 받는다.
receiver.read(data, 0, length);
// byte형식의 데이터를 string형식으로 변환한다.
msg = new String(data, "UTF-8");
// 콘솔에 출력한다.
System.out.println(msg);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
위 예제를 보면 client(java)에서 10개의 메시지를 보낼 때, 끝에 index 번호를 포함해서 보냈는데 순서대로 잘 전송이 되네요. server에서는 메시지를 잘 받아서 콘솔에 출력하고 「echo :」를 붙혀서 client로 다시 재 전송했습니다.
client에서는 「echo : 」 가 붙은 메시지를 받고 콘솔에 출력하였는데 제대로 표시되네요.
이번에는 반대로 Java를 소켓 서버로 두고 python을 클라이언트로 접속하겠습니다.
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.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
public static void main(String... args) {
// 클라이언트 소켓을 받을 threadpool를 선언한다. 쓰레드 풀안에는 최대 10개의 쓰레드를 가동시킬 수 있다.
ExecutorService clientService = Executors.newFixedThreadPool(10);
// serverSocket를 선언한다.
try (ServerSocket server = new ServerSocket()) {
// 포트는 9999로 오픈한다.
InetSocketAddress ipep = new InetSocketAddress(9999);
server.bind(ipep);
while (true) {
// 클라이언트가 접속할 때까지 대기한다.
Socket client = server.accept();
// 클라이언트가 접속이 되면 쓰레드 풀에 쓰레드를 하나 생성하고 inputstream과 outputstream을 받는다.
clientService.submit(() -> {
try (OutputStream sender = client.getOutputStream();
InputStream receiver = client.getInputStream();) {
// 서버 무한 대기를 한다.
while (true) {
byte[] data = new byte[4];
// 데이터 길이를 받는다.
receiver.read(data, 0, 4);
// ByteBuffer를 통해 little 엔디언 형식으로 데이터 길이를 구한다.
ByteBuffer b = ByteBuffer.wrap(data);
b.order(ByteOrder.LITTLE_ENDIAN);
int length = b.getInt();
// 데이터를 받을 버퍼를 선언한다.
data = new byte[length];
// 데이터를 받는다.
receiver.read(data, 0, length);
// byte형식의 데이터를 string형식으로 변환한다.
String msg = new String(data, "UTF-8");
// 콘솔에 출력한다.
System.out.println(msg);
// echo를 붙힌다.
msg = "Java server echo : " + msg;
// string을 byte배열 형식으로 변환한다.
data = msg.getBytes();
// ByteBuffer를 통해 데이터 길이를 byte형식으로 변환한다.
b = ByteBuffer.allocate(4);
// byte포멧은 little 엔디언이다.
b.order(ByteOrder.LITTLE_ENDIAN);
b.putInt(data.length);
// 데이터 길이 전송
sender.write(b.array(), 0, 4);
// 데이터 전송
sender.write(data);
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
try {
// 에러가 발생하면 접속을 종료한다.
client.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
});
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
위는 java에서 socketServer클래스를 이용해서 소켓 통신 서버를 생성하였습니다.
이번에는 python에서 클라이언트를 작성하여 접속합니다.
# 소켓을 사용하기 위해서는 socket을 import해야 한다.
import socket
# 로컬은 127.0.0.1의 ip로 접속한다.
HOST = '127.0.0.1'
# port는 위 서버에서 설정한 9999로 접속을 한다.
PORT = 9999
# 소켓을 만든다.
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# connect함수로 접속을 한다.
client_socket.connect((HOST, PORT))
# 10번의 루프로 send receive를 한다.
for i in range(1,10):
# 메시지를 보낸다.
msg = 'java hello message';
# 메시지를 바이너리(byte)형식으로 변환한다.
data = msg.encode();
# 메시지 길이를 구한다.
length = len(data);
# server로 little 엔디언 형식으로 데이터 길이를 전송한다.
client_socket.sendall(length.to_bytes(4, byteorder="little"));
# 데이터를 전송한다.
client_socket.sendall(data);
# server로 부터 전송받을 데이터 길이를 받는다.
data = client_socket.recv(4);
# 데이터 길이는 little 엔디언 형식으로 int를 변환한다.
length = int.from_bytes(data, "big");
# 데이터 길이를 받는다.
data = client_socket.recv(length);
# 데이터를 수신한다.
msg = data.decode();
# 데이터를 출력한다.
print('Received from : ', msg);
client_socket.close();
client에서 메시지를 만들어서 10번의 루프로 서버로 보냅니다. 그럼 서버에서는 메시지에 echo의 문자열을 붙여서 client로 메시지를 보냅니다. java server - python client도 문제없이 잘 실행됩니다.
Java서버에서 에러가 표시되는 이유는 client에서 강제로 종료를 하게 되면 exception이 발생하게 됩니다. 자바의 60번째 줄의 의해 에러 메시지가 표시되고 콘솔에 에러 메시지가 나오는 것입니다.
그리고 Python - C#의 소켓 통신과 차이라고 한다면 엔디언의 설정 차이입니다. C#은 big 엔디안이 표준이고 java는 little엔디안이 표준입니다.
여기까지 Python과 Java와의 소켓 통신에 관한 설명이었습니다.
궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.
'Development note > Python' 카테고리의 다른 글
[Python] 파일 압축, 해제(zipfile)하는 방법 (0) | 2020.01.27 |
---|---|
[Python] Apache cgi에서 python을 사용하는 방법 (0) | 2020.01.21 |
[Python] 웹 서버를 기동하는 방법(http.server) (0) | 2020.01.20 |
[Python] Websocket을 사용하는 방법 (2) | 2020.01.19 |
[Python] Python과 C#에서의 소켓 통신 (2) | 2020.01.15 |
[Python] IO - INI 다루기 (0) | 2019.12.27 |
[Python] IO - JSON 다루기 (0) | 2019.12.26 |
[Python] IO - XML 다루기 (0) | 2019.12.25 |