검색결과 리스트
2019/12에 해당되는 글 3건
- 2019.12.22 아름다운 영일만에 기적이 울리다 - 영일만항선[2019.12.18]
- 2019.12.16 동영상으로 보는 엔코더의 작동 원리
- 2019.12.04 안드로이드 기기 간에 Wifi Socket 통신하기(Kotlin) 2
글
아름다운 영일만에 기적이 울리다 - 영일만항선[2019.12.18]
우리나라 물류의 주요 수송방법이 육로가 대부분이라고 하지만 철도를 이용한 수송이 육로보다 효율적이기에 지금까지도 철도를 통한 물류 운송이 활발하게 이어지고 있습니다. 그럼에도 춘장대역으로 대표되는 서천화력선과 같이 더이상 사용되지 않고 폐선되는 철로가 있기도 합니다.
지난 2019년 12월 18일 화물철도인 영일만항선이 정식으로 개통되었습니다. 이번 포스팅에서는 새로 개통된 영일만항역 인근의 풍경을 담아보았습니다.
지도를 보았을 때 영일만은 포항시에 둘러싸인 모습을 하고 있습니다. 호랑이의 꼬리 부분에 해당하는 호미곶이 영일만을 감싸는 듯한 모습을 하고 있군요.
영일만항선은 포항역에서부터 영일만항까지 연결되어 있습니다. 영일만항 바로 옆에는 용한1리 해수욕장이 위치해 있습니다.
포항역에 내리다가 본 내일로 포스터
2019년 겨울 내일로는 만 34살까지 이용할 수 있습니다!
포항역에서 차로 15분 거리에 있는 포항국제컨테이너터미널에 도착하였습니다.
터미널 근처는 작은 어선들이 있습니다.
오늘 영일만항선의 개통식을 알리는 현수막들이 걸려있습니다.
최근 입체화 과정에서 사라져가는 건널목이 새로 만들어져있습니다.
아무래도 화물 열차가 많이 다니지 않아서인지 굳이 입체화를 할 필요가 없었던 듯 합니다.
희안하게 이 건널목에는 삼색 신호등도 설치되어 있습니다.
횡단보도가 없는 것으로 보다 기차가 지나갈 때만 동작하는 신호등인듯 합니다.
인근에 건널목이 있다는 표시판
철로는 컨테이너 터미널 내부로 연결되어 있습니다.
영일항만 주변은 공터가 대부분이었습니다.
곧 열차가 지나가려는지 청색 표시등이 켜져있습니다.
내부에서 개통식 행사를 하는 과정에서 시운전이 있었는지 멀리서 기관차가 보입니다.
다만 철문이 막혀있어 당장 건널목을 건널 수 없는 상황입니다.
이번에는 영일항만 근처 해수욕장을 찾아가 보았습니다.
해수욕장에 도착하자마자 서핑을 즐기는 사람들을 보았습니다.
이 추운 겨울에도 서핑을 하고자 하는 열정 대단합니다!
영일항만에 가려져서 이렇게 훌륭한 붕경을 보여주는 해수욕장이 근처에 있었다는 것이 신기하네요.
역시 동해안 답게 푸르른 바닷물이 인상깊습니다.
근처에 주차된 차량들이 상당히 많아 보이는데 무슨 일로 이 곳에 찾아온걸까요?
해수욕장에서 부터 방파제를 따라 만들어진 길을 산책해봅니다.
계속 걷다보니 아까 그 많은 사람들이 어디에 갔는지 알게 되었습니다.
어부가 잡은 물고기들이 바닥에서 싱싱하게 파닥거리고 있었습니다.
영일만 주변에 만들어진 방파제를 따라 사람들이 낚시를 즐기고 있었습니다!
등대 주변을 가보니 낚시를 즐기시는 분들이 많이 보입니다.
동해답게 물이 상당히 맑았습니다.
사진으로 다시 보니 실물보다 잘 안나오네요.
추운 날씨에 월척 한 마리 꼭 낚으시길!
다시 육지로 돌아가는길
생각보다 상당히 긴 길이었습니다.
저도 언젠가는 날씨 좋은 날에 바다낚시를 한 번 즐겨보고 싶네요!
다시 해수욕장에 돌아오니 서핑을 즐기시던 분들이 바다 바깥으로 나와계십니다.
가만히 서있기만 해도 추워보이는데 말이지요.
해수욕장과 영일만항을 함께 바라본 모습.
길 옆에 바닷가가 확 트인 모습 저는 매우 좋아합니다.
다시 포항역에 돌아가기 전 차 안에서 이 멋진 광경을 바라보며 영일만을 떠나갑니다...
'좌충우돌 여행기 > 국내여행' 카테고리의 다른 글
갤러리가 된 폐역, 잠시 부활하다 - 중앙선 반곡역[2020.06.22] (0) | 2020.06.23 |
---|---|
환골탈태(換骨奪胎)를 위해여 - 제천임시역(5)[2020.05.31] (0) | 2020.06.17 |
모노레일, 마을을 잇다 - 태백선 고한역[2019.08.30] (0) | 2019.09.14 |
탄광의 마을에서 리조트의 마을로 - 태백선 사북역[2019.08.30] (0) | 2019.09.04 |
환골탈태(換骨奪胎)를 위해여 - 제천임시역(4)[2019.08.30] (0) | 2019.09.03 |
설정
트랙백
댓글
글
동영상으로 보는 엔코더의 작동 원리
이전에 작성하였던 엔코더의 작동 원리에 대한 글이 지금까지도 많은 분들께서 찾아와주시는 것을 보면서 제 글이 많은 사람들에게 도움을 드리고 있다는 것에 상당한 뿌듯함을 느꼇습니다. 지금도 댓글로 질문을 달아주시는 분들이 계시는 것을 보면서 많은 분들께서 엔코더에 대해 궁금해 하시는 것이 많다는 것을 알게 되었습니다.
어떻게 하면 엔코더의 작동 과정을 직관적으로 설명할 수 있을까 하다가 유튜브에서 좋은 영상 자료를 구하게 되어 여러분들께 보여드리고자 합니다. 먼저 동영상을 보신 다음 아래의 설명을 읽어주신다면 이해하는데 많은 도움이 되시리라 믿습니다.
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 - [임베디드] - [로터리 엔코더] 엔코더의 작동 원리 및 사용 방법
'임베디드' 카테고리의 다른 글
[로터리 엔코더] 엔코더의 작동 원리 및 사용 방법 (42) | 2016.07.31 |
---|---|
진동모터 연결선이 빠졌을 때 대처법 (0) | 2014.08.08 |
파워서플라이로 모듈 사용시 주의사항 (0) | 2014.08.02 |
설정
트랙백
댓글
글
안드로이드 기기 간에 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' 버튼을 눌러 서버와의 연결을 해제합니다.