Java C Socket 통신, 파일전송 / C : Server / Java : Client
Java 와 C 사이의 Socket 통신에 대한 정리이다.
Socket 구성은 다음과 같다. C : Server Java : Client
|
본 포스팅은 Java (Client) 에서 C (Server)로 데이터를 보내고 받아오는 간단한 로직을 설명한다.
C ( Server ) 에 대한 소스는 없고 Java (Client) 소스 기준으로만 설명한다.
또한 C에서 받고 전송해주는 데이터는 구조체 Char 타입을 기준으로만 설명한다.
순서는 다음과 같다.
1. java와 C의 socket 통신 개념
2. C ( Server ) 에서 통신할 데이터 구조
3. java ( Client ) Socket 생성
4. java ( Client ) 에서 C ( Server ) 로 데이터 전송
5. C ( Server ) 에서 보낸 데이터를 java ( Client ) 에서 수신
6. 전체 소스
|
1. java와 C의 socket 통신 개념
java 와 C 의 데이터 개념이 다르다.
C 는 구조체를 사용하는데 java 에는 구조체가 없다.
구조체란 서로 다른 형태의 변수를 하나로 묶은 것을 말한다.
1
2
3
4
5 |
typedef struct
{
int aaaa;
char bbbb[9];
}; |
cs |
구조체는 위와같은 모양이다.
java에서 VO와 같다고 생각하면 이해가 쉽다.
반대로 생각하면 Java에서 사용하는 Object 또한 C 에서 해석할수 없기 때문에 상호 통신간에는 Byte 통신을 해야한다.
또한 데이터 전송간에 Endian 개념을 이해하면 도움이 된다.
다른 언어로 데이터 전송을 할 땐 플랫폼에 따라 ByteOrder 를 Big Endian을 사용하는지 Little Endian 을 사용하는지 확인해야 한다.
문자열, 숫자문자열 등을 전송할 땐 신경쓰지 않아도 된다.
정수타입의 데이터를 주고 받을 때 필요하다.
본 포스팅은 정수타입의 데이터 전송은 없으니 간단하게나마 4. java ( Client ) 에서 C ( Server ) 로 데이터 전송 에서 ByteOrder 를 설정하는 방법을 소개하고 지나간다.
2. C ( Server ) 에서 통신할 데이터 구조
서론은 이쯤하고 실제로 주고받을 데이터 형식이 어떤 모양인지 보자.
C ( server ) 에서 받고 결과를 보내주는 데이터의 모양은 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13 |
/* 1. 요청 (클라이언트 -> 서버) */
typedef struct
{
char sid[5];
char empid[9];
}REQ_HEADER;
/* 2. 결과 (서버 -> 클라이언트) */
typedef struct
{
char sid[5];
char status[10];
}REC_HEADER; |
cs |
데이터의 타입은 모두 char 타입이며 눈여겨 볼 것은 데이터 크기이다.
sid 변수의 length는 5, empid 변수의 length 는 9 등 각각 변수들의 length 를 기억하고 지나가자.
3. java ( Client ) Socket 생성
각 파트에 맞는 소스 일부만 가져왔으며 가독성과 설명을 위해 전역변수가 아닌 지역변수로 변경함
(전체 소스에서는 전역변수로 되어 있음)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
try {
InetSocketAddress isa = new InetSocketAddress("100.1.1.1", 35153);
Socket socket = new Socket();
socket.setReuseAddress(true);
socket.connect(isa);
socket.setSoTimeout(10000); // 10초
socket.setSoLinger(true, 0);
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
stop();
} |
cs |
2# ~ 5# : socket의 setReuseAddress 옵션을 사용하기 위한 연결방법
setReuseAddress 옵션 :
소켓은 종료될 때 네트워크를 통해 해당 포트로 전송 중인 나머지 패킷이 있는 경우 일정 시간동안 기다린다.
즉 소켓을 닫을 때 열린 연결이 있는 경우 포트가 즉시 해제되지 않을 수 있다.
SO_REUSEADDR 옵션이 켜진 경우(default값은 꺼짐)에 이전 소켓으로 전송될 데이터가 남아 있는 경우 또 다른 소켓이 해당 포트를 바인드(bind)할 수 있다.
SO_REUSEADDR 옵션을 사용하기 위해서는 새로운 소켓이 포트에 바인드(bind) 되기 전에 SO_REUSEADDR 옵션을 사용한다고 선언해야 한다.
즉 비어있는 소켓을 생성한 후 SO_REUSEADDR 옵션을 켜고 InetSocketAddress 와 connect() 메소드를 호출해 소켓을 연결해야 한다.
setReuseAddress 옵션을 사용하지 않을 경우 일반적인 소켓 연결방식을 사용하면 된다.
[ Socket socket = new Socket("100.1.1.1", 35153); ]
6# : socket의 timeout 설정을 한다. (10000 : 10초)
7# : setSoLinger 옵션 설정
setSoLinger 옵션 :
소켓이 닫힐 때, 전송되지 않은 데이터를 어떻게 처리하는지에 대한 옵션이다.
기본적으로 close() 메소드는 호출 즉시 반환되지만 시스템은 내부적으로 아직 전송되지 않은 데이터를 계속해서 전송한다.
링거(linger) 시간을 0으로 설정될 경우, 소켓이 닫힐 때 아직 전송되지 않은 패킷은 버려진다.
SO_LINGER 옵션이 켜져 있고 링거 타임이 정수값인 경우, close() 메소드는 지정된 시간 동안 데이터를 보내고 응답을 받기 위해 블록(block)된다.
그리고 지정된 시간이 초과될 경우, 소켓은 닫히고 아직 남아 있는 데이터는 보내지 않으며 응답을 기다리지 않는다.
링거(linger) 시간은 음수가 올 수 없으며 최대 링거타임은 65535초이다.
9#, 10# : 데이터를 송수신할 Stream 객체이다.
기본적인 OutputStream 과 InputStream 을 사용하며 데이터가 많다면 BufferedOutputStream 과 BufferedInputStream을 사용할 수 있다.
Byte 통신이기 때문에 ObjectInputStream, ObjectOutputStream 등은 사용하지 않는다.
4. java ( Client ) 에서 C ( Server ) 로 데이터 전송
각 파트에 맞는 소스 일부만 가져왔으며 가독성과 설명을 위해 전역변수가 아닌 지역변수로 변경함
(전체 소스에서는 전역변수로 되어 있음)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 |
try {
OutputStream os = socket.getOutputStream();
String sid = "0123";
String sabun = "123456";
//ByteBuffer
ByteBuffer sendByteBuffer = null;
sendByteBuffer = ByteBuffer.allocate(14);
sendByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
sendByteBuffer.put(sid.getBytes());
sendByteBuffer.put(new byte[5 - sid.getBytes().length]);
sendByteBuffer.put(sabun.getBytes());
sendByteBuffer.put(new byte[9 - sabun.getBytes().length]);
os.write(sendByteBuffer.array());
os.flush();
} catch (IOException e) {
e.printStackTrace();
} |
cs |
2# : 데이터를 전송하는데 사용할 OutputStream
4#, 5# : 전송할 데이터가 있는 변수
8# : 바이트 데이터를 담을 그릇이 될 ByteBuffer 객체
ByteBuffer 객체만 설명하기에도 엄청난 양이다.
단순하게 byteOrder 를 설정할 수 있는 byte를 담을 그릇이구나 정도만 알고 지나가면 될 것 같다.
byteOrder 은 big_endian 과 little_endian 의 설정을 말한다.
10# : allocate() 클래스 메소드를 사용하여 초기크기를 설정 한 후 ByteBuffer 객체를 생성한다.
초기 크기가 14인 이유는 우리가 전송해야 할 데이터의 총 크기가 14이기 때문이다.
2. C ( Server ) 에서 통신할 데이터 구조 참조
11# : 정수타입의 데이터를 사용할 경우 서버와 일치하는 Endian 설정을 해주면 된다.
문자열, 숫자문자열 등은 신경쓰지 않아도 된다.
13# : 첫번재 데이터인 sid 를 바이트로 변환하여 ByteBuffer 에 담는다.
14# : 입력한 sid 의 바이트 크기가 5보다 작으면 작은만큼 byte를 생성해서 ByteBuffer에 담는다.
16# : 두번째 데이터인 sabun 을 바이트로 변환하여 ByteBuffer 에 담는다.
17# : 입력한 sabun 의 바이트 크기가 9보다 작으면 작은만큼 byte를 생성해서 ByteBuffer에 담는다.
데이터를 전송할 때 왜 14#, 17# 과 같은 작업이 필요한가 ?
별도의 약속이나 룰이 있다면 그 룰을 따르는게 우선이다.
그게 없다면 아래 내용을 참고하면 도움이 될 수 있다.
2. C ( Server ) 에서 통신할 데이터 구조 에서 sid 변수의 데이터 크기가 얼마인가 ?
5 이다.
그런데 Java에서 보낼 sid 데이터 크기는 얼마인가 ?
4 이다.
보낼 값의 크기가 1 작다.
그래서 1만큼의 빈 데이터를 만들어서 담아주는 작업이다.
우리는 C ( Server ) 로 데이터를 보낼 때 이 크기를 맞춰 주어야 한다.
예를 들어 데이터의 크기가 100일때 전송할 값의 크기가 10이면 90을 더 만들어 보내야 한다.
C 에서 받을 데이터 크기가 100이면 Java 에서도 100의 크기인 데이터를 전송해야 한다.
우리는 sid[5], sabun[9] 총 14 크기의 바이트를 전송해야 한다.
패킷을 한번 분석해 보자.
14#, 17# 의 작업이 없을 경우 전달될 16진수 패킷(1번째 줄)과 실제 전달될 데이터(2번째 줄)이다.
C ( Server ) 에서는 데이터 크기만큼 데이터를 분리하는데 위와같이 전달된다면 잘못된 범위의 데이터를 분리하게 된다.
sid[5] 변수의 데이터를 0123으로 분리해야 하지만 위와 같이 전송하게되면 01231이 분리가 된다는 이야기다.
이렇게 빈 값으로 데이터의 크기를 맞춰 주어야 한다.
5. C ( Server ) 에서 보낸 데이터를 java ( Client ) 에서 수신
각 파트에 맞는 소스 일부만 가져왔으며 가독성과 설명을 위해 전역변수가 아닌 지역변수로 변경함
(전체 소스에서는 전역변수로 되어 있음)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
BufferedInputStream bis = bis = new BufferedInputStream(socket.getInputStream());
String sid = "";
String status = "";
byte[] buff = new byte[1024];
int read = 0;
while (true) {
if (this.socket == null) {
break;
}
read = bis.read(buff, 0, 1024);
if (read < 0) {
break;
}
// sid
byte[] tempArr = new byte[5];
System.arraycopy(buff, 0, tempArr, 0, 5);
sid = new String(tempArr);
// status
tempArr = new byte[10];
System.arraycopy(buff, 5, tempArr, 0, 10);
status = new String(tempArr);
} |
cs |
1# : BufferedInputStream 을 socket에서 꺼냄
3#, 4# : 수신한 데이터를 담을 변수
6# : 데이터를 읽어들일 byte 배열 변수 buff
8# : 읽은 데이터 수
9# : 무한루프
10# : socket 이 없으면 루프 종료
14# : BufferedInputStream 을 이용하여 buff 변수로 데이터를 읽어들임
16# : 읽은 데이터 수가 0보다 작으면 루프 종료
21# : 읽을 데이터많큼 임시 바이트배열 생성 ( sid[5] )
22# : buff 변수에 있는 데이터를 데이터 크기만큼 임시 바이트배열로 복사함 ( 0에서 5만큼 복사 )
23# : 임시 바이트 배열을 String 으로 변환하여 변수에 대입
26# : 읽을 데이터많큼 임시 바이트배열 생성 ( status[10] )
27# : buff 변수에 있는 데이터를 데이터 크기만큼 임시 바이트배열로 복사함 ( 5에서 10만큼 복사 )
28# : 임시 바이트 배열을 String 으로 변환하여 변수에 대입
데이터를 수신할 때 주의해야 할 점은 전송되는 데이터 역시 데이터 크기만큼 온다는 것이다.
그렇기 때문에 데이터 크기만큼 추출하여 사용해야 한다.
데이터를 읽어들이는 작업까지 모두 끝났다.
작업이 끝나면 socket을 비롯하여 사용했던 Stream 들을 close 해주어야 한다.
전체 소스에는 있으니 참고하면 된다.
또한 전체 소스에는 수신한 값을 VO를 사용하여 담았다.
6. 전체 소스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 |
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Java Socket Client
*/
public class JavaSocketClient {
public SignClientReceiver signClientReceiver;
public OutputStream os;
public Socket socket = null;
public static JavaSocketClient client;
/**
* Launch the application.
*/
public static void main(String[] args) {
client = new JavaSocketClient();
}
public JavaSocketClient() {
init();
start();
setSignClientReceiver();
try {
// 스레드 대기
signClientReceiver.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 스레드 종료 후 소켓 종료
client.stop();
}
// 리시버 클라이언트 스레드 생성
public void setSignClientReceiver() {
signClientReceiver = new SignClientReceiver(socket);
signClientReceiver.setDaemon(true);
signClientReceiver.start();
}
public void init() {
if (socket == null) {
try {
InetSocketAddress isa = new InetSocketAddress("100.1.1.1", 35153);
socket = new Socket();
// 이전에 연결된 소켓이 해당 포트를 점유하고 있어도 바인드하기 위한 설정, 때문에 빈 소켓을 먼저 만들고 connect 함
socket.setReuseAddress(true);
socket.connect(isa);
socket.setSoTimeout(10000); // 10초
// 링거타임 설정 0, 소켓을 닫을 때 아직 전송되지 않은 패킷이 있다면 이를 버리고 소켓을 즉시 닫음
socket.setSoLinger(true, 0);
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
stop();
}
}
}
public void start() {
try {
String sid = "0123";
String sabun = "123456";
//ByteBuffer
ByteBuffer sendByteBuffer = null;
sendByteBuffer = ByteBuffer.allocate(14);
sendByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
sendByteBuffer.put(sid.getBytes());
sendByteBuffer.put(new byte[5 - sid.getBytes().length]);
sendByteBuffer.put(sabun.getBytes());
sendByteBuffer.put(new byte[9 - sabun.getBytes().length]);
os.write(sendByteBuffer.array());
os.flush();
} catch (IOException e) {
e.printStackTrace();
stop();
}
}
public void stop() {
try {
socket.close();
os.close();
} catch (IOException e) {
}
}
}
/**
* SignClientReceiver
*/
class SignClientReceiver extends Thread {
private Socket socket;
public SignClientReceiver(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedInputStream bis = null;
try {
RecHeaderVO recHeaderVO = new RecHeaderVO();
bis = new BufferedInputStream(socket.getInputStream());
byte[] buff = new byte[1024];
int read = 0;
while (true) {
if (this.socket == null) {
break;
}
read = bis.read(buff, 0, 1024);
if (read < 0) {
break;
}
// sid
byte[] tempArr = new byte[5];
System.arraycopy(buff, 0, tempArr, 0, 5);
recHeaderVO.setSid(new String(tempArr));
// status
tempArr = new byte[10];
System.arraycopy(buff, 5, tempArr, 0, 10);
recHeaderVO.setStatus(new String(tempArr));
}
System.out.println(recHeaderVO.toString());
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* 정보 수신 VO
*/
class RecHeaderVO {
/** char sid[5] */
public String sid;
/** char status[10] */
public String status;
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
} |
cs |