본문으로 건너뛰기

안드로이드 웹소켓 예시

WebSocket은 클라이언트와 서버 간의 양방향 대화식 통신 세션을 열 수 있도록 하는 프로토콜입니다. 단일 TCP 연결을 통해 전이중 통신 채널을 제공합니다.

이 프로토콜은 웹 브라우저와 서버 사이에 "소켓" 연결을 설정하는 API를 정의합니다. 예를 들어 라이브 스코어 앱에서 업데이트를 받기 위해 긴 폴링을 하는 대신 웹소켓을 사용하여 클라이언트와 서버 사이에 지속적인 연결을 만들 수 있습니다. 따라서 최신 업데이트를 사용할 수 있을 때마다 앱이 자동으로 업데이트됩니다.

이렇게 하면 최신 업데이트가 있는지 확인하기 위해 추가 HTTP 요청을 보내는 오버헤드를 피할 수 있습니다.

따라서 클라이언트와 서버는 핸드셰이크만 완료하면 되며 둘 사이에 직접 지속적인 연결을 생성할 수 있으며 양방향 데이터 전송이 수행됩니다.

![Websocket](https://camo.githubusercontent.com/a985b5f6ece41fbd5ebcf5e126ac275cc0aab8f1d92c09696421f4c40e685f8a/68747470733a2f2f696d672d626c6f672e6373646e 696d672e636e2f32303139303130373130353635383936322e706e673f782d6f73732d70726f636573733d696d6167652f77617465726d61726b2c74797 0655f5a6d46755a33706f5a57356e6147567064476b2c736861646f775f31302c746578745f6148523063484d364c7939696247396e4c6d4e7a5a473475 626d56304c325a7662576c755833706f64513d3d2c73697a655f31362c636f6c6f725f4646464646462c745f3730)

Websocket은 대부분의 방화벽 제한을 우회할 수 있는 HTTP와 동일한 TCP 포트를 사용합니다. 기본적으로 Websocket 프로토콜은 포트 80을 사용합니다. TLS 위에서 실행할 때 기본적으로 포트 443이 사용됩니다. 해당 프로토콜 식별자는 ws입니다. wss가 암호화에 사용되는 경우 서버 웹 주소는 다음과 같은 URL입니다.

ws://www.example.com/
wss://www.example.com/

웹소켓의 장점

  • 제어 오버헤드 감소: 연결이 생성된 후 서버와 클라이언트 간에 데이터를 교환할 때 프로토콜 제어에 사용되는 데이터 패킷 헤더가 상대적으로 작음
  • 더 강력한 실시간 성능: 프로토콜이 전이중 방식이므로 서버는 언제든지 클라이언트에 능동적으로 데이터를 보낼 수 있습니다.
  • 연결 상태 유지: HTTP와 달리 Websocket은 먼저 연결을 생성해야 상태 저장 프로토콜이 되고 통신 시 상태 정보의 일부가 생략될 수 있습니다. HTTP 요청은 각 요청에서 상태 정보(예: ID 인증 등)를 전달해야 할 수 있습니다.
  • 더 나은 바이너리 지원: Websocket은 HTTP보다 바이너리 콘텐츠를 더 쉽게 처리할 수 있는 바이너리 프레임을 정의합니다.
  • 더 나은 압축 효과: HTTP 압축과 비교하여 Websocket은 적절한 확장 지원으로 이전 콘텐츠의 컨텍스트를 사용할 수 있으며 유사한 데이터를 전송할 때 압축률을 크게 향상시킬 수 있습니다.

예제 1: Android WebSockets 예제

이 튜토리얼에서는 OkHTTP를 사용하여 웹 소켓을 사용하는 방법을 배웁니다. url(ws://echo.websocket.org)은 websocket을 설정하는 데 사용됩니다.

1단계: Okhttp 설치

앱 수준 build.gradle에서 다음 구현 문을 추가합니다.

implementation 'com.squareup.okhttp3:okhttp:3.6.0'

2단계: 인터넷 권한 추가

Android 매니페스트에서 다음과 같이 인터넷 권한을 추가합니다.

    <uses-permission android:name="android.permission.INTERNET"/>

3단계: 디자인 레이아웃

textview와 버튼으로 레이아웃을 생성합니다. textview는 서버의 결과를 보여줍니다. 반면에 버튼은 연결을 시작합니다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/buttonSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="SEND"
android:layout_marginTop="60dp"
android:textSize="20sp"/>
<TextView
android:id="@+id/textResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/buttonSend"
android:layout_centerHorizontal="true"
android:textSize="18sp"
android:layout_marginTop="40dp"/>
</RelativeLayout>

4단계: 코드 작성

textview에서 서버의 결과를 인쇄하는 도우미 메서드를 만드는 것으로 시작합니다. 이는 UI 스레드에서 수행됩니다.

    private void print(final String message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
textResult.setText(textResult.getText().toString() + "\n" + message);
}
});
}

EchoWebListener를 내부 클래스로 만듭니다. 이 클래스는 WebSocketListener를 확장합니다.

    private final class EchoWebSocketListener extends WebSocketListener {
private static final int CLOSE_STATUS = 1000;
@Override
public void onOpen(WebSocket webSocket, Response response) {
webSocket.send("What's up ?");
webSocket.send(ByteString.decodeHex("abcd"));
webSocket.close(CLOSE_STATUS, "Socket Closed !!");
}
@Override
public void onMessage(WebSocket webSocket, String message) {
print("Receive Message: " + message);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
print("Receive Bytes : " + bytes.hex());
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
webSocket.close(CLOSE_STATUS, null);
print("Closing Socket : " + code + " / " + reason);
}
@Override
public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
print("Error : " + throwable.getMessage());
}
}

다음 방법은 websocket 연결을 시작합니다. OkHTTP 요청 클래스가 인스턴스화되고 url이 url() 메서드로 전달됩니다. 그런 다음 EchoWebSocketListener를 인스턴스화하고 Request 개체와 EchoWebListener 인스턴스를 모두 newWebSocket() 메서드에 전달합니다.

전체 방법은 다음과 같습니다.

    private void start() {
Request request = new Request.Builder().url("ws://echo.websocket.org").build();
EchoWebSocketListener listener = new EchoWebSocketListener();
WebSocket webSocket = mClient.newWebSocket(request, listener);
mClient.dispatcher().executorService().shutdown();
}

전체 코드는 다음과 같습니다.

메인액티비티.자바


import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;

public class MainActivity extends AppCompatActivity {

private Button buttonSend;
private TextView textResult;
private OkHttpClient mClient;

private final class EchoWebSocketListener extends WebSocketListener {
private static final int CLOSE_STATUS = 1000;
@Override
public void onOpen(WebSocket webSocket, Response response) {
webSocket.send("What's up ?");
webSocket.send(ByteString.decodeHex("abcd"));
webSocket.close(CLOSE_STATUS, "Socket Closed !!");
}
@Override
public void onMessage(WebSocket webSocket, String message) {
print("Receive Message: " + message);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
print("Receive Bytes : " + bytes.hex());
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
webSocket.close(CLOSE_STATUS, null);
print("Closing Socket : " + code + " / " + reason);
}
@Override
public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
print("Error : " + throwable.getMessage());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonSend = (Button) findViewById(R.id.buttonSend);
textResult = (TextView) findViewById(R.id.textResult);
mClient = new OkHttpClient();
buttonSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
start();
}
});
}
private void start() {
Request request = new Request.Builder().url("ws://echo.websocket.org").build();
EchoWebSocketListener listener = new EchoWebSocketListener();
WebSocket webSocket = mClient.newWebSocket(request, listener);
mClient.dispatcher().executorService().shutdown();
}
private void print(final String message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
textResult.setText(textResult.getText().toString() + "\n" + message);
}
});
}
}

참조

아래는 다운로드 링크입니다.

번호링크
1.다운로드 코드
2.팔로우 코드 작성자

예시 2: Kotlin Android Websocket with Okhttp 예시

다음은 또 다른 Android websocket 예제이지만 이번에는 Kotlin으로 작성되었습니다. 여전히 OkHttp를 네트워킹 라이브러리로 사용합니다.

1단계: 프로젝트 생성

Android Studio 프로젝트를 생성하여 시작합니다.

2단계: 종속성

두 개의 OkHttp 라이브러리를 설치합니다.

    implementation 'com.squareup.okhttp3:okhttp:3.12.6'
implementation 'com.squareup.okhttp3:mockwebserver:3.12.1'

app/build.gradle에 추가하고 동기화하십시오.

3단계: 디자인 레이아웃

MainActivity 레이아웃에서 아래와 같이 몇 개의 버튼과 편집 텍스트를 추가합니다.

activity_main.xml

<?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">

<Button
android:id="@+id/connectBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android : text = " Connect "
app:layout_constraintRight_toLeftOf="@+id/clientSendBtn"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/clientSendBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android : text = " Send from the client "
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/closeConnectionBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android : text = " Client is closed "
app:layout_constraintLeft_toRightOf="@+id/clientSendBtn"
app:layout_constraintTop_toTopOf="parent" />

<EditText
android:id="@+id/contentEt"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:background="@color/colorGray"
android:enabled="false"
android:gravity="top"
android:padding="5dp"
android:textColor="@color/colorWhite"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/clientSendBtn"
app:layout_constraintVertical_weight="1" />

</androidx.constraintlayout.widget.ConstraintLayout>

4단계: 메시지 리스너 만들기

이것은 연결 상태에 따라 발생하는 여러 콜백이 있는 인터페이스입니다. 예를 들어 성공적으로 연결되었을 때, 닫혔을 때, 실패했을 때 등입니다.

MessageListener.kt

interface MessageListener {
fun onConnectSuccess () // successfully connected
fun onConnectFailed () // connection failed
fun onClose () // close
fun onMessage(text: String?)
}

5단계: Websocket 관리자 만들기

WebSocketM 생성anager.kt는 다음 가져오기를 추가하여 시작합니다.

import  android.util.Log
import okhttp3.*
import okio.ByteString
import java.util.concurrent.TimeUnit

다음 개인 필드를 사용하여 WebSocketManager 개체 클래스를 만듭니다.

object  WebSocketManager {
private val TAG = WebSocketManager::class.java.simpleName
private const val MAX_NUM = 5 // Maximum number of reconnections
private const val MILLIS = 5000 // Reconnection interval, milliseconds
private lateinit var client: OkHttpClient
private lateinit var request: Request
private lateinit var messageListener: MessageListener
private lateinit var mWebSocket: WebSocket
private var isConnect = false
private var connectNum = 0

그런 다음 init 함수는 URL과 MessageListener를 전달합니다. 여기에서 OkHTTP 클라이언트를 초기화합니다.

    fun init(url: String, _messageListener: MessageListener) {
client = OkHttpClient.Builder()
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build()
request = Request.Builder().url(url).build()
messageListener = _messageListener
}

이제 연결할 함수를 만듭니다.

    fun connect() {
if (isConnect()) {
Log.i(TAG, "web socket connected")
return
}
client.newWebSocket(request, createListener())
}

또한 다시 연결하는 함수를 만듭니다.

    fun reconnect() {
if (connectNum <= MAX_NUM) {
try {
Thread.sleep(MILLIS.toLong())
connect()
connectNum++
} catch (e: InterruptedException) {
e.printStackTrace ()
}
} else {
Log.i(
TAG,
"reconnect over $MAX_NUM,please check url or network"
)
}
}

또한 메시지를 보내는 기능도 있습니다.

    fun sendMessage(text: String): Boolean {
return if (!isConnect()) false else mWebSocket.send(text)
}
fun sendMessage(byteString: ByteString): Boolean {
return if (!isConnect()) false else mWebSocket.send(byteString)
}

연결을 종료하는 기능:

    fun close() {
if (isConnect()) {
mWebSocket.cancel()
mWebSocket.close( 1001 , "The client actively closes the connection " )
}
}

전체 코드는 다음과 같습니다.

WebSocketManager.kt

import  android.util.Log
import okhttp3.*
import okio.ByteString
import java.util.concurrent.TimeUnit

object WebSocketManager {
private val TAG = WebSocketManager::class.java.simpleName
private const val MAX_NUM = 5 // Maximum number of reconnections
private const val MILLIS = 5000 // Reconnection interval, milliseconds
private lateinit var client: OkHttpClient
private lateinit var request: Request
private lateinit var messageListener: MessageListener
private lateinit var mWebSocket: WebSocket
private var isConnect = false
private var connectNum = 0
fun init(url: String, _messageListener: MessageListener) {
client = OkHttpClient.Builder()
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build()
request = Request.Builder().url(url).build()
messageListener = _messageListener
}

/**
* connect
*/
fun connect() {
if (isConnect()) {
Log.i(TAG, "web socket connected")
return
}
client.newWebSocket(request, createListener())
}

/**
* Reconnection
*/
fun reconnect() {
if (connectNum <= MAX_NUM) {
try {
Thread.sleep(MILLIS.toLong())
connect()
connectNum++
} catch (e: InterruptedException) {
e.printStackTrace ()
}
} else {
Log.i(
TAG,
"reconnect over $MAX_NUM,please check url or network"
)
}
}

/**
* Whether to connect
*/
fun isConnect(): Boolean {
return isConnect
}

/**
* send messages
*
* @param text string
* @return boolean
*/
fun sendMessage(text: String): Boolean {
return if (!isConnect()) false else mWebSocket.send(text)
}

/**
* send messages
*
* @param byteString character set
* @return boolean
*/
fun sendMessage(byteString: ByteString): Boolean {
return if (!isConnect()) false else mWebSocket.send(byteString)
}

/**
* Close connection
*/
fun close() {
if (isConnect()) {
mWebSocket.cancel()
mWebSocket.close( 1001 , "The client actively closes the connection " )
}
}

private fun createListener(): WebSocketListener {
return object : WebSocketListener() {
override fun onOpen(
webSocket: WebSocket,
response: Response
) {
super.onOpen(webSocket, response)
Log.d(TAG, "open:$response")
mWebSocket = webSocket
isConnect = response.code() == 101
if (!isConnect) {
reconnect()
} else {
Log.i(TAG, "connect success.")
messageListener.onConnectSuccess()
}
}

override fun onMessage(webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
messageListener.onMessage(text)
}

override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
super.onMessage(webSocket, bytes)
messageListener.onMessage(bytes.base64())
}

override fun onClosing(
webSocket: WebSocket,
code: Int,
reason: String
) {
super.onClosing(webSocket, code, reason)
isConnect = false
messageListener.onClose()
}

override fun onClosed(
webSocket: WebSocket,
code: Int,
reason: String
) {
super.onClosed(webSocket, code, reason)
isConnect = false
messageListener.onClose()
}

override fun onFailure(
webSocket: WebSocket,
t: Throwable,
response: Response?
) {
super.onFailure(webSocket, t, response)
if (response != null) {
Log.i(
TAG,
"connect failed:" + response.message()
)
}
Log.i(
TAG,
"connect failed throwable:" + t.message
)
isConnect = false
messageListener.onConnectFailed()
reconnect()
}
}
}
}

6단계: MainActivity 코드 작성

다음은 MainActivity의 전체 코드입니다.

메인액티비티.kt

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity(), MessageListener {
private val serverUrl = "ws://192.168.18.145:8086/socketServer/abc"
override fun onCreate(savedInstanceState: Bundle?) {
super .onCreate (savedInstanceState)
setContentView(R.layout.activity_main)
WebSocketManager.init(serverUrl, this)
connectBtn.setOnClickListener {
thread {
kotlin.run {
WebSocketManager.connect()
}
}
}
clientSendBtn.setOnClickListener {
if ( WebSocketManager .sendMessage( " Client send " )) {
addText( " Send from the client \n " )
}
}
closeConnectionBtn.setOnClickListener {
WebSocketManager.close()
}
}

override fun onConnectSuccess() {
addText( " Connected successfully \n " )
}

override fun onConnectFailed() {
addText( " Connection failed \n " )
}

override fun onClose() {
addText( " Closed successfully \n " )
}

override fun onMessage(text: String?) {
addText( " Receive message: $text \n " )
}

private fun addText(text: String?) {
runOnUiThread {
contentEt.text.append(text)
}
}

override fun onDestroy() {
super .onDestroy ()
WebSocketManager.close()
}
}

달리다

코드를 복사하거나 아래 링크에서 다운로드하여 빌드하고 실행합니다.

참조

참조 링크는 다음과 같습니다.

번호링크
1.다운로드 예시
2.팔로우 코드 작성자