Android NDK開發之旅39--Linux&Android平臺下Socket編程

Android NDK開發之旅 目錄

前言

我們做前端開發主要用http/https請求,這種請求從數據更新角度是單向的,即用戶發起請求才能獲取到最新數據。但有時候,一些狀態和數據的變更要及時推送到前端。例如O2O行業,消費者下定單 -> O2O公司接受到訂單 -> 送外賣小哥即時收到訂單-> 消費者實時收到外賣小哥和自己的距離。
其中,后兩步要即時收到信息,就得利用 Socket編程保持長連接。再比如,消息推送,語音聊天等。

注意:
HTTP也可以建立長連接的,使用Connection:keep-alive,HTTP 1.1默認進行持久連接。HTTP1.1和HTTP1.0相比較而言,最大的區別就是增加了持久連接支持(貌似最新的 http1.0 可以顯示的指定 keep-alive),但還是無狀態的,或者說是不可以信任的。

1、網絡中進程之間如何通信?

本地的進程間通信(IPC)有很多種方式,但可以總結為下面4類:

  • 消息傳遞(管道、FIFO、消息隊列)
  • 同步(互斥量、條件變量、讀寫鎖、文件和寫記錄鎖、信號量)
  • 共享內存(匿名的和具名的)
  • 遠程過程調用(Solaris門和Sun RPC)

網絡中進程之間如何通信?
首要解決的問題是如何唯一標識一個進程,否則通信無從談起!在本地可以通過進程PID來唯一標識一個進程,但是在網絡中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網絡層的“ip地址”可以唯一標識網絡中的主機,而傳輸層的“協議+端口”可以唯一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就可以標識網絡的進程了,網絡中的進程通信就可以利用這個標志與其它進程進行交互。

使用TCP/IP協議的應用程序通常采用應用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通信。
就目前而言,幾乎所有的應用程序都是采用socket,而現在又是網絡時代,網絡中進程通信是無處不在,這就是我為什么說“一切皆socket”。

2. Socket是什么

2.1 socket套接字:

socket起源于Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉).
說白了Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

注意:其實socket也沒有層的概念,它只是一個facade設計模式的應用,讓編程變的更簡單。是一個軟件抽象層。在網絡編程中,我們大量用的都是通過socket實現的。

2.2、套接字描述符

其實就是一個整數,我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、stderr

套接字API最初是作為UNIX操作系統的一部分而開發的,所以套接字API與系統的其他I/O設備集成在一起。特別是,當應用程序要為因特網通信而創建一個套接字(socket)時,操作系統就返回一個小整數作為描述符(descriptor)來標識這個套接字。然后,應用程序以該描述符作為傳遞參數,通過調用函數來完成某種操作(例如通過網絡傳送數據或接收輸入的數據)。

在許多操作系統中,套接字描述符和其他I/O描述符是集成在一起的,所以應用程序可以對文件進行套接字I/O或I/O讀/寫操作。

當應用程序要創建一個套接字時,操作系統就返回一個小整數作為描述符,應用程序則使用這個描述符來引用該套接字需要I/O請求的應用程序請求操作系統打開一個文件。操作系統就創建一個文件描述符提供給應用程序訪問文件。從應用程序的角度看,文件描述符是一個整數,應用程序可以用它來讀寫文件。下圖顯示,操作系統如何把文件描述符實現為一個指針數組,這些指針指向內部數據結構。



對于每個程序系統都有一張單獨的表。精確地講,系統為每個運行的進程維護一張單獨的文件描述符表。當進程打開一個文件時,系統把一個指向此文件內部數據結構的指針寫入文件描述符表,并把該表的索引值返回給調用者 。應用程序只需記住這個描述符,并在以后操作該文件時使用它。操作系統把該描述符作為索引訪問進程描述符表,通過指針找到保存該文件所有的信息的數據結構。

針對套接字的系統數據結構:

1)、套接字API里有個函數socket,它就是用來創建一個套接字。套接字設計的總體思路是,單個系統調用就可以創建任何套接字,因為套接字是相當籠統的。一旦套接字創建后,應用程序還需要調用其他函數來指定具體細節。例如調用socket將創建一個新的描述符條目:


2)、雖然套接字的內部數據結構包含很多字段,但是系統創建套接字后,大多數字字段沒有填寫。應用程序創建套接字后在該套接字可以使用之前,必須調用其他的過程來填充這些字段。

3、基本的socket接口函數


 服務器端先初始化/創建Socket,然后與端口綁定/綁定地址(bind),對端口進行監聽(listen),調用accept阻塞/等待連續,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求并處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。

3.1、socket函數

函數原型:

  int socket(int protofamily, int type, int protocol);

返回值:
  //返回sockfd sockfd是描述符,類似于open函數。

函數功能:

socket函數對應于普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用于創建一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟文件描述字一樣,后續的操作都有用到它,把它作為參數,通過它來進行一些讀寫操作。

函數參數:

protofamily:即協議域,又稱為協議族(family)。常用的協議族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須采用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。

3.2、bind()函數

函數功能:
  bind()函數把一個地址族中的特定地址賦給socket,也可以說是綁定ip端口和socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。

函數原型:

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函數參數:

  1. 函數的三個參數分別為:sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。
  2. addr:一個const struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同,
  3. addrlen:對應的是地址的長度。
3.3、listen()、connect()函數

函數功能:
  如果作為一個服務器,在調用socket()、bind()之后就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,服務器端就會接收到這個請求。
函數原型:

  int listen(int sockfd, int backlog);
  int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函數參數:
  listen函數的第一個參數即為要監聽的socket描述字,第二個參數為相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變為被動類型的,等待客戶的連接請求。
  connect函數的第一個參數即為客戶端的socket描述字,第二參數為服務器的socket地址,第三個參數為socket地址的長度。客戶端通過調用connect函數來建立與TCP服務器的連接。成功返回0,若連接失敗則返回-1。

3.4、accept()函數

函數功能:
  TCP服務器端依次調用socket()、bind()、listen()之后,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()之后就向TCP服務器發送了一個連接請求。TCP服務器監聽到這個請求之后,就會調用accept()函數取接收請求,這樣連接就建立好了。之后就可以開始網絡I/O操作了,即類同于普通文件的讀寫I/O操作。
函數原型:

  int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回連接connect_fd

函數參數:
sockfd:
  參數sockfd就是上面解釋中的監聽套接字,這個套接字用來監聽一個端口,當有一個客戶與服務器連接時,它使用這個一個端口號,而此時這個端口號正與這個套接字關聯。當然客戶不知道套接字這些細節,它只知道一個地址和一個端口號。
addr:
  這是一個結果參數,它用來接受一個返回值,這返回值指定客戶端的地址,當然這個地址是通過某個地址結構來描述的,用戶應該知道這一個什么樣的地址結構。如果對客戶的地址不感興趣,那么可以把這個值設置為NULL。
len:
  如同大家所認為的,它也是結果的參數,用來接受上述addr的結構的大小的,它指明addr結構所占有的字節個數。同樣的,它也可以被設置為NULL。
如果accept成功返回,則服務器與客戶已經正確建立連接了,此時服務器通過accept返回的套接字來完成與客戶的通信。

注意:

accept默認會阻塞進程,直到有一個客戶連接建立后返回,它返回的是一個新可用的套接字,這個套接字是連接套接字。
此時我們需要區分兩種套接字:
  監聽套接字: 監聽套接字正如accept的參數sockfd,它是監聽套接字,在調用listen函數之后,是服務器開始調用socket()函數生成的,稱為監聽socket描述字(監聽套接字)
  連接套接字:一個套接字會從主動連接的套接字變身為一個監聽套接字;而accept函數返回的是已連接socket描述字(一個連接套接字),它代表著一個網絡已經存在的點點連接。
一個服務器通常通常僅僅只創建一個監聽socket描述字,它在該服務器的生命周期內一直存在。內核為每個由服務器進程接受的客戶連接創建了一個已連接socket描述字,當服務器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。
  連接套接字socketfd_new 并沒有占用新的端口與客戶端通信,依然使用的是與監聽套接字socketfd一樣的端口號

3.5、recv()/send()函數

當然也可以使用其他函數來實現數據傳送,比如read和write。

3.5.1 send
 ssize_t send(int sockfd, const void *buf, size_t len, int flags);

不論是客戶還是服務器應用程序都用send函數來向TCP連接的另一端發送數據。

客戶程序一般用send函數向服務器發送請求,而服務器則通常用send函數來向客戶程序發送應答。
  第一個參數指定發送端套接字描述符;
  第二個參數指明一個存放應用程序要發送數據的緩沖區;
  第三個參數指明實際要發送的數據的字節數;
  第四個參數一般置0。

3.5.2 recv
  int recv( SOCKET s,  char FAR *buf, int len, int flags );   

不論是客戶還是服務器應用程序都用recv函數從TCP連接的另一端接收數據。
 該函數的第一個參數指定接收端套接字描述符;
 第二個參數指明一個緩沖區,該緩沖區用來存放recv函數接收到的數據;
 第三個參數指明buf的長度;
 第四個參數一般置0。

3.6、close()函數

函數功能:
  在服務器與客戶端建立連接之后,會進行一些讀寫操作,完成了讀寫操作就要關閉相應的socket描述字,好比操作完打開的文件要調用fclose關閉打開的文件。
函數原型:

#include <unistd.h>
int close(int fd);

close一個TCP socket的缺省行為時把該socket標記為以關閉,然后立即返回到調用進程。該描述字不能再由調用進程使用,也就是說不能再作為read或write的第一個參數。

注意:
close操作只是使相應socket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向服務器發送終止連接請求。

4、Linux下Socket編程實例

我們在Xshell5中,開啟兩個會話,分別用來運行socket_server端、socket_client端。
4.1 編寫socket_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//第一步:導入Socket編程的標準庫
//這個標準庫:linux數據類型(size_t、time_t等等)

#include <sys/types.h>
//提供socket函數以及數據結構
#include <sys/socket.h>

//數據解構(sockaddr_in)
#include <netinet/in.h>
//IP地址的轉換函數
#include <arpa/inet.h>

//定義服務端
#define SERVER_PORT 9999


int main(){
 
 //第二步:創建socket
 //服務端的socket
 int server_socket_fd;
 //客戶端
 int client_socket_fd;
 //服務端網絡地址
 struct sockaddr_in  server_addr;
 //客戶端網絡地址
 struct sockaddr_in client_addr;

 //初始化網絡地址
 //參數一:傳變量的地址($server_addr)
 //參數二:開始為止 
 //參數三:大小
 //初始化服務端網絡地址
 memset(&server_addr,0,sizeof(server_addr ));
 //初始化客戶端網絡地址
 //memset(&client_addr,0,sizeof(client_addr));
 
 //設置服務端網絡地址-協議簇(sin_family)
 //AF_INET:TCP/IP協議、UDP
  //AF_ISO:ISO 協議         
 server_addr.sin_family = AF_INET;

 //設置服務端IP地址(自動獲取系統默認的本機IP,自動分配)
 server_addr.sin_addr.s_addr = INADDR_ANY;
 
 //設置服務端端口
 server_addr.sin_port = htons(SERVER_PORT);
 
 //創建服務端socket 
 //參數一(family):通信域(例如:IPV4->PF_INET、IPV6等等......)
 //參數二(type):通信類型(例如:TCP->SOCK_STREAM,UDP->SOCK_DGRAM等等......)
 //參數三(protocol):指定使用的協議(一般情況下都是默認為0)
 //默認為0就是使用系統默認的協議,系統支持什么我就就用什么
 //TCP->IPPROTO_TCP
 //UDP->IPPROTO_UDP
 //SCTP->IPPROTO_SCTP
  server_socket_fd = socket(PF_INET,SOCK_STREAM,0);

  //判斷是否創建成功
  if(server_socket_fd <0){
     printf("create error!");
     return 1;
  }


  printf("服務器創建成功!\n");

  //服務端綁定地址
  //參數一:服務端socket
  //參數二:網絡地址
  //參數三:數據類型大小
  //socketaddr和sockaddr_in
  bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
  
  //監聽客戶端連接請求(服務端監聽有沒有客戶端連接)
  //參數一:監聽的服務器socket
  //參數二:客戶端數量(未處理隊列數量)
  listen(server_socket_fd,6);

  //接收客戶端連接
  //參數一(sockfd):服務端
  //參數二(addr):客戶端
  //參數三(addrlen):大小
 socklen_t sin_size = sizeof(struct sockaddr_in);
 //獲取一個客戶端
 client_socket_fd= accept(server_socket_fd,(struct sockaddr*)&client_socket_fd,&sin_size);
 //判斷客戶端是否連接成功
 if(client_socket_fd < 0){
     printf("連接失敗");
     return 1;
 }
 //連接成功:讀取客戶端數據
 //BUFSIZ:默認值
 char buffer[BUFSIZ];
 int len=0;
 while(1){
     //參數一:讀取客戶端數據(數據源)
     //參數二:讀取到哪里(我們要讀取到緩沖區buffer)
     //參數三:每次讀取多大BUFSIZ
     //參數四:從哪里開始讀0

     len = recv(client_socket_fd,buffer,BUFSIZ,0);
     if(len > 0){
       //說明讀取到了數據
       printf("%s\n",buffer);

     }
 }
 //關閉服務端和客戶端Socket
 //參數一:關閉的源
 //參數二:關閉的類型(設置權限)
 //SHUT_RD:關閉讀(只允許寫,不允許讀)
 //SHUT_WR:關閉寫(只允許讀,不允許寫)
 //SHUT_RDWR:讀寫都關閉(書寫都不允許)
 shutdown(client_socket_fd,SHUT_RDWR);
 shutdown(server_socket_fd,SHUT_RDWR);
 

 printf("server end.....\n");
 getchar();
 return 0;

}

4.2 執行socket_server.c
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -c socket_server.c
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -o socket_server socket_server.o
root@jdu4e00u53f7:/usr/kpioneer/pthread# ./socket_server
服務器創建成功!
4.3 編寫socket_client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//第一步:導入Socket編程的標準庫
//這個標準庫:linux數據類型(size_t、time_t等等......)
#include<sys/types.h>
//提供socket函數及數據結構
#include<sys/socket.h>
//數據結構(sockaddr_in)
#include<netinet/in.h>
//ip地址的轉換函數
#include<arpa/inet.h>

//定義服務器的端口號
#define SERVER_PORT 9999

int main(){
    
    //客戶端socket
    int client_socket_fd;

    //服務端網絡地址
    struct sockaddr_in  server_addr;
    //客戶端網絡地址
    struct sockaddr_in client_addr;
              
    //初始化網絡地址
    //參數一:傳變量的地址($server_addr)
    //參數二:開始位置 
    //參數三:大小
    //初始化服務端網絡地址
     memset(&server_addr,0,sizeof(server_addr ));
    //AF_INET:TCP/IP協議、UDP
    //AF_ISO:ISO 協議         
     server_addr.sin_family = AF_INET;
                    
    //設置服務端IP地址(自動獲取系統默認的本機IP,自動分配)
    server_addr.sin_addr.s_addr = INADDR_ANY;
                         
     //設置服務端端口
    server_addr.sin_port = htons(SERVER_PORT);
   
    //創建客戶端
    client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
    //判斷是否創建成功
    if(client_socket_fd < 0){
       printf("create error!!!");
       return 1;
    }

    //連接服務器
    //參數一:哪一個客戶端
    //參數二:連接服務器地址
    //參數三:地址大小
    int con_result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(con_result<0){
      printf("connect error!");
    return -1;
    }
     printf("create Socket Client\n ");

    //發送消息(向服務器發送內容)
    char buffer[BUFSIZ] = "Hello, Socket Server!";
    //參數一:指定客戶端
    //參數二:指定緩沖區(沖那里數據讀取)
    //參數三:實際讀取的大小strlen(buffer)(其實讀取到"\0"結束)
    //參數四:從哪里開始讀取
    send(client_socket_fd,buffer,strlen(buffer),0);

    //關閉
    shutdown(client_socket_fd,SHUT_RDWR);
    printf("client--- end-----\n");
   
   return 0; 
}

4.4 執行socket_client.c
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -o socket_client socket_client.o
root@jdu4e00u53f7:/usr/kpioneer/pthread# ./socket_client 
create Socket Client
 client--- end-----
4.5 再次查看socket_server


我們看到服務器創建成功! 下多了一個打印語句Hello, Socket Server! ,程序運行成功。

5、Android下Socket編程實例(jni實現)

我們新建一個Java工程和一個Android工程,分別用來運行socket_server端、socket_client端。
5.1 Java工程端
5.1.1 編寫 SocketServer.java
package com.haocai;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer {

    public static void main(String[] args) {
        try{
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress("192.168.90.221",9998));

            System.out.println("服務器Start...");
            while(true){
                //獲取連接客戶端
                Socket socket = serverSocket.accept();
                //讀取內容
                new ReaderThread(socket).start();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    static class ReaderThread extends Thread{
        BufferedReader bufferedReader;
        public ReaderThread(Socket socket){
            try {
                bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void run() {
            super.run();
            //循環讀取內容
            String content = null;
            while(true){
                try {
                    while((content = bufferedReader.readLine())!=null){
                      System.out.println("接收到了客戶端:"+content);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
5.1.2 運行SocketServer
服務器Start...
5.2 Android工程端
5.2.1 編寫Java jni聲明
package com.haocai.socketclient;

public class SocketUtil {
    public native void startClient(String serverIp,int serverPort);

    static {
        System.loadLibrary("socketlib");
    }

}

5.2.2 編寫socket_client.c
#include"com_haocai_socketclient_SocketUtil.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//第一步:導入Socket編程的標準庫
//這個標準庫:linux數據類型(size_t、time_t等等......)
#include<sys/types.h>
//提供socket函數及數據結構
#include<sys/socket.h>
//數據結構(sockaddr_in)
#include<netinet/in.h>
//ip地址的轉換函數
#include<arpa/inet.h>

#include <android/log.h>


#define  LOG_TAG    "socket_client"
#define  LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,##__VA_ARGS__);
#define  LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,FORMAT,##__VA_ARGS__);
#define  LOGD(FORMAT,...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,FORMAT, ##__VA_ARGS__);

JNIEXPORT void JNICALL Java_com_haocai_socketclient_SocketUtil_startClient
  (JNIEnv *env, jobject jobj, jstring server_ip_jstr, jint server_port){

    const char* server_ip = (*env)->GetStringUTFChars(env, server_ip_jstr, NULL);

    //客戶端socket
    int client_socket_fd;

    //服務端網絡地址
    struct sockaddr_in  server_addr;

    //初始化網絡地址
    //參數一:傳變量的地址($server_addr)
    //參數二:開始位置
    //參數三:大小
    //初始化服務端網絡地址
     memset(&server_addr,0,sizeof(server_addr));
    //AF_INET:TCP/IP協議、UDP
    //AF_ISO:ISO 協議
     server_addr.sin_family = AF_INET;
    //設置服務端IP地址(自動獲取系統默認的本機IP,自動分配)
     server_addr.sin_addr.s_addr = inet_addr(server_ip);

     //設置服務端端口
     server_addr.sin_port = htons(server_port);

    //創建客戶端
    client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
    //判斷是否創建成功
    if(client_socket_fd < 0){

       LOGE("create error!");
       return ;
    }

    //連接服務器
    //參數一:哪一個客戶端
    //參數二:連接服務器地址
    //參數三:地址大小
    int con_result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(con_result<0){
    LOGE("connect error!");

    return ;
    }

    //發送消息(向服務器發送內容)
    char buffer[BUFSIZ] = "Hello Socket Server!";
    //參數一:指定客戶端
    //參數二:指定緩沖區(沖那里數據讀取)
    //參數三:實際讀取的大小strlen(buffer)(其實讀取到"\0"結束)
    //參數四:從哪里開始讀取
    send(client_socket_fd,buffer,strlen(buffer),0);

    //關閉
    shutdown(client_socket_fd,SHUT_RDWR);
    LOGI("client--- end-----");
        (*env)->ReleaseStringUTFChars(env, server_ip_jstr, server_ip);
   return ;

  }
5.2.3 調用主程序MainActivity
package com.haocai.socketclient;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    public static final String SERVER_IP = "192.168.90.221";
    public static final int SERVER_PORT = 9998;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
    }
    public void startSocket(View v){
        new Thread(new Runnable() {

            @Override
            public void run() {
                SocketUtil socketUtil = new SocketUtil();
                socketUtil.startClient(SERVER_IP,SERVER_PORT);
            }
        }).start();
    }
}

5.3 再次查看Java工程Log
接收到了客戶端:Hello Socket Server!
源碼下載
Github:https://github.com/kpioneer123/SocketClient
特別感謝:

guisu--Linux的SOCKET編程詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。