나중에 쓸 것 같아서 (이러고 쓰지 않는다.) 대충 서버와 메시지를 주고 받을 수 있게 만들었다.
기본 화면 구성이다.
IP와 Port를 받아서 소켓을 생성하고 Input datas에 아무 문자열을 입력하면 보내면
에코서버에서 똑같은 문자열을 보내며 그 결과를 토스트객체를 통해 화면에 띄운다. 이게 다다. 매우 초라하다.
예외처리를 하지 않았기 때문에 소켓 생성 없이 SEND 버튼을 누르면 앱이 뻗는다. 꼭 미리 누르자.
AndroidManifest.xml 파일에 네트워킹 사용을 위해
=> <uses-permission android:name="android.permission.INTERNET"/>
이 퍼미션을 넣는 것을 잊으면 안된다.
참고 : 에코서버 코드
MainActivity.java
package com.example.hyunseo.testingapp; import android.app.ProgressDialog; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.*; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button m_bCreateSocket; private Button m_bSend; private EditText m_etInputIP; private EditText m_etInputPort; private EditText m_etSendData; private ProgressDialog m_pdIsLoading; private SocketManager m_hSocketManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); m_etInputIP = (EditText) findViewById(R.id.editText_inputIP); m_etInputPort = (EditText) findViewById(R.id.editText_inputPort); m_etSendData = (EditText) findViewById(R.id.editText_sendData); m_pdIsLoading = new ProgressDialog(this); // 클릭 이벤트 리스너 등록 m_bCreateSocket.setOnClickListener(this); m_bSend.setOnClickListener(this); } private Handler m_Handler = new Handler() { public void handleMessage(Message msg) { switch(msg.what) { case 0: // 소켓 생성 완료 // 로딩화면 제거 m_pdIsLoading.dismiss(); break; case 1: // 데이터 수신 완료 // 수신 데이터 토스트로 띄움. Toast.makeText(MainActivity.this, "server responded : " + msg.obj, Toast.LENGTH_SHORT).show(); break; } } }; public void onClick(View v) { switch (v.getId()) { case R.id.button_CreateSocket: String IP = m_etInputIP.getText().toString(); int Port = Integer.parseInt(m_etInputPort.getText().toString()); m_pdIsLoading.setProgressStyle(ProgressDialog.STYLE_SPINNER); m_pdIsLoading.setMessage("Loading.."); m_pdIsLoading.show(); m_hSocketManager = new SocketManager(IP, Port, m_Handler); break; case R.id.button_Send: m_hSocketManager.sendData(m_etSendData.getText().toString()); break; } } }
안드로이드에서는 아이스크림 샌드위치 버전부터(아마 맞음) 소켓 생성같은 네트워킹 작업들을 메인 UI 스레드에서는
못하도록 해놓았다. 그래서 SocketManager에서 별도의 스레드를 생성하여 네트워킹을 관리하며 메인 스레드에서는 핸들러를 통한
화면 갱신만을 하고 있다. 여기서 ProgressDialog는 흔히 "로딩중"을 표시하는 객체로
요 화면이다. 소켓 생성이 끝나면 핸들러를 통해 메시지를 받아 ProgressDialog를 제거한다.
SocketManager.java
package com.example.hyunseo.testingapp; import android.os.Handler; import android.os.Message; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.Iterator; public class SocketManager { private String IP; private int Port; private SocketChannel m_hSocketChannel; private Selector m_hSelector; private readDataThread m_readData; private sendDataThread m_sendData; private Handler m_handler; public SocketManager(String ip, int port, Handler h) { this.IP = ip; this.Port = port; this.m_handler = h; // thread objects의 작업 할당 및 초기화 m_readData = new readDataThread(); m_readData.start(); } // m_createSocket thread 안에서 실행 private void setSocket(String ip, int port) throws IOException { // selector 생성 m_hSelector = Selector.open(); // 채널 생성 m_hSocketChannel = SocketChannel.open(new InetSocketAddress(ip, port)); // 논블로킹 모드 설정 m_hSocketChannel.configureBlocking(false); // 소켓 채널을 selector에 등록 m_hSocketChannel.register(m_hSelector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE); } public void sendData(String data) { m_sendData = new sendDataThread(m_hSocketChannel, data); m_sendData.start(); } private void read(SelectionKey key) throws Exception { // SelectionKey로부터 소켓채널을 얻어온다. SocketChannel sc = (SocketChannel) key.channel(); // ByteBuffer를 생성한다. ByteBuffer buffer = ByteBuffer.allocate(1024); int read = 0; // 요청한 클라이언트의 소켓채널로부터 데이터를 읽어들인다. read = sc.read(buffer); buffer.flip(); String data = new String(); CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); data = decoder.decode(buffer).toString(); // 메시지 얻어오기 Message msg = m_handler.obtainMessage(); // 메시지 ID 설정 msg.what = 1; // 메시지 정보 설정3 (Object 형식) msg.obj = data; m_handler.sendMessage(msg); // 버퍼 메모리를 해제한다. clearBuffer(buffer); } private void clearBuffer(ByteBuffer buffer) { if (buffer != null) { buffer.clear(); buffer = null; } } /*********** inner thread classes **************/ public class sendDataThread extends Thread { private SocketChannel sdt_hSocketChannel; private String data; public sendDataThread(SocketChannel sc, String d) { sdt_hSocketChannel = sc; data = d; } public void run() { try { // 데이터 전송. sdt_hSocketChannel.write(ByteBuffer.wrap(data.getBytes())); } catch (Exception e1) { } } } public class readDataThread extends Thread { public readDataThread() { } public void run() { try { setSocket(IP, Port); } catch (IOException e) { e.printStackTrace(); } // 소켓 생성 완료를 메인UI스레드에 알림. m_handler.obtainMessage(); m_handler.sendEmptyMessage(0); // 데이터 읽기 시작. try { while(true) { // 셀렉터의 select() 메소드로 준비된 이벤트가 있는지 확인한다. m_hSelector.select(); // 셀렉터의 SelectoedSet에 저장된 준비된 이벤트들(SelectionKey)을 하나씩 처리한다. Iterator it = m_hSelector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); if (key.isReadable()) { // 이미 연결된 클라이언트가 메시지를 보낸경우... try { read(key); } catch (Exception e) { } } // 이미 처리한 이벤트므로 반드시 삭제해준다. it.remove(); } } } catch (Exception e) { } } } }
아주 중구난방이다.
기본 소켓은 블럭킹 방식이기 때문에 SocketChannel을 이용하여 논블럭킹 방식으로 소켓을 생성하였다.
크게 중요한 스레드 객체가 두개있다. 소켓 생성 후 소켓들이 등록된(클라이언트라 하나밖에 음슴) Selector를 돌며
읽을 수 있는 상태의 소켓이 있으면 데이터를 읽는 readDataThread와 단순히 서버로 데이터를 보내는 sendDataThread이다.
readDataThread에서 수신 가능한 소켓이 있으면 read() 메소드를 호출하여 핸들러를 통해 UI 스레드로 보내고
UI 스레드에서 Toast를 이용하여 화면에 띄운다.
'프로그래밍 > Java' 카테고리의 다른 글
너비 우선 탐색(BFS) (0) | 2019.10.21 |
---|---|
깊이 우선 탐색 (DFS), 인접 행렬, 인접 리스트 (0) | 2019.10.18 |
[Android] 사설 SSL 인증서를 이용한 https 통신 (4) | 2018.05.30 |
[JAVA] NFC 제어 (2) | 2017.10.15 |
[Android] HCE를 이용한 NFC 통신 (8) | 2017.10.15 |