Android客戶端與本地服務器Socket通信
Socket服務器運行結果圖😄
一.客戶端和服務器端的選擇:
- 客戶端是我們手機端,關於服務器端,只要安裝了JDK,自然就擁有通訊的功能,我們只需要在Eclipse或者MyEclipse中寫好文章中服務器端的代碼,運行起來即可,用accept()方法啟動服務器端,等待客戶端的連接,在未連接的情況下,服務器端處於堵塞的狀態。
二.客戶端注意事項
-
andriod客戶端添加網絡訪問權限
<uses-permission android:name="android.permission.INTERNET" />
-
對Socket的操作放在非UI線程內進行
-
要使用正確的IP地址和端口號端口號的范圍是0~65535,1024一下的端口被系統分給了一些服務,在cmd窗口執行
netstat -ano
命令可以看到所有端口的使用情況 -
真機進行調試(1、連接上手機,手機開啟adb。步驟:設置> 應用程序> 開發>選擇USB調試;usb選項有些不可見,具體百度),指定Server的IP地址,此地址為局域網地址,如果是使用WIFI上網,則為PC機的WIFI IP。上圖中連接的第二個就是示例。
三.Socket通信 -
利用ip地址+端口號唯一標示網絡中的一個進程,能夠唯一標示網絡中的進程后,它們就可以利用socket進行通信。
-
socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層復雜的操作抽象為幾個簡單的接口供應用層調用,實現進程在網絡中通信。
-
socket是"打開—讀/寫—關閉"模式的實現(只能讀取對方放在流中的數據),以使用TCP協議通訊的socket為例,其交互流程大概是這樣子的
-
Socket有兩種主要的操作方式:面向連接的和無連接的,即TCP和UDP。
面向連接的Socket操作就像一部電話,Socket必須在發送數據之前與目的地的Socket取得連接,一旦連接建立了,Socket就可以使用一個流接口進行打開、讀寫以及關閉操作。並且,所有發送的數據在另一端都會以相同的順序被接收。 -
無連接的Socket操作就像一個郵件投遞,每一個數據報都是一個獨立的單元,它包含了這次投遞的所有信息(目的地址和要發送的內容)。在這個模式下的Socket不需要連接目的地Socket,它只是簡單的投出數據報。
四.TCP連接與HTTP連接與Socket連接的區別 -
TCP連接與HTTP連接的區別
HTTP是基於TCP的,客戶端往服務端發送一個HTTP請求時第一步就是要建立與服務端的TCP連接。
- TCP連接與Socket連接的區別:
socket層只是在TCP/UDP傳輸層上做的一個抽象接口層,基於TCP協議的socket連接同樣需要通過三次握手建立連接,是可靠的;基於UDP協議的socket連接不需要建立連接的過程,不過對方能不能收到都會發送過去,是不可靠的,大多數的即時通訊IM都是后者。
- HTTP連接與Socket連接的區別
HTTP是短連接,Socket(基於TCP協議的)是長連接。盡管HTTP1.1開始支持持久連接,但仍無法保證始終連接。而Socket連接一旦建立TCP三次握手,除非一方主動斷開,否則連接狀態一直保持。
HTTP連接服務端無法主動發消息。決定二者分別適合應用在什么場景下。HTTP采用“請求-響應”機制,必須滿足客戶端發送消息在前,服務端回復在后。Socket連接雙方類似peer2peer的關系,一方隨時可以向另一方喊話。
- 什么時候該用HTTP,什么時候該用socket
用HTTP的情況:雙方不需要時刻保持連接在線,比如客戶端資源的獲取、文件上傳等。
用Socket的情況:大部分即時通訊應用(QQ、微信)、聊天室、蘋果APNs等。
五.socket代碼
客戶端代碼
public class MainActivity extends AppCompatActivity {
//IP地址和端口號
public static String IP_ADDRESS = "192.168.1.106";
public static int PORT = 2346;
//三個控件
EditText text = null;
Button connect = null;
TextView info = null;
//handler
Handler handler = null;
Socket soc = null;
DataOutputStream dos = null;
DataInputStream dis = null;
String messageRecv = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (EditText) findViewById(R.id.editText);
connect = (Button) findViewById(R.id.buttonConnection);
info = (TextView) findViewById(R.id.info);
connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new ConnectionThread(text.getText().toString()).start();
}
});
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle b = msg.getData(); //獲取消息中的Bundle對象
String str = b.getString("data"); //獲取鍵為data的字符串的值
info.append(str);
}
}; }
//新建一個子線程,實現socket通信
class ConnectionThread extends Thread {
String message = null;
public ConnectionThread(String msg) {
message = msg;
}
@Override
public void run() {
if (soc == null) {
try {
//Log.d("socket","new socket");
soc = new Socket(IP_ADDRESS, PORT);
//獲取socket的輸入輸出流
dis = new DataInputStream(soc.getInputStream());
dos = new DataOutputStream(soc.getOutputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
dos.writeUTF(message);
dos.flush();
messageRecv = dis.readUTF();//如果沒有收到數據,會阻塞
Message msg = new Message();
Bundle b = new Bundle();
b.putString("data", messageRecv);
msg.setData(b);
handler.sendMessage(msg);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
`
服務器端代碼
public class Server {
ServerSocket serverSocket = null;
public final int port = 2346;
public Server(){
//輸出服務器的IP地址
try {
InetAddress addr = InetAddress.getLocalHost();
System.out.println("local host:"+addr);
serverSocket = new ServerSocket(port);
System.out.println("0k");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void startService(){
try {
Socket socket = null;
System.out.println("waiting...");
//等待連接,每建立一個連接,就新建一個線程
while(true){
socket = serverSocket.accept();//等待一個客戶端的連接,在連接之前,此方法是阻塞的
System.out.println("connect to"+socket.getInetAddress()+":"+socket.getLocalPort());
new ConnectThread(socket).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("IOException");
e.printStackTrace();
}
}
//向客戶端發送信息
class ConnectThread extends Thread{
Socket socket = null;
public ConnectThread(Socket socket){
super();
this.socket = socket;
}
@Override
public void run(){
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
while(true){
String msgRecv = dis.readUTF();
System.out.println("msg from client:"+msgRecv);
dos.writeUTF("received:"+msgRecv);
dos.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new Server().startService();
}
}
代碼的邏輯
客戶端 :
-
初始化控件,並綁定監聽器,編寫按鈕的事件處理代碼。
-
在事件處理代碼中開啟子線程,子線程中通過Sockt訪問服務器。
-
利用異步消息處理機制,message對象將子線程的數據傳回handle的處理方法更新UI。
服務端
- serversocket對象監聽等待,利用循環當有客戶訪問就開啟子線程處理傳消息回客戶端。(模擬器和手機同時通過局域網訪問)
最后的廢話
-
搞計算機必須得有理論指導實踐,否則只能像個沒頭蒼蠅到處亂撞,我的計算機網絡知識真的是一言難盡。
-
各大語言的官網是個好東西。豐富的資料和教程簡直讓人沉醉其中不能自拔。雖然比不上網絡小說通俗易懂,但是引人入勝一點也不差。
-
不會的東西太多,用谷歌插件等工具列表,先解決主要的,平時有想法也可以記錄。
-
用博客整理自己的知識,形成體系。看得再多不如編一遍。
解惑
- 關於模擬器通過10.0.2.2訪問本地PC
Android的底層是Linuxkernel,包括Android本身就是一個操作系統,因此,這時我們在模擬器的瀏覽器中輸入的localhost或127.0.0.1所代表的是Android模擬器(Android虛擬機),而不是你的電腦。在Android中,將我們本地電腦的地址映射為10.0.2.2,因此,只需要將原先的localhost或者127.0.0.1換成10.0.2.2,就可以在模擬器上訪問本地計算機上的Web資源了
PS. 集成了很多地方的知識點,官網,博主,就不一一記錄原地址了。
2018-05-13 22:15:53 星期日