WebSocket은 클라이언트와 서버 간의 양방향 대화식 통신 세션을 열 수 있도록 하는 프로토콜입니다. 단일 TCP 연결을 통해 전이중 통신 채널을 제공합니다.
이 프로토콜은 웹 브라우저와 서버 사이에 "소켓" 연결을 설정하는 API를 정의합니다. 예를 들어 라이브스코어 앱에서 업데이트를 가져오기 위해 긴 폴링을 하는 대신 웹 소켓을 사용하여 클라이언트와 서버 간의 지속적인 연결을 생성할 수 있습니다. 따라서 앱은 최신 업데이트가 있을 때마다 자동으로 업데이트됩니다.
이렇게 하면 최신 업데이트가 있는지 확인하기 위해 추가 HTTP 요청을 보내는 오버헤드를 피할 수 있습니다.
따라서 클라이언트와 서버는 핸드셰이크를 완료하기만 하면 되며 둘 사이에 직접 영구 연결을 만들 수 있으며 양방향 데이터 전송이 수행됩니다.
Websocket은 대부분의 방화벽 제한을 우회할 수 있는 HTTP와 동일한 TCP 포트를 사용합니다. 기본적으로 Websocket 프로토콜은 포트 80을 사용합니다. TLS 위에서 실행할 때 기본적으로 포트 443이 사용됩니다. 프로토콜 식별자는 ws입니다. 암호화에 wss가 사용되는 경우 서버 웹 주소는 다음과 같은 URL입니다.
ws://www.example.com/
wss://www.example.com/
웹소켓의 장점
- 적은 제어 오버헤드: 연결이 생성된 후 서버와 클라이언트 간에 데이터 교환 시 프로토콜 제어에 사용되는 데이터 패킷 헤더가 상대적으로 작음
- 더 강력한 실시간 성능: 프로토콜이 전이중이므로 서버는 언제든지 능동적으로 클라이언트에 데이터를 보낼 수 있습니다.
- 연결 상태 유지: HTTP와 달리 Websocket은 먼저 연결을 생성해야 하므로 Stateful 프로토콜이 된 다음 통신 시 상태 정보의 일부를 생략할 수 있습니다. HTTP 요청은 각 요청에 상태 정보(예: ID 인증 등)를 전달해야 할 수 있습니다.
- 더 나은 바이너리 지원: Websocket은 바이너리 프레임을 정의하여 HTTP보다 바이너리 콘텐츠를 더 쉽게 처리할 수 있습니다.
- 더 나은 압축 효과: Websocket은 HTTP 압축과 비교하여 적절한 확장 지원으로 이전 콘텐츠의 컨텍스트를 사용할 수 있으며 유사한 데이터를 전송할 때 압축률을 크게 향상시킬 수 있습니다.
예제 1: Android WebSockets 예제
이 튜토리얼에서는 OkHTTP를 사용하여 웹 소켓을 사용하는 방법을 배웁니다. url(ws://echo.websocket.org)은 웹 소켓을 설정하는 데 사용됩니다.
1단계: Okhttp 설치
앱 수준 build.gradle에서 다음 구현 문을 추가합니다.
implementation 'com.squareup.okhttp3:okhttp:3.6.0'
2단계: 인터넷 권한 추가
Android 매니페스트에서 다음과 같이 인터넷 권한을 추가합니다.
<uses-permission android:name="android.permission.INTERNET"/>
3단계: 레이아웃 디자인
textview와 버튼으로 레이아웃을 만듭니다. textview는 서버의 결과를 보여줍니다. 반면에 버튼은 연결을 시작합니다.
활동_메인.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단계: 코드 작성
텍스트 보기에서 서버의 결과를 인쇄하는 도우미 메서드를 만드는 것으로 시작합니다. 이것은 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());
}
}
다음 방법은 웹 소켓 연결을 시작합니다. 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();
}
전체 코드는 다음과 같습니다.
MainActivity.java
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: Okhttp를 사용한 Kotlin Android Websocket 예제
다음은 또 다른 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' 레이아웃에서 아래와 같이 여러 버튼과 편집 텍스트를 추가합니다.
활동_메인.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단계: 웹 소켓 관리자 생성
WebSocketManager.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 " )
}
}
전체 코드는 다음과 같습니다.
웹소켓매니저.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
의 전체 코드입니다.
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. | 팔로우 코드 작성자 |