아름다운 영일만에 기적이 울리다 - 영일만항선[2019.12.18]


 우리나라 물류의 주요 수송방법이 육로가 대부분이라고 하지만 철도를 이용한 수송이 육로보다 효율적이기에 지금까지도 철도를 통한 물류 운송이 활발하게 이어지고 있습니다. 그럼에도 춘장대역으로 대표되는 서천화력선과 같이 더이상 사용되지 않고 폐선되는 철로가 있기도 합니다.

 지난 2019년 12월 18일 화물철도인 영일만항선이 정식으로 개통되었습니다. 이번 포스팅에서는 새로 개통된 영일만항역 인근의 풍경을 담아보았습니다.



 지도를 보았을 때 영일만은 포항시에 둘러싸인 모습을 하고 있습니다. 호랑이의 꼬리 부분에 해당하는 호미곶이 영일만을 감싸는 듯한 모습을 하고 있군요.




 영일만항선은 포항역에서부터 영일만항까지 연결되어 있습니다. 영일만항 바로 옆에는 용한1리 해수욕장이 위치해 있습니다.



포항역에 내리다가 본 내일로 포스터

2019년 겨울 내일로는 만 34살까지 이용할 수 있습니다!



포항역에서 차로 15분 거리에 있는 포항국제컨테이너터미널에 도착하였습니다.



터미널 근처는 작은 어선들이 있습니다.



오늘 영일만항선의 개통식을 알리는 현수막들이 걸려있습니다.



최근 입체화 과정에서 사라져가는 건널목이 새로 만들어져있습니다.



아무래도 화물 열차가 많이 다니지 않아서인지 굳이 입체화를 할 필요가 없었던 듯 합니다.



희안하게 이 건널목에는 삼색 신호등도 설치되어 있습니다.

횡단보도가 없는 것으로 보다 기차가 지나갈 때만 동작하는 신호등인듯 합니다.



인근에 건널목이 있다는 표시판



철로는 컨테이너 터미널 내부로 연결되어 있습니다.



영일항만 주변은 공터가 대부분이었습니다.



곧 열차가 지나가려는지 청색 표시등이 켜져있습니다.



내부에서 개통식 행사를 하는 과정에서 시운전이 있었는지 멀리서 기관차가 보입니다.

다만 철문이 막혀있어 당장 건널목을 건널 수 없는 상황입니다.


이번에는 영일항만 근처 해수욕장을 찾아가 보았습니다.





해수욕장에 도착하자마자 서핑을 즐기는 사람들을 보았습니다.

이 추운 겨울에도 서핑을 하고자 하는 열정 대단합니다!



영일항만에 가려져서 이렇게 훌륭한 붕경을 보여주는 해수욕장이 근처에 있었다는 것이 신기하네요.



역시 동해안 답게 푸르른 바닷물이 인상깊습니다.



근처에 주차된 차량들이 상당히 많아 보이는데 무슨 일로 이 곳에 찾아온걸까요?



해수욕장에서 부터 방파제를 따라 만들어진 길을 산책해봅니다.



계속 걷다보니 아까 그 많은 사람들이 어디에 갔는지 알게 되었습니다.



어부가 잡은 물고기들이 바닥에서 싱싱하게 파닥거리고 있었습니다.



영일만 주변에 만들어진 방파제를 따라 사람들이 낚시를 즐기고 있었습니다!



등대 주변을 가보니 낚시를 즐기시는 분들이 많이 보입니다.



동해답게 물이 상당히 맑았습니다.

사진으로 다시 보니 실물보다 잘 안나오네요.



추운 날씨에 월척 한 마리 꼭 낚으시길!



다시 육지로 돌아가는길

생각보다 상당히 긴 길이었습니다.



저도 언젠가는 날씨 좋은 날에 바다낚시를 한 번 즐겨보고 싶네요!



다시 해수욕장에 돌아오니 서핑을 즐기시던 분들이 바다 바깥으로 나와계십니다.

가만히 서있기만 해도 추워보이는데 말이지요.



해수욕장과 영일만항을 함께 바라본 모습.



길 옆에 바닷가가 확 트인 모습 저는 매우 좋아합니다.



다시 포항역에 돌아가기 전 차 안에서 이 멋진 광경을 바라보며 영일만을 떠나갑니다... 



이 장소를 Daum지도에서 확인해보세요.
경북 포항시 북구 흥해읍 용한리 853 | 포항영일만항
도움말 Daum 지도

동영상으로 보는 엔코더의 작동 원리

임베디드 2019. 12. 16. 00:43


 이전에 작성하였던 엔코더의 작동 원리에 대한 글이 지금까지도 많은 분들께서 찾아와주시는 것을 보면서 제 글이 많은 사람들에게 도움을 드리고 있다는 것에 상당한 뿌듯함을 느꼇습니다. 지금도 댓글로 질문을 달아주시는 분들이 계시는 것을 보면서 많은 분들께서 엔코더에 대해 궁금해 하시는 것이 많다는 것을 알게 되었습니다.


 어떻게 하면 엔코더의 작동 과정을 직관적으로 설명할 수 있을까 하다가 유튜브에서 좋은 영상 자료를 구하게 되어 여러분들께 보여드리고자 합니다. 먼저 동영상을 보신 다음 아래의 설명을 읽어주신다면 이해하는데 많은 도움이 되시리라 믿습니다.


Absolute(절대)방식과 Incremental(증분)방식 엔코더의 차이점





 엔코더의 구조로 크게 선형(Linear)엔코더와 로터리(Rotary)엔코더가 있습니다. 선형엔코더는 이름 그대로 직선으로 이동하며 위치를 측정하는 용도로 사용되며 로터리엔코더는 회전 정보를 측정하는 용도로 사용됩니다.



 선형엔코더는 트랜스듀서(Transducer)를 사용하여 위치 변화를 측정합니다. 로터리(샤프트)엔코더의 경우 샤프트 축을 기준으로 회전 과정에서 얻게 되는 정보를 측정합니다. 위의 로터리엔코더는 Absolute 방식으로 회전 각도에 대한 정보를 출력합니다.



 그렇다면 로터리엔코더의 Absolute 방식과 Incremental 방식은 어떠한 차이가 있을까요? 두 엔코더는 사용 목적에 따라 크게 다릅니다. 측정하고자 하는 대상이 샤프트를 중심으로 어느 정도 회전했는지를 360도 이내에서 알아내기 위해서는 Absolute 방식이 적합하고, 회전 속도를 측정하는 것이 목적이라면 Incremental 방식이 적합합니다.

 Absolute 방식은 어느 정도 회전 했는지의 각도 정보(Angular Position)을 제공하며 Incremental 방식은 엔코더의 회전횟수에 따른 물체의 속도와 이동거리를 측정할 수 있습니다.



 Incremental 방식의 엔코더의 회전 속도를 측정하는 방법은 위의 그림과 같이 엔코더 내부의 슬릿(틈새)사이로 빛을 쏘는 부분과 빛은 수신하는 부분이 맞물린 구조에서 이루어집니다. 엔코더가 회전할 때 슬릿이 지나갈 때 마다 수신하는 부분에서는 빛이 차단되다 다시 수신되는 과정을 거치게 됩니다.



 Absolute 방식의 엔코더는 특정한 각도만큼 회전하였을 때 출력값이 위에서 보는 바와 같이 다르게 나타납니다. 이는 엔코더의 광수신 부분 갯수에 따라 각을 나타낼 수 있는 값이 더 미세해집니다. 위의 예제의 경우 3개의 광수신기가 있으므로 3^2=8개의 위치를 파악할 수 있는 것입니다.



 Absolute 엔코더는 구조에 따라 싱글턴(Single-turn) 방식과 멀티턴(Multi-turn) 방식이 있습니다. 싱글턴 방식의 경우 엔코더가 회전할 수 있는 360º 범위를 측정할 수 있습니다. 쉽게 말해 Absolute 엔코더가 15º 만큼 돌았는지 375º 만큼 한바퀴를 더 돌았는지 아닌지를 알 수 없다는 것입니다.

 이러한 싱글턴 엔코더의 한계를 해결하고자 한 것이 멀티턴 엔코더입니다. Absolute 엔코더의 광수신 부분을 위의 그림과 같이 하나 더 설치하여 엔코더의 회전수를 체크할 수 있도록 하여 각도의 변화량 측정 범위를 360º 이상의 범위에서 측정할 수 있게 됩니다.



 Incremental 엔코더의 경우 회전할 때 마다 광수신부의 빛이 차단과 수신을 반복하게 되면서 출력으로 Rising edge trigger가 수신될 때 마다 값을 1씩 증가시켜 엔코더의 회전 여부 및 속도를 나타낼 수 있습니다.


 그렇다면 엔코더의 회전 방향이 샤프트를 기준으로 시계방향(CW)인지 시계반대방향(CCW)인지 확인할 수 있는 방법은 어떤 것이 있을까요? 아래의 동영상을 통해 이해해보도록 합니다.


Incremental 방식 엔코더의 A상과 B상





 위에서 설명드렸듯이 Incremental 엔코더가 회전할 때 counting disc를 지날 때 마다 카운터가 1씩 증가되는 과정을 위 동영상에서 다시 확인하실 수 있습니다.



 이러한 특성의 Incremental 엔코더에 counting disc를 두 개로 만들어 두 개의 상이 90º 차이가 나도록 설계합니다. 이렇게 되면 A상과 B상이 서로 90º 위상 차이로 출력되고 있는 것을 보실 수 있습니다.



 지속적으로 회전을 해보면 A상이 90º 차이로 B상보다 먼저 출력되고 있음을 확인하실 수 있습니다.



 반대로 이번에는 엔코더의 회전 방향을 반대로 돌려봅니다. 이 경우 B상이 A상보다 90º 빠르게 돌아가고 있음을 확인하실 수 있습니다. 이러한 두 상의 위상차를 통해 엔코더의 회전 방향을 파악할 수 있음을 알 수 있습니다.


 엔코더의 작동 원리에 대해 좀 더 자세히 알아보고자 하시는 분들은 이전에 정리하였던 제 글을 참조해 주셨으면 합니다!


2016/07/31 - [임베디드] - [로터리 엔코더] 엔코더의 작동 원리 및 사용 방법


안드로이드 기기 간에 Wifi Socket 통신하기(Kotlin)


 오랜만에 안드로이드를 다루어보는 기회가 생겼는데 생각보다 많은 변화가 있었습니다. 특히 올해부터는 구글에서 새로 만든 프로그래밍 언어인 Kotlin이 도입되면서 Java 위주로 설계된 안드로이드 생태계에 거대한 변화가 있을 것으로 보입니다. 이러한 안드로이드의 환경 변화에 맞추어 제가 기존에 다루었던 프로그램들을 Kotlin으로 다시 한 번 설계해보려고 합니다.


 이전에 안드로이드 기기간의 Wi-Fi 통신 프로그래밍을 설계하였던 적이 있었는데 Kotlin으로 다시 짜보는 김에 프로그램을 최적화 해서 다시 만들어보았습니다.


AndroidMenifest.xml

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.elecs.interandroidcommunication">
 
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>
cs


strings.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resources>
    <string name="app_name">IAC</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="ip">IP</string>
    <string name="port">PORT</string>
    <string name="name">NAME</string>
    <string name="msg">MESSAGE</string>
    <string name="hint_port">PORT NUMBER</string>
    <string name="hint_ip">192.168.0.1</string>
    <string name="hint_msg">Hello, world!</string>
    <string name="button1">Connect</string>
    <string name="button2">Disconnect</string>
    <string name="button3">Set Server</string>
    <string name="button4">Close Server</string>
    <string name="button5">View my info</string>
    <string name="button6">Send Message</string>
</resources>
 
cs


activity_main.xml

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
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <TextView
                android:id="@+id/textView"
                android:layout_width="70dp"
                android:layout_height="wrap_content"
                android:text="@string/ip" />
 
            <EditText
                android:id="@+id/et_ip"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/hint_ip"
                android:importantForAccessibility="no"
                tools:ignore="Autofill"
                android:inputType="textUri"
                android:singleLine="true" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <TextView
                android:id="@+id/textView2"
                android:layout_width="70dp"
                android:layout_height="wrap_content"
                android:text="@string/port" />
 
            <EditText
                android:id="@+id/et_port"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/hint_port"
                android:importantForAccessibility="no"
                tools:ignore="Autofill"
                android:inputType="number"
                android:singleLine="true" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <TextView
                android:id="@+id/textView3"
                android:layout_width="70dp"
                android:layout_height="wrap_content"
                android:text="@string/msg" />
 
            <EditText
                android:id="@+id/et_msg"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/hello_world"
                android:importantForAccessibility="no"
                tools:ignore="Autofill"
                android:inputType="textPersonName" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <Button
                android:id="@+id/button_connect"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:minWidth="0dip"
                android:text="@string/button1"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_disconnect"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:minWidth="0dip"
                android:text="@string/button2"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_msg"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:minWidth="0dip"
                android:text="@string/button6"
                android:textSize="12sp" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <Button
                android:id="@+id/button_setserver"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/button3"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_closeserver"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/button4"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_info"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/button5"
                android:textSize="12sp" />
 
        </LinearLayout>
 
        <TextView
            android:id="@+id/text_status"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
 
    </LinearLayout>
 
</androidx.constraintlayout.widget.ConstraintLayout>
cs


MainActivity.kt

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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package com.elecs.interandroidcommunication
 
import android.content.Context
import android.net.ConnectivityManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.*
 
class MainActivity : AppCompatActivity() {
 
    companion object{
        var socket = Socket()
        var server = ServerSocket()
        lateinit var writeSocket: DataOutputStream
        lateinit var readSocket: DataInputStream
        lateinit var cManager: ConnectivityManager
 
        var ip = "192.168.0.1"
        var port = 2222
        var mHandler = Handler()
        var closed = false
    }
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        cManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        server.close()
        socket.close()
 
        button_connect.setOnClickListener {    //클라이언트 -> 서버 접속
            if(et_ip.text.isNotEmpty()) {
                ip = et_ip.text.toString()
                if(et_port.text.isNotEmpty()) {
                    port = et_port.text.toString().toInt()
                    if(port<0 || port>65535){
                        Toast.makeText(this@MainActivity, "PORT 번호는 0부터 65535까지만 가능합니다.", Toast.LENGTH_SHORT).show()
                    }else{
                        if(!socket.isClosed){
                            Toast.makeText(this@MainActivity, ip + "에 이미 연결되어 있습니다.", Toast.LENGTH_SHORT).show()
                        }else {
                            Connect().start()
                        }
                    }
 
                }else{
                    Toast.makeText(this@MainActivity, "PORT 번호를 입력해주세요.", Toast.LENGTH_SHORT).show()
                }
            }else{
                Toast.makeText(this@MainActivity, "IP 주소를 입력해주세요.", Toast.LENGTH_SHORT).show()
            }
        }
 
        button_disconnect.setOnClickListener {    //클라이언트 -> 서버 접속 끊기
            if(!socket.isClosed){
                Disconnect().start()
            }else{
                Toast.makeText(this@MainActivity, "서버와 연결이 되어있지 않습니다.", Toast.LENGTH_SHORT).show()
            }
        }
 
        button_setserver.setOnClickListener{    //서버 포트 열기
            if(et_port.text.isNotEmpty()) {
                val cport = et_port.text.toString().toInt()
                if(cport<0 || cport>65535){
                    Toast.makeText(this@MainActivity, "PORT 번호는 0부터 65535까지만 가능합니다.", Toast.LENGTH_SHORT).show()
                }else{
                    if(server.isClosed) {
                        port = cport
                        SetServer().start()
                    }else{
                        val tstr = port.toString() + "번 포트가 열려있습니다."
                        Toast.makeText(this@MainActivity, tstr, Toast.LENGTH_SHORT).show()
                    }
                }
            }else{
                Toast.makeText(this@MainActivity, "PORT 번호를 입력해주세요.", Toast.LENGTH_SHORT).show()
            }
        }
 
        button_closeserver.setOnClickListener {    //서버 포트 닫기
            if(!server.isClosed){
                CloseServer().start()
            }else{
                mHandler.obtainMessage(17).apply {
                    sendToTarget()
                }
            }
        }
 
        button_info.setOnClickListener {    //자기자신의 연결 정보(IP 주소)확인
            ShowInfo().start()
        }
 
        button_msg.setOnClickListener {    //상대에게 메시지 전송
            if(socket.isClosed){
                Toast.makeText(this@MainActivity, "연결이 되어있지 않습니다.", Toast.LENGTH_SHORT).show()
            }else {
                val mThread = SendMessage()
                mThread.setMsg(et_msg.text.toString())
                mThread.start()
            }
        }
 
        mHandler = object : Handler(Looper.getMainLooper()){  //Thread들로부터 Handler를 통해 메시지를 수신
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                when(msg.what){
                    1->Toast.makeText(this@MainActivity, "IP 주소가 잘못되었거나 서버의 포트가 개방되지 않았습니다.", Toast.LENGTH_SHORT).show()
                    2->Toast.makeText(this@MainActivity, "서버 포트 "+port +"가 준비되었습니다.", Toast.LENGTH_SHORT).show()
                    3->Toast.makeText(this@MainActivity, msg.obj.toString(), Toast.LENGTH_SHORT).show()
                    4->Toast.makeText(this@MainActivity, "연결이 종료되었습니다.", Toast.LENGTH_SHORT).show()
                    5->Toast.makeText(this@MainActivity, "이미 사용중인 포트입니다.", Toast.LENGTH_SHORT).show()
                    6->Toast.makeText(this@MainActivity, "서버 준비에 실패하였습니다.", Toast.LENGTH_SHORT).show()
                    7->Toast.makeText(this@MainActivity, "서버가 종료되었습니다.", Toast.LENGTH_SHORT).show()
                    8->Toast.makeText(this@MainActivity, "서버가 정상적으로 닫히는데 실패하였습니다.", Toast.LENGTH_SHORT).show()
                    9-> text_status.text = msg.obj as String
                    11->Toast.makeText(this@MainActivity, "서버에 접속하였습니다.", Toast.LENGTH_SHORT).show()
                    12->Toast.makeText(this@MainActivity, "메시지 전송에 실패하였습니다.", Toast.LENGTH_SHORT).show()
                    13->Toast.makeText(this@MainActivity, "클라이언트와 연결되었습니다.",Toast.LENGTH_SHORT).show()
                    14->Toast.makeText(this@MainActivity,"서버에서 응답이 없습니다.", Toast.LENGTH_SHORT).show()
                    15->Toast.makeText(this@MainActivity, "서버와의 연결을 종료합니다.", Toast.LENGTH_SHORT).show()
                    16->Toast.makeText(this@MainActivity, "클라이언트와의 연결을 종료합니다.", Toast.LENGTH_SHORT).show()
                    17->Toast.makeText(this@MainActivity, "포트가 이미 닫혀있습니다.", Toast.LENGTH_SHORT).show()
                    18->Toast.makeText(this@MainActivity, "서버와의 연결이 끊어졌습니다.", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
 
    class Connect:Thread(){
 
        override fun run(){
            try{
                socket = Socket(ip, port)
                writeSocket = DataOutputStream(socket.getOutputStream())
                readSocket = DataInputStream(socket.getInputStream())
                val b = readSocket.read()
                if(b==1){    //서버로부터 접속이 확인되었을 때
                    mHandler.obtainMessage(11).apply {
                        sendToTarget()
                    }
                    ClientSocket().start()
                }else{    //서버 접속에 성공하였으나 서버가 응답을 하지 않았을 때
                    mHandler.obtainMessage(14).apply {
                        sendToTarget()
                    }
                    socket.close()
                }
            }catch(e:Exception){    //연결 실패
                val state = 1
                mHandler.obtainMessage(state).apply {
                    sendToTarget()
                }
                socket.close()
            }
 
        }
    }
 
    class ClientSocket:Thread(){
        override fun run() {
            try{
                while (true) {
                    val ac = readSocket.read()
                    if(ac == 2) {    //서버로부터 메시지 수신 명령을 받았을 때
                        val bac = readSocket.readUTF()
                        val input = bac.toString()
                        val recvInput = input.trim()
 
                        val msg = mHandler.obtainMessage()
                        msg.what = 3
                        msg.obj = recvInput
                        mHandler.sendMessage(msg)
                    }else if(ac == 10){    //서버로부터 접속 종료 명령을 받았을 때
                        mHandler.obtainMessage(18).apply {
                            sendToTarget()
                        }
                        socket.close()
                        break
                    }
                }
            }catch(e:SocketException){    //소켓이 닫혔을 때
                mHandler.obtainMessage(15).apply {
                    sendToTarget()
                }
            }
        }
    }
 
    class Disconnect:Thread(){
        override fun run() {
            try{
                writeSocket.write(10)    //서버에게 접속 종료 명령 전송
                socket.close()
            }catch(e:Exception){
 
            }
        }
    }
 
    class SetServer:Thread(){
 
        override fun run(){
            try{
                server = ServerSocket(port)    //포트 개방
                mHandler.obtainMessage(2"").apply {
                    sendToTarget()
                }
 
                while(true) {
                    socket = server.accept()
                    writeSocket = DataOutputStream(socket.getOutputStream())
                    readSocket = DataInputStream(socket.getInputStream())
 
                    writeSocket.write(1)    //클라이언트에게 서버의 소켓 생성을 알림
                    mHandler.obtainMessage(13).apply {
                        sendToTarget()
                    }
                    while (true) {
                        val ac = readSocket.read()
                        if(ac==10){    //클라이언트로부터 소켓 종료 명령 수신
                            mHandler.obtainMessage(16).apply {
                                sendToTarget()
                            }
                            break
                        }else if(ac == 2){    //클라이언트로부터 메시지 전송 명령 수신
                            val bac = readSocket.readUTF()
                            val input = bac.toString()
                            val recvInput = input.trim()
 
                            val msg = mHandler.obtainMessage()
                            msg.what = 3
                            msg.obj = recvInput
                            mHandler.sendMessage(msg)    //핸들러에게 클라이언트로 전달받은 메시지 전송
                        }
                    }
                }
 
            }catch(e:BindException) {    //이미 개방된 포트를 개방하려 시도하였을때
                mHandler.obtainMessage(5).apply {
                    sendToTarget()
                }
            }catch(e:SocketException){    //소켓이 닫혔을 때
                mHandler.obtainMessage(7).apply {
                    sendToTarget()
                }
            }
            catch(e:Exception){
                if(!closed) {
                    mHandler.obtainMessage(6).apply {
                        sendToTarget()
                    }
                }else{
                    closed = false
                }
            }
        }
    }
 
    class CloseServer:Thread(){
        override fun run(){
            try{
                closed = true
                writeSocket.write(10)    //클라이언트에게 서버가 종료되었음을 알림
                socket.close()
                server.close()
            }catch(e:Exception){
                e.printStackTrace()
                mHandler.obtainMessage(8).apply {
                    sendToTarget()
                }
            }
        }
    }
 
    class SendMessage:Thread(){
        private lateinit var msg:String
 
        fun setMsg(m:String){
            msg = m
        }
 
        override fun run() {
            try{
                writeSocket.writeInt(2)    //메시지 전송 명령 전송
                writeSocket.writeUTF(msg)    //메시지 내용 
            }catch(e:Exception){
                e.printStackTrace()
                mHandler.obtainMessage(12).apply {
                    sendToTarget()
                }
            }
        }
    }
 
    class ShowInfo:Thread(){
 
        override fun run(){
            lateinit var ip:String
            var breakLoop = false
            val en = NetworkInterface.getNetworkInterfaces()
            while(en.hasMoreElements()){
                val intf = en.nextElement()
                val enumIpAddr = intf.inetAddresses
                while(enumIpAddr.hasMoreElements()){
                    val inetAddress = enumIpAddr.nextElement()
                    if(!inetAddress.isLoopbackAddress && inetAddress is Inet4Address){
                        ip = inetAddress.hostAddress.toString()
                        breakLoop = true
                        break
                    }
                }
                if(breakLoop){
                    break
                }
            }
 
            val msg = mHandler.obtainMessage()
            msg.what = 9
            msg.obj = ip
            mHandler.sendMessage(msg)
        }
    }
 
}
 
cs



위의 코드를 실행화면 다음과 같은 화면이 나타납니다.




※Sever 사용 방법

1. Port에 원하는 포트 번호를 입력합니다.

2. 'SET SERVER' 버튼을 클릭하면 포트를 열 수 있습니다. 만약 다른 프로세스에서 포트를 사용중일 경우 다른 포트 번호를 입력합니다.

3. 'VIEW MY INFO' 버튼을 클릭하면 현재 접속중인 IP주소를 알 수 있습니다. 이를 Client의 IP란에 입력해주세요.

4. Client와의 연결이 성공하면 Toast로 연결에 성공하였다는 알림이 나옵니다.


※Client 사용 방법

1. IP와 PORT에 서버의 IP주소와 설정 PORT 번호를 입력합니다.

2. 'CONNECT' 버튼을 클릭하여 서버에 접속합니다. 성공시 Toast로 접속에 성공하였다는 메시지를 확인하실 수 있습니다.

3. 'SEND MESSAGE' 버튼을 클릭하면 MESSAGE 칸에 입력한 텍스트 정보를 상대에게 전송할 수 있습니다.

4.  'DISCONNECT' 버튼을 눌러 서버와의 연결을 해제합니다.