برای ایجاد ارتباط بین دو دستگاه، باید هر دو مکانیزم سمت سرور و سمت کلاینت را پیاده سازی کنید زیرا یک دستگاه باید سوکت سرور را باز کند و دیگری باید با استفاده از آدرس MAC دستگاه سرور، اتصال را آغاز کند. دستگاه سرور و دستگاه سرویس گیرنده هر کدام BluetoothSocket
مورد نیاز را به روش های مختلف دریافت می کنند. هنگامی که اتصال ورودی پذیرفته می شود، سرور اطلاعات سوکت را دریافت می کند. وقتی کلاینت یک کانال RFCOMM را به سرور باز می کند، اطلاعات سوکت را ارائه می دهد.
سرور و کلاینت زمانی به یکدیگر متصل تلقی می شوند که هر کدام یک BluetoothSocket
متصل در یک کانال RFCOMM داشته باشند. در این مرحله، هر دستگاه میتواند جریانهای ورودی و خروجی را دریافت کند و انتقال دادهها آغاز شود، که در بخش انتقال دادههای بلوتوث مورد بحث قرار گرفته است. در این بخش نحوه برقراری ارتباط بین دو دستگاه توضیح داده شده است.
مطمئن شوید که مجوزهای بلوتوث مناسب را دارید و قبل از تلاش برای یافتن دستگاههای بلوتوث ، برنامه خود را برای بلوتوث تنظیم کنید .
تکنیک های اتصال
یکی از تکنیک های پیاده سازی این است که به طور خودکار هر دستگاه را به عنوان یک سرور آماده کنید تا هر دستگاه یک سوکت سرور باز داشته باشد و برای اتصالات گوش کند. در این حالت، هر یک از دستگاهها میتوانند با دیگری ارتباط برقرار کرده و مشتری شوند. از طرف دیگر، یک دستگاه می تواند صریحاً اتصال را میزبانی کند و یک سوکت سرور را در صورت تقاضا باز کند، و دستگاه دیگر اتصال را آغاز می کند.
شکل 1. گفتگوی جفت شدن بلوتوث.
به عنوان یک سرور متصل شوید
وقتی می خواهید دو دستگاه را به هم وصل کنید، یکی باید با نگه داشتن یک BluetoothServerSocket
باز به عنوان سرور عمل کند. هدف سوکت سرور گوش دادن به درخواستهای اتصال ورودی و ارائه BluetoothSocket
متصل پس از پذیرش درخواست است. هنگامی که BluetoothSocket
از BluetoothServerSocket
به دست می آید، BluetoothServerSocket
را می توان - و باید - دور انداخت، مگر اینکه بخواهید دستگاه اتصالات بیشتری را بپذیرد.
برای راهاندازی سوکت سرور و پذیرش اتصال، مراحل زیر را انجام دهید:
با تماس
listenUsingRfcommWithServiceRecord(String, UUID)
یکBluetoothServerSocket
دریافت کنید.رشته یک نام قابل شناسایی از سرویس شما است که سیستم به طور خودکار آن را در یک ورودی پایگاه داده جدید سرویس کشف پروتکل (SDP) روی دستگاه می نویسد. نام دلخواه است و به سادگی می تواند نام برنامه شما باشد. شناسه منحصر به فرد جهانی (UUID) نیز در ورودی SDP گنجانده شده است و اساس توافقنامه اتصال با دستگاه مشتری را تشکیل می دهد. یعنی زمانی که مشتری سعی می کند با این دستگاه متصل شود، یک UUID را حمل می کند که به طور منحصر به فرد سرویسی را که می خواهد با آن ارتباط برقرار کند، شناسایی می کند. این UUID ها باید مطابقت داشته باشند تا اتصال پذیرفته شود.
UUID یک فرمت استاندارد ۱۲۸ بیتی برای شناسه رشته ای است که برای شناسایی منحصر به فرد اطلاعات استفاده می شود. یک UUID برای شناسایی اطلاعاتی استفاده می شود که باید در یک سیستم یا یک شبکه منحصر به فرد باشند زیرا احتمال تکرار یک UUID عملاً صفر است. به طور مستقل و بدون استفاده از یک مرجع متمرکز تولید می شود. در این مورد، برای شناسایی منحصر به فرد سرویس بلوتوث برنامه شما استفاده می شود. برای استفاده از یک UUID با برنامه خود، میتوانید از یکی از بسیاری از تولیدکنندگان
UUID
تصادفی در وب استفاده کنید، سپس یک UUID را باfromString(String)
مقداردهی کنید.گوش دادن به درخواست های اتصال را با فراخوانی
accept()
شروع کنید.این یک تماس مسدود کننده است. زمانی که یک اتصال پذیرفته شده باشد یا یک استثنا رخ داده باشد، برمی گردد. اتصال تنها زمانی پذیرفته می شود که یک دستگاه راه دور یک درخواست اتصال حاوی UUID ارسال کرده باشد که با درخواست ثبت شده در این سوکت سرور شنود مطابقت دارد. در صورت موفقیت آمیز بودن،
accept()
یکBluetoothSocket
متصل را برمی گرداند.مگر اینکه بخواهید اتصالات اضافی را بپذیرید، با
close()
تماس بگیرید.این فراخوانی متد، سوکت سرور و تمام منابع آن را آزاد میکند، اما
BluetoothSocket
متصل را که توسطaccept()
برگردانده شده است، نمیبندد. برخلاف TCP/IP، RFCOMM تنها به یک کلاینت متصل در هر کانال اجازه میدهد، بنابراین در بیشتر موارد منطقی است که بلافاصله پس از پذیرش سوکت متصل، درBluetoothServerSocket
close()
فراخوانی کنید.
از آنجایی که accept()
یک تماس مسدودکننده است، آن را در رشته UI اکتیویتی اصلی اجرا نکنید. اجرای آن در یک رشته دیگر تضمین می کند که برنامه شما همچنان می تواند به سایر تعاملات کاربر پاسخ دهد. معمولاً انجام تمام کارهایی که شامل BluetoothServerSocket
یا BluetoothSocket
در رشته جدیدی است که توسط برنامه شما مدیریت می شود، منطقی است. برای لغو یک تماس مسدود شده مانند accept()
، close()
را در BluetoothServerSocket
یا BluetoothSocket
از رشته دیگری فراخوانی کنید. توجه داشته باشید که تمام روشهای موجود در BluetoothServerSocket
یا BluetoothSocket
از نظر موضوعی ایمن هستند.
مثال
موضوع زیر یک رشته ساده شده برای مؤلفه سرور است که اتصالات ورودی را می پذیرد:
کاتلین
private inner class AcceptThread : Thread() { private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) { bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID) } override fun run() { // Keep listening until exception occurs or a socket is returned. var shouldLoop = true while (shouldLoop) { val socket: BluetoothSocket? = try { mmServerSocket?.accept() } catch (e: IOException) { Log.e(TAG, "Socket's accept() method failed", e) shouldLoop = false null } socket?.also { manageMyConnectedSocket(it) mmServerSocket?.close() shouldLoop = false } } } // Closes the connect socket and causes the thread to finish. fun cancel() { try { mmServerSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the connect socket", e) } } }
جاوا
private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // Use a temporary object that is later assigned to mmServerSocket // because mmServerSocket is final. BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code. tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { Log.e(TAG, "Socket's listen() method failed", e); } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned. while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { Log.e(TAG, "Socket's accept() method failed", e); break; } if (socket != null) { // A connection was accepted. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(socket); mmServerSocket.close(); break; } } } // Closes the connect socket and causes the thread to finish. public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { Log.e(TAG, "Could not close the connect socket", e); } } }
در این مثال، تنها یک اتصال ورودی مورد نظر است، بنابراین به محض اینکه یک اتصال پذیرفته شد و BluetoothSocket
به دست آمد، برنامه BluetoothSocket
به دست آمده را به یک رشته جداگانه ارسال می کند، BluetoothServerSocket
می بندد و از حلقه خارج می شود.
توجه داشته باشید که وقتی accept()
BluetoothSocket
را برمی گرداند، سوکت قبلاً متصل است. بنابراین، همانطور که از سمت کلاینت انجام می دهید، نباید connect()
را فراخوانی کنید.
متد manageMyConnectedSocket()
ویژه برنامه برای شروع رشته برای انتقال داده طراحی شده است که در مبحث انتقال داده های بلوتوث بحث شده است.
معمولاً به محض پایان یافتن گوش دادن به اتصالات ورودی، باید BluetoothServerSocket
خود را ببندید. در این مثال، به محض دریافت BluetoothSocket
close()
فراخوانی می شود. همچنین ممکن است بخواهید یک روش عمومی در رشته خود ارائه دهید که می تواند BluetoothSocket
خصوصی را در صورت نیاز به توقف گوش دادن در آن سوکت سرور ببندد.
به عنوان یک مشتری متصل شوید
برای شروع اتصال با یک دستگاه راه دور که اتصالات را در سوکت سرور باز می پذیرد، ابتدا باید یک شئ BluetoothDevice
دریافت کنید که نشان دهنده دستگاه راه دور است. برای یادگیری نحوه ایجاد یک BluetoothDevice
، به یافتن دستگاه های بلوتوث مراجعه کنید. سپس باید از BluetoothDevice
برای دریافت BluetoothSocket
و شروع اتصال استفاده کنید.
روش اصلی به شرح زیر است:
با استفاده از
BluetoothDevice
، با تماس باcreateRfcommSocketToServiceRecord(UUID)
یکBluetoothSocket
دریافت کنید.این روش یک شی
BluetoothSocket
را راه اندازی می کند که به مشتری اجازه می دهد به یکBluetoothDevice
متصل شود. UUID ارسال شده در اینجا باید با UUID استفاده شده توسط دستگاه سرور مطابقت داشته باشد که برای باز کردنBluetoothServerSocket
آنlistenUsingRfcommWithServiceRecord(String, UUID)
فراخوانی کرد. برای استفاده از یک UUID منطبق، رشته UUID را در برنامه خود کدگذاری کنید و سپس از کد سرور و مشتری به آن ارجاع دهید.اتصال را با فراخوانی
connect()
آغاز کنید. توجه داشته باشید که این روش یک تماس مسدود کننده است.پس از اینکه مشتری این روش را فراخوانی کرد، سیستم یک جستجوی SDP را برای یافتن دستگاه راه دور با UUID منطبق انجام می دهد. اگر جستجو موفقیت آمیز باشد و دستگاه راه دور اتصال را بپذیرد، کانال RFCOMM را برای استفاده در طول اتصال به اشتراک می گذارد و متد
connect()
برمی گردد. اگر اتصال ناموفق باشد، یا اگر زمان متدconnect()
(پس از حدود 12 ثانیه) تمام شود، متد یکIOException
را پرتاب میکند.
از آنجایی که connect()
یک تماس مسدود کننده است، شما همیشه باید این روش اتصال را در رشته ای که از رشته فعالیت اصلی (UI) جدا است انجام دهید.
مثال
در زیر یک مثال اساسی از یک رشته کلاینت است که اتصال بلوتوث را آغاز می کند:
کاتلین
private inner class ConnectThread(device: BluetoothDevice) : Thread() { private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) { device.createRfcommSocketToServiceRecord(MY_UUID) } public override fun run() { // Cancel discovery because it otherwise slows down the connection. bluetoothAdapter?.cancelDiscovery() mmSocket?.let { socket -> // Connect to the remote device through the socket. This call blocks // until it succeeds or throws an exception. socket.connect() // The connection attempt succeeded. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(socket) } } // Closes the client socket and causes the thread to finish. fun cancel() { try { mmSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the client socket", e) } } }
جاوا
private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { // Use a temporary object that is later assigned to mmSocket // because mmSocket is final. BluetoothSocket tmp = null; mmDevice = device; try { // Get a BluetoothSocket to connect with the given BluetoothDevice. // MY_UUID is the app's UUID string, also used in the server code. tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { Log.e(TAG, "Socket's create() method failed", e); } mmSocket = tmp; } public void run() { // Cancel discovery because it otherwise slows down the connection. bluetoothAdapter.cancelDiscovery(); try { // Connect to the remote device through the socket. This call blocks // until it succeeds or throws an exception. mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and return. try { mmSocket.close(); } catch (IOException closeException) { Log.e(TAG, "Could not close the client socket", closeException); } return; } // The connection attempt succeeded. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(mmSocket); } // Closes the client socket and causes the thread to finish. public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "Could not close the client socket", e); } } }
توجه داشته باشید که در این قطعه، cancelDiscovery()
قبل از تلاش برای اتصال فراخوانی می شود. همیشه باید cancelDiscovery()
قبل از connect()
فراخوانی کنید، به خصوص به این دلیل که cancelDiscovery()
بدون در نظر گرفتن اینکه کشف دستگاه در حال حاضر در حال انجام است یا خیر موفق می شود. اگر برنامه شما باید تعیین کند که آیا کشف دستگاه در حال انجام است یا خیر، میتوانید با استفاده از isDiscovering()
بررسی کنید.
متد manageMyConnectedSocket()
ویژه برنامه برای شروع رشته برای انتقال داده طراحی شده است که در بخش مربوط به انتقال داده های بلوتوث بحث شده است.
وقتی کارتان با BluetoothSocket
تمام شد، همیشه close()
فراخوانی کنید. با انجام این کار بلافاصله سوکت متصل بسته می شود و تمام منابع داخلی مرتبط آزاد می شوند.