I want to achieve something in Android using Kotlin to do:
If I click a button on the app, the app sends a word to a TCP server (which I wrote with python). The server will send back another word, and the app will show a toast message.
Here is what I have done so far, I can figure out the sending part but I can't manage to make it keep listening to the socket to hear from the server.
I am trying to use coroutine but after finding all the resources online, this is as best as I can get.
Also, I am not sure if I am setting the IP address in the correct manner.
Thank you in advance for your help!
'''
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sendBtn = findViewById<Button>(R.id.sendBtn )
val ipBtn = findViewById<Button>(R.id.ipBtn)
val ipInput = findViewById<TextView>(R.id.ipInput)
var ipAddress: String = "192.168.0.101"
// Below is my attempt to keep listening to the socket, if commented, the sending would work.
// My guess is the IO thread is caught in the while loop so the other coroutines cannot use
// IO thread to send to the server.
CoroutineScope(IO).launch{
val socket = Socket(ipAddress, 9999)
var text = ""
while (true) {
text = BufferedReader(InputStreamReader(socket.inputStream)).readLine()
// if text is not null
// Toast.makeText(this#MainActivity, "Set IP", Toast.LENGTH_SHORT).show()
}
}
suspend fun sendMessage(message:String){
val socket = Socket(ipAddress, 9999)
socket.outputStream.write(message.toByteArray())
socket.close()
}
ipBtn.setOnClickListener {
Toast.makeText(this#MainActivity, "Set IP", Toast.LENGTH_SHORT).show()
ipAddress = ipInput.text.toString()
}
sendBtn .setOnClickListener {
CoroutineScope(IO).launch {
Log.d("TAG", "message")
sendMessage("record")
}
}
'''
To send a Data from P2P Fist we required a Server and Client . Here the Socket act as end point for sending and receiving data across the network .
Create a Server Class like this
class ServerClass() :Thread(){
lateinit var serverSocket:ServerSocket
lateinit var inputStream: InputStream
lateinit var outputStream: OutputStream
lateinit var socket: Socket
override fun run() {
try {
serverSocket = ServerSocket(8888)
socket = serverSocket.accept()
inputStream =socket.getInputStream()
outputStream = socket.getOutputStream()
}catch (ex:IOException){
ex.printStackTrace()
}
val executors = Executors.newSingleThreadExecutor()
val handler = Handler(Looper.getMainLooper())
executors.execute(Runnable{
kotlin.run {
val buffer = ByteArray(1024)
var byte:Int
while (true){
try {
byte = inputStream.read(buffer)
if(byte > 0){
var finalByte = byte
handler.post(Runnable{
kotlin.run {
var tmpMeassage = String(buffer,0,finalByte)
Log.i("Server class","$tmpMeassage")
}
})
}
}catch (ex:IOException){
ex.printStackTrace()
}
}
}
})
}
fun write(byteArray: ByteArray){
try {
Log.i("Server write","$byteArray sending")
outputStream.write(byteArray)
}catch (ex:IOException){
ex.printStackTrace()
}
}
}
Create a client Class where we need to pass hostaddress
class ClientClass(hostAddress: InetAddress): Thread() {
var hostAddress: String = hostAddress.hostAddress
lateinit var inputStream: InputStream
lateinit var outputStream: OutputStream
lateinit var socket: Socket
fun write(byteArray: ByteArray){
try {
outputStream.write(byteArray)
}catch (ex:IOException){
ex.printStackTrace()
}
}
override fun run() {
try {
socket = Socket()
socket.connect(InetSocketAddress(hostAddress,8888),500)
inputStream = socket.getInputStream()
outputStream = socket.getOutputStream()
}catch (ex:IOException){
ex.printStackTrace()
}
val executor = Executors.newSingleThreadExecutor()
var handler =Handler(Looper.getMainLooper())
executor.execute(kotlinx.coroutines.Runnable {
kotlin.run {
val buffer =ByteArray(1024)
var byte:Int
while (true){
try{
byte = inputStream.read(buffer)
if(byte>0){
val finalBytes = byte
handler.post(Runnable{
kotlin.run {
val tmpMeassage = String(buffer,0,finalBytes)
Log.i("client class", tmpMeassage)
}
})
}
}catch (ex:IOException){
ex.printStackTrace()
}
}
}
})
}
}
make sure server and client port should be same , this is two way communication where we can transfer data in both sides .
Related
Hello am new in Android development I was reading about android bluetooth Here on Android development documentation.
I was able to setup bluetooth, find the bonded device and to connect but am having an issue on transfer data between them
Here is the bluetooth server socket code that listen to bluetooth connection request.
class BluetoothActivity : AppCompatActivity() {
private lateinit var listen: Button
private lateinit var msgBox:TextView
private lateinit var status:TextView
private lateinit var sendButton: Button
private lateinit var writeMsg:EditText
private lateinit var listDevice:Button
private lateinit var listView: ListView
private val handler = Handler()
private var bluetoothDevices = arrayListOf<BluetoothDevice>()
private var deviceName = arrayListOf<String>()
private inner class ServerAcceptThread:Thread(){
private val mmServerSocket:BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE){
bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(myName,myUUID)
}
override fun run() {
//Keep listen until error occured or socket is returned
var shouldKeepListen = true
while (shouldKeepListen){
val socket:BluetoothSocket? = try {
mmServerSocket?.accept()
}catch (e:IOException){
Log.e("bluetoothSocket","ServerSocket failde",e)
shouldKeepListen = false
null
}
if (socket!= null){
val connected = ConnectedThread(socket)
connected.start()
}
}
}
//Close server socket and cause the thread to finish
fun cancel(){
try {
mmServerSocket?.close()
}catch (e:IOException){
Log.e("ConnectionFailed!", "Connection close failed",e)
}
}
}
And down below is the code for Bluetooth client that connect to bluetooth server socket.
private inner class ClientConnectThread(device: BluetoothDevice):Thread(){
private val mmSocket:BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE){
device.createRfcommSocketToServiceRecord(myUUID)
}
public override fun run() {
//Cancel the discovery process because it slow down the connection
bluetoothAdapter?.cancelDiscovery()
mmSocket?.let { socket ->
socket.connect()
}
}
fun cancel(){
try {
mmSocket?.close()
}catch (e:IOException){
Log.e("Socket", "Could not close the client socket",e)
}
}
}
And Then I have bluetooth service that read and write data to send to remote device (client). which take BluetoothSocket as parameter, were the server is listening to
private inner class ConnectedThread(private val mmSocket:BluetoothSocket):Thread(){
private val mmInPutStream:InputStream = mmSocket.inputStream
private val mmOutPutStream:OutputStream = mmSocket.outputStream
private val mmBuffer:ByteArray = ByteArray(1024)
override fun run() {
var numByte:Int //number of bytes returns from read()
//keep listen to the InputStream until an error occured
while (true){
//Read from inputStream
numByte = try {
mmInPutStream.read(mmBuffer)
}catch (e:IOException){
Log.e(TAG,"InputStream was disconnected",e)
break
}
//Send the message to Ui activity
val readMsg = handler.obtainMessage(
MESSAGE_READ,numByte,-1,mmBuffer
)
readMsg.sendToTarget()
}
}
//Call this function to mainActivity to send data to remote device
fun write(byte:ByteArray){
try {
mmOutPutStream.write(byte)
}catch (e:IOException){
Log.e(TAG,"Error occured during send messge",e)
//Send the failed message back to activity
val writeErrorMessage = handler.obtainMessage(MESSAGE_TOAST)
val bundle = Bundle().apply {
putString("Toast","could not send the data")
}
writeErrorMessage.data = bundle
handler.sendMessage(writeErrorMessage)
return
}
//Share the sent message with UI activity
val writtenMsg = handler.obtainMessage(
MESSAGE_WRITE, -1,-1,mmBuffer
)
writtenMsg.sendToTarget()
}
//Call this method to activity to shut socket
fun cancle(){
try {
mmSocket.close()
}catch (e:IOException){
Log.e(TAG,"Connection closed failed!")
}
}
}
}
And I have also implement the listener for UI to start listen to a connection request, list Bonded device and connect to remote device and transfer data through each other.
fun implementsListeners(){
listDevice.setOnClickListener {
val pairedDevice: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
var index:Int = 0
val pairedDevice: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
if (pairedDevice != null){
var listDeviceName = arrayListOf<String>()
try {
pairedDevice.forEachIndexed { index, device ->
listDeviceName.add(index, device.name)
bluetoothDevices.add(device)
}
}catch (e:IndexOutOfBoundsException){
Log.e(TAG, "indexOutOfBond",e)
}
val arrayAdapter:ArrayAdapter<String> = ArrayAdapter(
this,android.R.layout.simple_list_item_1,listDeviceName
)
listView.adapter =arrayAdapter
}
listen.setOnClickListener {
val serverClass = ServerAcceptThread()
serverClass.start()
}
listView.setOnItemClickListener { parent, view, position, id ->
val client = ClientConnectThread(bluetoothDevices[position])
client.start()
status.text = "Connecting..."
}
sendButton.setOnClickListener {
val client = BluetoothService(Handler())
//Call the write() method to write data
}
My Question is how can I access the write() method and read() that is on ConnectedThread. I have tried to Instantiate ConnectedThread but it's take BluetoothSocket as parameter I can't access the socket outside client or server class. Method Any help or suggestion on. I would Appreciate
I have difficulties writing an UDP message receive loop for Android.
In the following code, in receiveLoop, the call to receiveMessages never returns and I therefore never enter the message treatment loop.
Note that I am still able to receive packets, but it stops when the channel buffer is full.
I would expect receiveMessages to return immediately, while the blocking IO loop inside it would still run forever.
class MySocketUDP(private val params: SocketParams) {
private val rcvSocket: DatagramSocket by lazy {
val sock = DatagramSocket(params.rcvPort)
sock.reuseAddress = true
sock.soTimeout = 1000
sock
}
suspend fun receiveMessages(channel: SendChannel<Message>) {
withContext(Dispatchers.IO) {
val buf = ByteArray(MAX_MSG_SIZE)
while (true) {
val pkt = DatagramPacket(buf, buf.size)
try {
if (channel.isClosedForSend) {
break
}
rcvSocket.receive(pkt)
val msg = packetToMessage(buf, 0, pkt.length)
Log.d("SOCKET", "filling channel with $msg")
channel.send(msg)
} catch (ex: SocketTimeoutException) {
} catch (ex: CancellationException) {
break
}
}
}
}
}
class MyModel {
private suspend fun receiveLoop(socket: MySocketUDP) {
withContext(Dispatchers.Main) {
val channel = Channel<Message>(16)
socket.receiveMessages(channel)
Log.d("MODEL", "Entering msg loop")
for (msg in channel) {
dispatchRcvMessage(msg)
}
}
}
}
Why does receiveMessages never return while it is running in the IO dispatcher and called from the Main dispatcher?
Do I need to actually spawn a thread to such producer/consumer work?
Can you show how to achieve such long blocking code nicely in a "coroutine-friendly" manner?
Thank you
receiveMessages() is a suspend function which calls another suspend function withContext(), which in turn has an infinite loop. So calling socket.receiveMessages(channel) will suspend code execution while the loop is not finished.
You need to launch separate coroutines for consumer and producer, e.g. using launch function.
Some example of using coroutines:
val someScope = CoroutineScope(Dispatchers.Main)
private suspend fun receiveLoop(socket: MySocketUDP) = someScope.launch {
val channel = Channel<Message>(16)
socket.receiveMessages(channel)
Log.d("MODEL", "Entering msg loop")
for (msg in channel) {
dispatchRcvMessage(msg)
}
}
// In MySocketUDP
suspend fun receiveMessages(channel: SendChannel<Message>) {
someAnotherScope.launch { // or can use coroutineScope builder function
val buf = ByteArray(MAX_MSG_SIZE)
while (true) {
val pkt = DatagramPacket(buf, buf.size)
try {
if (channel.isClosedForSend) {
break
}
rcvSocket.receive(pkt)
val msg = packetToMessage(buf, 0, pkt.length)
Log.d("SOCKET", "filling channel with $msg")
channel.send(msg)
} catch (ex: SocketTimeoutException) {
} catch (ex: CancellationException) {
break
}
}
}
}
I have simple client and server socket application connecting each other based on this code. The problem is i can send and get values but when client getting answer from server continue to send message.
Where is my problem?
here is server : My server socket is in Service.I execute it when service starts
class SocketService: Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
thread { ClientHandler().run() }
return START_STICKY
}
internal inner class ClientHandler() : Runnable{
override fun run() {
val server = ServerSocket(5000)
val client = server.accept()
var reader = BufferedReader(InputStreamReader(client.getInputStream()))
var writer = PrintWriter(client.getOutputStream())
try {
receiveString = reader.readLine()
while(receiveString != "")
{
println(receiveString)
writer.write("hello from server" + "\n")
writer.flush()
}
writer.close();
reader.close();
server.close();
client.close();
} catch (ex: Exception) {
Timber.e("$TAG $formatted $ex")
}
}
}
Here my client :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
connectToPaymentService()
}
fun connectToPaymentService(){
thread { ThreadopenPort().run() }
}
internal inner class ThreadopenPort : Runnable {
override fun run() {
val socket: Socket
try {
socket = Socket(SERVER_IP, SERVER_PORT)
output = PrintWriter(socket.getOutputStream())
input = BufferedReader(InputStreamReader(socket.getInputStream()))
rMessage = input!!.readLine()
println(rMessage)
while(rMessage != ""){
output!!.write("hello from client" + "\n")
output!!.flush()
rMessage = input!!.readLine()
}
output!!.close();
input!!.close();
socket.close();
} catch (e: Exception) {
e.printStackTrace()
}
}
}
#saulyasar -
If I gave you an example, it would be in Java, not Kotlin ;)
Stylistically, you should prefer "use()" to explicit "close()". It won't help your immediate problem - but it's a good habit :)
Your problem is: while(receiveString != ""). You loop ... but "receiveString" is never modified. So the loop never terminates. Whoops!
SUGGESTED ALTERNATIVE:
Assignment not allowed in while expression?
BufferedReader(reader).use { r ->
r.lineSequence().forEach {
println(it)
}
}
I believe this idiom can be successfully applied to your socket "read" loop.
Please post back what you find :)
What I trying to do is listen to socket data and convert into an observable string that my UI can Subscribe this event and do Change on UI
So far I created a class SocketConnection maintain in dagger connection happen properly and received data and able to do with interface correctly, but want to apply with rxkotlin.
Using Socket.io,kotlin
SocketConnection class
class SocketConnection : SocketStreamListener {
private var socket: Socket? = null
var responseSocket :ResponseHandler?= null
companion object {
var instance = SocketConnection()
}
override fun createSocket(socketQuery: SocketQuery): Socket? {
try {
val okHttpClient = UnsafeOkHttpClient.getUnsafeOkHttpClient()
IO.setDefaultOkHttpWebSocketFactory(okHttpClient)
IO.setDefaultOkHttpCallFactory(okHttpClient)
val opts = IO.Options()
opts.reconnection = false
opts.callFactory = okHttpClient
opts.webSocketFactory = okHttpClient
opts.query = "userID=" + socketQuery.userID + "&token=" + socketQuery.token
socket = IO.socket(CommonContents.BASE_API_LAYER, opts)
L.d("Socket object created")
} catch (e: URISyntaxException) {
L.e("Error creating socket", e)
}
return socket
}
override fun createSocketListener(socket: Socket) {
L.d("inside the socket Listner")
socket.connect()?.on(Socket.EVENT_CONNECT, {
L.d("connected")
listenSocketEvents()
//socketDataListener()
createMessageListener()
})?.on(Socket.EVENT_DISCONNECT,
{
L.d("disconnected")
return#on
})
}
/**
* function used to listen a socket chanel data
*/
private fun listenSocketEvents() {
/* socket?.on("1502", { args ->
// This Will Work
L.d("Socket market depth event successfully")
val socketData = args[0] as String
L.d(socketData)
// instance.data = Observable.just(socketData)
//data!!.doOnNext({ socketData })
*//*
data = args[0] as String
for (i in 0 until arr.size) {
arr[i].socketStreamingData(data)
}*//*
})*/
}
// This Will Not Work
fun socketDataListener(): Observable<String>{
return Observable.create({
subscibe ->
// L.d("Socket market depth event successfully")
socket?.on("1502", { args ->
L.d("Socket market depth event successfully")
val socketData = args[0] as String
subscibe.onNext(socketData)
})
})
}
}
Repository
fun getSocketData(): Observable<String> {
// L.e("" + SocketConnection.instance.socketDataListener())
return SocketConnection.instance.createMessageListener()
}
ViewModel
fun getSocketData(): Observable<String>{
return groupRepository.getSocketData()
}
OnFragement (UI)
private fun getSocketUpdate(){
subscribe(watchlistViewModel.getSocketData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
L.d("SocketData : " + it.count())
}, {
L.e("Error")
}))
}
In this UI using disposable subscribe method into base class.
Please let me know what i doing wrong thanx in advance
Instead of creating an Observable every time a message is sent, I suggest using a Subject for that, since it has a similar "nature" as the Socket connection.
val subject = PublishSubject.create<String>()
...
fun listenSocketEvents() {
socket?.on("1502") { args ->
val socketData = args[0] as String
subject.onNext(socketData)
}
}
fun observable(): Observable<String>{
return subject
}
You can then listen to the changes on the subject via (repository layer etc not included, you'd have to do that yourself)
private fun getSocketUpdate() {
disposable = socketConnection.observable()
.subscribeOn(Schedulers.io())
.observeOn(...)
.subscribe({...}, {...})
}
As a side note, your singleton instance is not how you'd do that in kotlin.
Instead of having an instance field in a companion object, you should make the declare the class as object SocketConnection.
This will automatically give you all singleton features. (I do not know whether it is smart to use a singleton with socket.io, but I assume that you know what you're doing :-) )
I want to Transfer file with tcp client to server, but image file has been wrong.
My client code is
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream
import org.msgpack.core.MessageBufferPacker
import org.msgpack.core.MessagePack
import org.msgpack.core.MessageUnpacker
import java.io.*
import java.net.Socket
import java.util.*
fun main(args: Array<String>) {
fileClient("localhost",1988,"fruit.jpg")
}
class fileClient (host:String, port:Int, file:String){
var s : Socket ?= null
var out = ByteArrayOutputStream()
var msg : MessageBufferPacker = MessagePack.newDefaultBufferPacker()
init {
try {
s = Socket(host,port)
sendFile(file)
}catch (e:Exception){
e.printStackTrace()
}
}
#Throws(IOException::class)
fun sendFile(file: String) {
val dos = DataOutputStream(s!!.getOutputStream())
val buffer = ByteArray(4096)
val filebytes = File(file).readBytes()
var msgdata = ByteOutputStream()
msg.packString(file)
msg.packBinaryHeader(filebytes.size)
msg.writePayload(filebytes)
msg.close()
val data = msg.toByteArray()
val datasize = data.size
val ins = ByteArrayInputStream(data)
dos.writeInt(datasize)
while (ins.read(buffer) > 0) {
dos.write(buffer)
}
dos.close()
}
}
And my server code is
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream
import org.msgpack.core.MessagePack
import org.msgpack.core.MessageUnpacker
import java.awt.List
import java.io.*
import java.net.ServerSocket
import java.net.Socket
import java.text.SimpleDateFormat
import java.util.*
fun main(args: Array<String>) {
var fs = FileServer(1988)
fs.start()
}
class FileServer(port: Int) : Thread() {
private var ss: ServerSocket? = null
var fileRealName : String ?= null
init {
try {
ss = ServerSocket(port)
} catch (e: IOException) {
e.printStackTrace()
}
}
override fun run() {
while (true) {
try {
val clientSock = ss!!.accept()
saveFile(clientSock)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
#Throws(IOException::class)
private fun saveFile(clientSock: Socket) {
var msgList = ArrayList<Any>()
val dis = DataInputStream(clientSock.inputStream)
val msgdata = ByteOutputStream()
val buffer = ByteArray(4096)
var read = 0
while (true) {
val datalen = dis.readInt() // data length
if(datalen!= null && datalen >0){
var finaldata = ByteArray(datalen)
var process = 0;
while (process <= datalen) {
read = dis.read(buffer)
if (read < 0) {
return
}
msgdata.write(buffer)
process += 4096
}
println(process.toString() + " "+ datalen.toString())
var allData = msgdata.toByteArray().slice(0..datalen).toByteArray()
unpackByte(allData)
}
}
msgdata.close()
dis.close()
}
private fun unpackByte(data:ByteArray){
var unpacker : MessageUnpacker = MessagePack.newDefaultUnpacker(data)
var fileName = unpacker.unpackString().toString()
var filesize = unpacker.unpackBinaryHeader()
var buffer = ByteArray(filesize)
unpacker.readPayload(buffer)
var fos = FileOutputStream(fileName)
fos.write(buffer)
fos.close()
unpacker.close()
}
}
And a file what I want to transfer is
but after transfer, image on server is like this.
How can I transfer this file correctly?
I didn't succeeded in reproducing your problem. However I think I've found the bug that may be causing corrupted file transfer.
In your server code there's an infinite loop that returns immediately out of method leaving the rest of method unreachable. This is the clean-up code that closes connections and streams. Quite possibly the OutputStream was not properly closed and this is the cause of corrupted file write.
That's how the server code should look like:
val datalen = dis.readInt() // data length
if (datalen > 0) {
var finaldata = ByteArray(datalen)
var process = 0;
while (process <= datalen) {
read = dis.read(buffer)
if (read < 0) {
break
}
msgdata.write(buffer)
process += 4096
}
println(process.toString() + " " + datalen.toString())
val allData = msgdata.toByteArray().slice(0..datalen).toByteArray()
unpackByte(allData)
}
msgdata.close()
dis.close()
while loop is unnecessary. Also you probably should just break the loop, not return from function.
P.S.
Have you considered using IOUtils to handle all the IO read/writes? Half of your code could be replaced with just a few lines of code using this library.