1. android 多個Activity 共用一個socket
想要讓多個activity共用一個socket(本文使用bluetoothsocket,TCP通信其實也是一樣的,只是socket引入的庫不同而已),網上三個方法,這里只是實現了比較簡單的一種(application實體類)。
第一步實現socket實體類:
第二步:設置AndroidManifest.xml
加入android:name=".Mysocket"
第三步:在第一次創建鏈接時對它初始化:
因為我是藍牙與51單片機進行通信,初始化bluetoothsocket後,再使用((Mysocket)getApplication()).setSocket(socket8051);這句話初始化實體類。
第四步:在其他的activity調用這個socket可以使用
不出意外可以正常運行。
實體類
`hello`
2. Android中Socket編程(二)
LocalServerSocket是屬於Android系統內置的一個類,屬於net包下,類似java中的serversocket類的功能。
這個類比較簡單,構造方法有兩個:
一般使用第一個即可;
本類中方法只有四個,分別是:
查看LocalServerSocket源碼可知:
本類內容比較簡單,主要實現方式都在LocalSocketImpl中,下次分析。
內容基本同LocalScoket,主要功能也是通過LocalSocketImpl實現的。
3. Android-Socket
由於二者不屬於同一層面,所以本來是沒有可比性的。但隨著發展,默認的Http里封裝了下面幾層的使用,所以才會出現Socket & HTTP協議的對比:(主要是工作方式的不同):
Socket可理解為一種特殊的文件,在伺服器和客戶端各自維護一個文件,並使用SocketAPI函數對其進行文件操作。在建立連接打開後,可以向各自文件寫入內容供對方讀取或讀取對方內容,通信結束時關閉文件。在UNIX哲學中「一切皆文件」,文件的操作模式基本為「打開-讀寫-關閉」三大步驟,Socket其實就是這個模式的一個實現。
創建socket的時候,也可以指定不同的參數創建不同的socket描述符,socket函數的三個參數分別為:
當我們調用socket創建一個socket時,返回的socket描述字它存在於協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須調用bind()函數,否則就當調用connect()、listen()時系統會自動隨機分配一個埠。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數的三個參數分別為:
如果作為一個伺服器,在調用socket()、bind()之後就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,伺服器端就會接收到這個請求。
TCP伺服器端依次調用socket()、bind()、listen()之後,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()之後就想TCP伺服器發送了一個連接請求。TCP伺服器監聽到這個請求之後,就會調用accept()函數取接收請求,這樣連接就建立好了。之後就可以開始網路I/O操作了,即類同於普通文件的讀寫I/O操作。
注意:accept的第一個參數為伺服器的socket描述字,是伺服器開始調用socket()函數生成的,稱為監聽socket描述字;而accept函數返回的是已連接的socket描述字。一個伺服器通常通常僅僅只創建一個監聽socket描述字,它在該伺服器的生命周期內一直存在。內核為每個由伺服器進程接受的客戶連接創建了一個已連接socket描述字,當伺服器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。
萬事具備只欠東風,至此伺服器與客戶已經建立好連接了。可以調用網路I/O進行讀寫操作了,即實現了網咯中不同進程之間的通信!網路I/O操作有下面幾組:
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
我推薦使用recvmsg()/sendmsg()函數,這兩個函數是最通用的I/O函數,實際上可以把上面的其它函數都替換成這兩個函數。
從圖中可以看出,當客戶端調用connect時,觸發了連接請求,向伺服器發送了SYN J包,這時connect進入阻塞狀態;伺服器監聽到連接請求,即收到SYN J包,調用accept函數接收請求向客戶端發送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到伺服器的SYN K ,ACK J+1之後,這時connect返回,並對SYN K進行確認;伺服器收到ACK K+1時,accept返回,至此三次握手完畢,連接建立。
總結:客戶端的connect在三次握手的第二個次返回,而伺服器端的accept在三次握手的第三次返回。
某個應用進程首先調用close主動關閉連接,這時TCP發送一個FIN M;
另一端接收到FIN M之後,執行被動關閉,對這個FIN進行確認。它的接收也作為文件結束符傳遞給應用進程,因為FIN的接收意味著應用進程在相應的連接上再也接收不到額外數據;
一段時間之後,接收到文件結束符的應用進程調用close關閉它的socket。這導致它的TCP也發送一個FIN N;
接收到這個FIN的源發送端TCP對它進行確認。
這樣每個方向上都有一個FIN和ACK。
所謂短連接,即連接只保持在數據傳輸過程,請求發起,連接建立,數據返回,連接關閉。它適用於一些實時數據請求,配合輪詢來進行新舊數據的更替。
https://github.com/nuisanceless/MySocketDemo
https://github.com/xuuhaoo/OkSocket
4. android studio連接io.socket:sokect.io-client伺服器的條件
條件是socket協議。
WebSocket是跟隨HTML5一同提出的,所以在兼容性上存在問題,這時一個非常好用的庫就登場了——Socket.io。
socket.io封裝了websocket,同時包含了其它的連接方式,你在任何瀏覽器里都可以使用socket.io來建立非同步的連接。socket.io包含了服務端和客戶端的庫,如果在瀏覽器中使用了socket.io的js,服務端也必須同樣適用。
socket.io是基於Websocket的Client-Server實時通信庫。
socket.io底層是基於engine.io這個庫。engine.io為socket.io提供跨瀏覽器/跨設備的雙向通信的底層庫。engine.io使用了Websocket和XHR方式封裝了一套socket協議。在低版本的瀏覽器中,不支持Websocket,為了兼容使用長輪詢(polling)替代。
5. Android - Socket簡單使用
ServerSocket類提供如下構造器:
當ServerSocket使用完畢,應使用 close() 方法來關閉此ServerSocket。通常情況下,伺服器不應該只接收一個客戶端請求,而應該不斷接收來自客戶端的請求,所以程序可以通過循環,不斷調用ServerSocket的accept方法:
Socket 常用構造器
註:上面兩個構造器指定遠程主機時既可以使用InetAddress來指定,也可以直接使用String對象來指定遠程IP。本地主機只有一個IP地址時,使用第一個方法更簡單。
在與伺服器進行通訊的時候,無法判斷遠程的伺服器是否斷開連接。如果使用 OutputStream 發送數據則會影響正常的數據發送(無法區分)。所以就引入了一個心跳機制。
心跳機制實現,使用 Socket.sendUrgentData() 方法發送一個位元組流數據(緊急數據)。可以通過判斷服務端的 OOBINLINE 屬性是否打開,來確定是否斷開連接;
setSoTimeout()理解 :設置超時時間;例如:設置為2s,如果阻塞的時間>2s ,那麼就會報錯。
6. android做IM 有沒有合適的socket框架
1、可以使用openfire,以前做項目用過,必要時可以修改源碼滿足需求。
2、現在做IM第三方的API有很多。如環信,極推等。
3、Socket socket = new Socket();
socket .connect(new InetSocketAddress(host, port), 20000);
socket .setSoTimeout(45000);
byte[] byte = date;//你要傳輸的數據
BufferedInputStream is = new BufferedInputStream(socket.getInputStream(), 512);
DataInputStream dis = new DataInputStream(is);
out = new BufferedOutputStream(socket.getOutputStream());
out.write(bytes);
服務端寫個socketServer監聽客戶端的請求就可以了
7. Android socket源碼解析(三)socket的connect源碼解析
上一篇文章著重的聊了socket服務端的bind,listen,accpet的邏輯。本文來著重聊聊connect都做了什麼?
如果遇到什麼問題,可以來本文 https://www.jianshu.com/p/da6089fdcfe1 下討論
當服務端一切都准備好了。客戶端就會嘗試的通過 connect 系統調用,嘗試的和服務端建立遠程連接。
首先校驗當前socket中是否有正確的目標地址。然後獲取IP地址和埠調用 connectToAddress 。
在這個方法中,能看到有一個 NetHooks 跟蹤socket的調用,也能看到 BlockGuard 跟蹤了socket的connect調用。因此可以hook這兩個地方跟蹤socket,不過很少用就是了。
核心方法是 socketConnect 方法,這個方法就是調用 IoBridge.connect 方法。同理也會調用到jni中。
能看到也是調用了 connect 系統調用。
文件:/ net / ipv4 / af_inet.c
在這個方法中做的事情如下:
注意 sk_prot 所指向的方法是, tcp_prot 中 connect 所指向的方法,也就是指 tcp_v4_connect .
文件:/ net / ipv4 / tcp_ipv4.c
本質上核心任務有三件:
想要能夠理解下文內容,先要明白什麼是路由表。
路由表分為兩大類:
每個路由器都有一個路由表(RIB)和轉發表 (fib表),路由表用於決策路由,轉發表決策轉發分組。下文會接觸到這兩種表。
這兩個表有什麼區別呢?
網上雖然給了如下的定義:
但實際上在Linux 3.8.1中並沒有明確的區分。整個路由相關的邏輯都是使用了fib轉發表承擔的。
先來看看幾個和FIB轉發表相關的核心結構體:
熟悉Linux命令朋友一定就能認出這裡面大部分的欄位都可以通過route命令查找到。
命令執行結果如下:
在這route命令結果的欄位實際上都對應上了結構體中的欄位含義:
知道路由表的的內容後。再來FIB轉發表的內容。實際上從下面的源碼其實可以得知,路由表的獲取,實際上是先從fib轉發表的路由字典樹獲取到後在同感加工獲得路由表對象。
轉發表的內容就更加簡單
還記得在之前總結的ip地址的結構嗎?
需要進行一次tcp的通信,意味著需要把ip報文准備好。因此需要決定源ip地址和目標IP地址。目標ip地址在之前通過netd查詢到了,此時需要得到本地發送的源ip地址。
然而在實際情況下,往往是面對如下這么情況:公網一個對外的ip地址,而內網會被映射成多個不同內網的ip地址。而這個過程就是通過DDNS動態的在內存中進行更新。
因此 ip_route_connect 實際上就是選擇一個緩存好的,通過DDNS設置好的內網ip地址並找到作為結果返回,將會在之後發送包的時候填入這些存在結果信息。而查詢內網ip地址的過程,可以成為RTNetLink。
在Linux中有一個常用的命令 ifconfig 也可以實現類似增加一個內網ip地址的功能:
比如說為網卡eth0增加一個IPV6的地址。而這個過程實際上就是調用了devinet內核模塊設定好的添加新ip地址方式,並在回調中把該ip地址刷新到內存中。
注意 devinet 和 RTNetLink 嚴格來說不是一個存在同一個模塊。雖然都是使用 rtnl_register 注冊方法到rtnl模塊中:
文件:/ net / ipv4 / devinet.c
文件:/ net / ipv4 / route.c
實際上整個route模塊,是跟著ipv4 內核模塊一起初始化好的。能看到其中就根據不同的rtnl操作符號注冊了對應不同的方法。
整個DDNS的工作流程大體如下:
當然,在tcp三次握手執行之前,需要得到當前的源地址,那麼就需要通過rtnl進行查詢內存中分配的ip。
文件:/ include / net / route.h
這個方法核心就是 __ip_route_output_key .當目的地址或者源地址有其一為空,則會調用 __ip_route_output_key 填充ip地址。目的地址為空說明可能是在回環鏈路中通信,如果源地址為空,那個說明可能往目的地址通信需要填充本地被DDNS分配好的內網地址。
在這個方法中核心還是調用了 flowi4_init_output 進行flowi4結構體的初始化。
文件:/ include / net / flow.h
能看到這個過程把數據中的源地址,目的地址,源地址埠和目的地址埠,協議類型等數據給記錄下來,之後內網ip地址的查詢與更新就會頻繁的和這個結構體進行交互。
能看到實際上 flowi4 是一個用於承載數據的臨時結構體,包含了本次路由操作需要的數據。
執行的事務如下:
想要弄清楚ip路由表的核心邏輯,必須明白路由表的幾個核心的數據結構。當然網上搜索到的和本文很可能大為不同。本文是基於LInux 內核3.1.8.之後的設計幾乎都沿用這一套。
而內核將路由表進行大規模的重新設計,很大一部分的原因是網路環境日益龐大且復雜。需要全新的方式進行優化管理系統中的路由表。
下面是fib_table 路由表所涉及的數據結構:
依次從最外層的結構體介紹:
能看到路由表的存儲實際上通過字典樹的數據結構壓縮實現的。但是和常見的字典樹有點區別,這種特殊的字典樹稱為LC-trie 快速路由查找演算法。
這一篇文章對於快速路由查找演算法的理解寫的很不錯: https://blog.csdn.net/dog250/article/details/6596046
首先理解字典樹:字典樹簡單的來說,就是把一串數據化為二進制格式,根據左0,右1的方式構成的。
如圖下所示:
這個過程用圖來展示,就是沿著字典樹路徑不斷向下讀,比如依次讀取abd節點就能得到00這個數字。依次讀取abeh就能得到010這個數字。
說到底這種方式只是存儲數據的一種方式。而使用數的好處就能很輕易的找到公共前綴,在字典樹中找到公共最大子樹,也就找到了公共前綴。
而LC-trie 則是在這之上做了壓縮優化處理,想要理解這個演算法,必須要明白在 tnode 中存在兩個十分核心的數據:
這負責什麼事情呢?下面就簡單說說整個lc-trie的演算法就能明白了。
當然先來看看方法 __ip_dev_find 是如何查找
文件:/ net / ipv4 / fib_trie.c
整個方法就是通過 tkey_extract_bits 生成tnode中對應的葉子節點所在index,從而通過 tnode_get_child_rcu 拿到tnode節點中index所對應的數組中獲取葉下一級別的tnode或者葉子結點。
其中查找index最為核心方法如上,這個過程,先通過key左移動pos個位,再向右邊移動(32 - bits)演算法找到對應index。
在這里能對路由壓縮演算法有一定的理解即可,本文重點不在這里。當從路由樹中找到了結果就返回 fib_result 結構體。
查詢的結果最為核心的就是 fib_table 路由表,存儲了真正的路由轉發信息
文件:/ net / ipv4 / route.c
這個方法做的事情很簡單,本質上就是想要找到這個路由的下一跳是哪裡?
在這裡面有一個核心的結構體名為 fib_nh_exception 。這個是指fib表中去往目的地址情況下最理想的下一跳的地址。
而這個結構體在上一個方法通過 find_exception 獲得.遍歷從 fib_result 獲取到 fib_nh 結構體中的 nh_exceptions 鏈表。從這鏈表中找到一模一樣的目的地址並返回得到的。
文件:/ net / ipv4 / tcp_output.c