전 회에 이어
Wifi Aware를 이용한 DiscoverySession으로 연결된 network를 바탕으로 Socket 연결을 만들어 두 Anrdroid 디바이스 간의 고속의 안정적인 연결을 만들어 내는 과정이다
Developer Page에 나와 있는 내용을 따라 가보자
1. Server side OnMessageReceived 수정
이전회 까지의 연결에서 Subscriber로 부터의 message가 오면 onMessageReceived() 가 호출 되고
override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
// peerHandled을 set
viewModel.setPeerHandle(peerHandle)
// 이제 이것은 필요 없긴 하다
val receivedMessage = String(message, Charsets.UTF_8)
Log.i(">>>>", "onMessageReceived...$peerHandle, $receivedMessage")
val strDispay = bindMain.tvChattingArea.text.toString() + "\n" + receivedMessage
bindMain.tvChattingArea.text = strDispay
// serverSocketThread가 없으면 server socket 활성화를 시작한다
if(viewModel.serverSocketThread.value != null) initServerSocket()
// client에 message를 보내고 client는 아래 message를 받으면 ClientSocket을 만든다
sendMessageViaSession("hello from server")
}
peerHandle 을 Global 변수에 set 한 후 ServerSocket을 준비 한다
한편으로는 client(subscriber) 에 session을 이용해 message를 보내고
이 메시지를 받은 client는 onMessageReceived()에서 Socket을 만들고 sever socket에 connect 할 수 있게 된다.
2. ServerSocketThread 준비
일반적인 socket 연결 에서와 비슷한 Thread를 준비 했다
port 번호는 8888 로 임의로 설정했다
class ServerSocketThread(private val context: Context) : Thread(){
private var serverSocket: ServerSocket? = null
private var outputStream: OutputStream? = null
private var inputStream : InputStream? = null
private var isRunning = true
override fun run() {
Log.i(">>>>", "ServerSocketTrhead Thread Started")
try {
// 포트는 임의로 8888로 했다
serverSocket = ServerSocket(8888)
serverSocket?.also { serverSocket1 ->
val clientSocket : Socket? = serverSocket1.accept()
Log.i(">>>>" , "server socket ; Accepted clientSocket = $clientSocket")
clientSocket?.also {
inputStream = it.getInputStream()
outputStream = it.getOutputStream()
sendMessage("hello from server through socket")
while(isRunning){
val buffer = ByteArray(1024)
val bytesRead = inputStream?.read(buffer)
if (bytesRead != null && bytesRead > 0) {
val receivedMessage = String(buffer, 0, bytesRead)
Log.d(">>>>", "ReceivedMessage : $receivedMessage")
// 종료 메시지
if(receivedMessage == "quit") isRunning = false
}
}
outputStream?.close()
inputStream?.close()
clientSocket.close()
}
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
serverSocket?.close()
}
Log.i(">>>>", "Server Thread terminating...")
}
fun sendMessage(message: String): Unit {
outputStream?.write(message.toByteArray())
}
}
3. Network 생성 및 Callback 등록
ConnectivityManager.requestNetworkCallback()을 이용해 Network를 생성하고 Callback 을 등록
onAvailable() callback 에서 ServerSocketThread를 실행시킴
class MainActivity : AppCompatActivity() {
..........
private lateinit var connectivityManager : ConnectivityManager
private var serverSocketThread : ServerSocketThread? = null
private var clientSocketThread : ClientSocketThread? = null
............
@RequiresApi(Build.VERSION_CODES.S)
override fun onCreate(savedInstanceState: Bundle?) {
..............
connectivityManager= getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
initViewModel()
initUIListener()
initWifiAwareManager()
checkPermission()
}
@SuppressLint("MissingPermission")
fun setWifiAwareSession(wifiAwareSession: WifiAwareSession?){
.........
if(asServer!!) {
............
viewModel.wifiAwareSession.value?.publish(config, object : DiscoverySessionCallback() {
...............
@RequiresApi(Build.VERSION_CODES.Q)
override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
viewModel.setPeerHandle(peerHandle)
//이부분은 필요 없긴 하다 이제
val receivedMessage = String(message, Charsets.UTF_8)
Log.i(">>>>", "onMessageReceived...$peerHandle, $receivedMessage")
val strDispay = bindMain.tvChattingArea.text.toString() + "\n" + receivedMessage
bindMain.tvChattingArea.text = strDispay
// serverSocketThread가 null 이면 serverSocket을 만들고 실행
if(serverSocketThread == null) initServerSocket()
// client는 아래 message를 받으면 ClientSocket을 만든다
sendMessageViaSession("hello from server")
}
..............
}, null)
} else {
.........
}
}
@RequiresApi(Build.VERSION_CODES.Q)
fun initServerSocket(){
if(asServer == null || asServer==false) return
Log.i(">>>>", "init serversocket")
if(viewModel.publishDiscoverySession.value == null
|| viewModel.peerHandle.value == null) return
// WifiAwareNetworkSpecifier 생성
val networkSpecifier = WifiAwareNetworkSpecifier.Builder(
viewModel.publishDiscoverySession.value!!,
viewModel.peerHandle.value!!)
.setPskPassphrase("12340987")
.build()
Log.i(">>>>", "init serversocket $networkSpecifier")
// WifiAware 를 이용 하는 NetworkRequest 생성
val myNetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(networkSpecifier)
.build()
Log.i(">>>>", "init serversocket $myNetworkRequest")
// 콜백 만들고 등록
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.i(">>>>", "NetworkCallback onAvailable")
Toast.makeText(this@MainActivity, "Socket network availabe", Toast.LENGTH_LONG).show()
// ServerSocketThread가 만들어 져 있지 않으면
// ServerSocketThread를 만들고 시작시킴
try{
if(serverSocketThread == null) {
serverSocketThread = ServerSocketThread(this@MainActivity)
serverSocketThread?.start()
}
} catch ( e : Exception){
Log.e(">>>>", "starting socket thread exception : ${e.message}")
}
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
Log.i(">>>>", "NetworkCallback onCapabilitiesChanged network : $network")
Log.i(">>>>", "NetworkCapabilities : $networkCapabilities")
}
override fun onLost(network: Network) {
super.onLost(network)
Log.i(">>>>", "NetworkCallback onLost")
}
}
connectivityManager.requestNetwork(myNetworkRequest, networkCallback)
}
.............
}
4. Client Side의 onMessageReceived() 수정
위의 과정중 server에서 ServerSocket 시작 후 Session을 이용해 메시지를 보내게 되고
onMessageReceived()에서 clientSocket을 준비한다
@SuppressLint("MissingPermission")
fun setWifiAwareSession(wifiAwareSession: WifiAwareSession?){
...........
if(asServer!!) {
.........
} else {
.........
viewModel.wifiAwareSession.value?.subscribe(config, object : DiscoverySessionCallback() {
.............
@RequiresApi(Build.VERSION_CODES.Q)
override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
viewModel.setPeerHandle(peerHandle)
//마찬가지 이제 필요 없다
val receivedMessage = String(message, Charsets.UTF_8)
Log.i(">>>>", "onMessageReceived...$peerHandle, $receivedMessage")
val strDisplay = bindMain.tvChattingArea.text.toString() + "\n" + receivedMessage
bindMain.tvChattingArea.text = strDisplay
// clientSocketThread가 없으면 실행 시킨다
if(clientSocketThread ==null) connectToServerSocket()
}
............
}, null)
}
}
5. ClientSocketThread 준비
일반적인 client 용 socket thread 와 비슷하다
class ClientSocketThread(val context: Context, private val host : InetSocketAddress) : Thread() {
private lateinit var socket : Socket
private var outputStream: OutputStream? = null
private var inputStream : InputStream? = null
private var isRunning = true
override fun run() {
Log.i(">>>>", "Client Thread Started")
try {
socket = Socket()
socket.connect(host, 10000)
Log.i(">>>>" , "client socket ; connected to server = $socket")
outputStream = socket.getOutputStream()
inputStream = socket.getInputStream()
// Send message to the server (group owner)
sendMessage("hello from client through socket")
while(isRunning){
val buffer = ByteArray(1024)
val bytesRead = inputStream?.read(buffer)
if (bytesRead != null && bytesRead > 0) {
val receivedMessage = String(buffer, 0, bytesRead)
// Handle the received message
Log.d(">>>>", "ReceivedMessage : $receivedMessage")
//탈출구
if(receivedMessage == "quit") isRunning = false
}
}
outputStream?.close()
inputStream?.close()
socket.close()
} catch (e: SocketTimeoutException) {
// Handle timeout exception
e.printStackTrace()
} catch (e: Exception) {
// Handle other exceptions
e.printStackTrace()
}
Log.i(">>>>", "Client Thread terminating...")
}
fun sendMessage(message: String): Unit {
val strMessage : String = "" + message
outputStream?.write(strMessage.toByteArray())
}
}
6. connectToServerSocket() 구현
서버의 경우와 내용이 거의 비슷하긴 하지만 이번에는
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) Callback 이 실행 되어야 비로소 Network 정보(server의 IPV6 Address 포함) 를 알게 되고
그것을 이용해 InetSocketAddress를 만들어 ClientSocketThread를 만들고 실행시킨다, 포트는 서버와 같은 포트를 설정
@RequiresApi(Build.VERSION_CODES.Q)
fun connectToServerSocket(){
if(asServer == null || asServer==true) return
Log.i(">>>>", "init client socket")
if(viewModel.subscribeDiscoverySession.value == null
|| viewModel.peerHandle.value == null) return
// NetworkSpectifer를 만들고
val networkSpecifier = WifiAwareNetworkSpecifier.Builder(
viewModel.subscribeDiscoverySession.value!!,
viewModel.peerHandle.value!!
)
.setPskPassphrase("12340987")
.build()
Log.i(">>>>", "connecting to server socket $networkSpecifier")
// NetworkRequest를 만든 후
val myNetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(networkSpecifier)
.build()
Log.i(">>>>", "connecting to server socket $myNetworkRequest")
// 해당 Callback을 만들고
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.i(">>>>", "NetworkCallback onAvailable")
Toast.makeText(this@MainActivity, "Socket network availabe", Toast.LENGTH_LONG)
.show()
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
Log.i(">>>>", "NetworkCallback onCapabilitiesChanged network : $network")
Log.i(">>>>", "NetworkCapabilities : $networkCapabilities")
val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo
val peerIpv6 = peerAwareInfo.peerIpv6Addr
if(clientSocketThread == null){
try{
clientSocketThread = ClientSocketThread(this@MainActivity, InetSocketAddress(peerIpv6, 8888))
clientSocketThread?.start()
} catch(e : Exception){
Log.e(">>>>", "clientSocket : ${e.message}")
}
}
}
override fun onLost(network: Network) {
super.onLost(network)
Log.i(">>>>", "NetworkCallback onLost")
}
}
// connectivityManager를 이용해 등록하고 실행
connectivityManager.requestNetwork(myNetworkRequest,
networkCallback as ConnectivityManager.NetworkCallback
)
}
7. 종료시에는
위의 Socket connection이 종료 되어도 WifiAware Network는 살아 있어서, 현 상태에서 다시 Socket Connection을 연결 할 수도 있다. Socket network connection이 종료되면 connectivityManager.unregisterNetworkCallback(netwokrCallback) 을 이용해 현재 등록된 networkCallback을 해제 해 주어야 한다. 해제를 안하고 다시 netwokrkCallback을 다시 만들어서 등록을 하니 등록이 안 되는 것 같았다.
8 . 실행 결과
Log를 보면 Socket이 잘 연결되고
양 측 Thread가 정상적으로 시작 되었다. 현재는 Disconnect로 끝 내는 수 밖에 없고, 다음에 메시지 보내는 기능을 추가 하면 "quit" message로 종료 시킬 수 있다
onCapabilitiesChanged, 나 onAvailable이 여러번 호출 되기도 해서 현재 Thread가 생성 되어 있는지 확인 하는 것은 꼭 추가 되어 있어야 할 듯 하다.
1) server side
2024-03-25 00:35:34.512 21097-22356 >>>> com.example.wifi_aware_test I NetworkCallback onAvailable
2024-03-25 00:35:34.548 21097-22356 >>>> com.example.wifi_aware_test I NetworkCallback onCapabilitiesChanged network : 718
2024-03-25 00:35:34.548 21097-22418 >>>> com.example.wifi_aware_test I ServerSocketTrhead Thread Started
2024-03-25 00:35:34.549 21097-22356 >>>> com.example.wifi_aware_test I NetworkCapabilities : [ Transports: WIFI_AWARE Capabilities: NOT_METERED&NOT_RESTRICTED&TRUSTED&NOT_VPN&NOT_ROAMING&FOREGROUND&NOT_CONGESTED&NOT_SUSPENDED LinkUpBandwidth>=1Kbps LinkDnBandwidth>=1Kbps TransportInfo: <AwareNetworkInfo: IPv6=/fe80::87:56ff:fec7:c02a%aware_data0, port=0, transportProtocol=-1> SignalStrength: 1]
2024-03-25 00:35:34.552 21097-22356 >>>> com.example.wifi_aware_test I NetworkCallback onCapabilitiesChanged network : 718
2024-03-25 00:35:34.552 21097-22356 >>>> com.example.wifi_aware_test I NetworkCapabilities : [ Transports: WIFI_AWARE Capabilities: NOT_METERED&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED&NOT
2024-03-25 00:35:35.561 21097-22418 >>>> com.example.wifi_aware_test I server socket ; Accepted clientSocket = Socket[address=/fe80::87:56ff:fec7:c02a%aware_data0,port=49654,localPort=8888]
2024-03-25 00:35:35.563 21097-22418 >>>> com.example.wifi_aware_test D ReceivedMessage : hello from client through socket
2) client side
2024-03-25 00:31:15.208 21097-22356 >>>> com.example.wifi_aware_test I NetworkCallback onAvailable
2024-03-25 00:31:15.255 21097-22358 >>>> com.example.wifi_aware_test I Client Thread Started
2024-03-25 00:31:15.257 21097-22358 TcpOptimizer com.example.wifi_aware_test D TcpOptimizer-ON
2024-03-25 00:31:15.261 21097-22356 >>>> com.example.wifi_aware_test I NetworkCallback onCapabilitiesChanged network : 717
2024-03-25 00:31:15.261 21097-22356 >>>> com.example.wifi_aware_test I NetworkCapabilities : [ Transports: WIFI_AWARE Capabilities: NOT_METERED&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDAT
2024-03-25 00:31:16.279 21097-22358 >>>> com.example.wifi_aware_test I client socket ; connected to server = Socket[address=/fe80::c3:4dff:febf:b76c%aware_data0,port=8888,localPort=38700]
2024-03-25 00:31:16.283 21097-22358 >>>> com.example.wifi_aware_test D ReceivedMessage : hello from server through socket
8 다음에는
전송 button을 활성화 시켜서 서로 메시지를 보내고, UI에 message를 나타내도록 추가해 보자