【socket】- 客戶端源碼分析

簡介

網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。
建立網(wǎng)絡(luò)通信連接至少要一對端口號(socket)。socket本質(zhì)是編程接口(API),對TCP/IP的封裝峻仇,TCP/IP也要提供可供程序員做網(wǎng)絡(luò)開發(fā)所用的接口,這就是Socket編程接口邑商;HTTP是轎車摄咆,提供了封裝或者顯示數(shù)據(jù)的具體形式;Socket是發(fā)動(dòng)機(jī)人断,提供了網(wǎng)絡(luò)通信的能力吭从。

構(gòu)造體

  • Socket()

    void setImpl() {
        if (factory != null) {
            impl = factory.createSocketImpl();
            checkOldImpl();
        } else {
            impl = new SocksSocketImpl();
        }
        if (impl != null)
            impl.setSocket(this);
    }
    
    1. factory
      創(chuàng)建Socket具體實(shí)現(xiàn)的工廠對象,調(diào)用setSocketImplFactory方法進(jìn)行設(shè)置恶迈,注意該變量只允許設(shè)置一次影锈,設(shè)置多次會導(dǎo)致異常。一般情況是不需要設(shè)置的蝉绷,這里將會使用默認(rèn)的實(shí)現(xiàn)類SocksSocketImpl。里面保存了服務(wù)器的地址(server)和端口號(serverPort)枣抱。
  • Socket(Proxy proxy)
    創(chuàng)建具有代理功能的Socket對象熔吗。

    1. Proxy
      包含了兩個(gè)屬性,Type和SocketAddress佳晶。Type有三種類型桅狠,分別是:

      • DIRECT:表示直接連接或缺省代理
      • HTTP:高級協(xié)議(如HTTP或FTP)的代理
      • SOCKS:表示SOCKS(V4或V5)代理。

      SocketAddress具體實(shí)現(xiàn)類是InetSocketAddress轿秧,存儲地址相關(guān)的變量和地址相關(guān)的操作中跌。里面存儲了三個(gè)屬性值。分別是:

      • hostname:Socket地址的主機(jī)名
      • addr:Socket地址的IP地址
      • port:Socket地址的端口號

      addr是InetAddress對象菇篡,具體實(shí)現(xiàn)類有Inet4Address和Inet6Address漩符,InetAddress存儲了4個(gè)屬性值,其中family指定地址族類型驱还,例如嗜暴,IPv4地址是AF_INET和IPv6地址是AF_INET6凸克。

綁定(bind)

無連接的socket的客戶端和服務(wù)端以及面向連接socket的服務(wù)端通過調(diào)用bind函數(shù)來配置本地信息。使用bind函數(shù)時(shí)闷沥,通過將my_addr.sin_port置為0萎战,函數(shù)會自動(dòng)為你選擇一個(gè)未占用的端口來使用。
Bind()函數(shù)在成功被調(diào)用時(shí)返回0舆逃;出現(xiàn)錯(cuò)誤時(shí)返回"-1"并將errno置為相應(yīng)的錯(cuò)誤號蚂维。需要注意的是,在調(diào)用bind函數(shù)時(shí)一般不要將端口號置為小于1024的值路狮,因?yàn)?到1024是保留端口號虫啥,你可以選擇大于1024中的任何一個(gè)沒有被占用的端口號。

有連接的socket客戶端通過調(diào)用Connect函數(shù)在socket數(shù)據(jù)結(jié)構(gòu)中保存本地和遠(yuǎn)端信息览祖,無須調(diào)用bind()孝鹊,因?yàn)檫@種情況下只需知道目的機(jī)器的IP地址,而客戶通過哪個(gè)端口與服務(wù)器建立連接并不需要關(guān)心展蒂,socket執(zhí)行體為你的程序自動(dòng)選擇一個(gè)未被占用的端口又活,并通知你的程序數(shù)據(jù)什么時(shí)候打開端口。(當(dāng)然也有特殊情況锰悼,linux系統(tǒng)中rlogin命令應(yīng)當(dāng)調(diào)用bind函數(shù)綁定一個(gè)未用的保留端口號柳骄,還有當(dāng)客戶端需要用指定的網(wǎng)絡(luò)設(shè)備接口和端口號進(jìn)行通信等等)
總之:

  1. 需要在建連前就知道端口的話,需要 bind
  2. 需要通過指定的端口來通訊的話箕般,需要 bind

連接(connect)

public void connect(SocketAddress endpoint, int timeout) throws IOException {
   if (!created)
        createImpl(true);
   if (!oldImpl)
        impl.connect(epoint, timeout);
   else if (timeout == 0) {
       if (epoint.isUnresolved())
           impl.connect(addr.getHostName(), port);
       else
           impl.connect(addr, port);
    } else
        throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");
    connected = true;
    bound = true;
}
  1. createImpl(boolean stream)
    創(chuàng)建流或數(shù)據(jù)報(bào)套接字,stream為true時(shí)表示創(chuàng)建的是TCP socket耐薯,為false代表創(chuàng)建的是UDP socket。

接下來看一下丝里,socket具體實(shí)現(xiàn)類里面的connect曲初。

  • connect(SocketAddress endpoint, int timeout)
    1. 創(chuàng)建套接字并將其連接到指定端口和地址上
      privilegedConnect(server, serverPort, remainingMillis(deadlineMillis)).

    2. 通過socket輸出流創(chuàng)建BufferedOutputStream 對象向輸出流寫socket協(xié)議。

        if (useV4) {
            // SOCKS Protocol version 4 doesn't know how to deal with
            // DOMAIN type of addresses (unresolved addresses here)
            if (epoint.isUnresolved())
                throw new UnknownHostException(epoint.toString());
            connectV4(in, out, epoint, deadlineMillis);
            return;
        }
      

      如果使用的是SOCKS Protocol version 4杯聚,這調(diào)用connectV4方法臼婆。

      private void connectV4(InputStream in, OutputStream out,
                           InetSocketAddress endpoint,
                           long deadlineMillis) throws IOException {
        ...
        out.write(PROTO_VERS4);
        out.write(CONNECT);
        out.write((endpoint.getPort() >> 8) & 0xff);
        out.write((endpoint.getPort() >> 0) & 0xff);
        out.write(endpoint.getAddress().getAddress());
        String userName = getUserName();
        try {
            out.write(userName.getBytes("ISO-8859-1"));
        } catch (java.io.UnsupportedEncodingException uee) {
            assert false;
        }
        out.write(0);
        out.flush();
        byte[] data = new byte[8];
        int n = readSocksReply(in, data, deadlineMillis);      
        ...
      }
      

      下面看一下SOCKS Protocol version 4協(xié)議的具體內(nèi)容。參考:http://www.openssh.com/txt/socks4.protocol幌绍。下面只講解connect的協(xié)議颁褂,bind過程自行查看上面連接文檔。

      1. connect過程
        客戶端連接到SOCKS服務(wù)器傀广,當(dāng)它想要與服務(wù)器建立連接時(shí)颁独,客戶端發(fā)送CONNECT請求∥北客戶端在請求包中包含IP地址和目標(biāo)主機(jī)端口號誓酒,userid,格式如下糜值。

         +----+----+----+----+----+----+----+----+----+----+....+----+
         | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
         +----+----+----+----+----+----+----+----+----+----+....+----+
            1    1      2              4           variable       1        
        

        上面數(shù)字表示占用多少個(gè)字節(jié)丰捷。VN是SOCKS協(xié)議版本號坯墨,對應(yīng)4. CD是
        SOCKS命令代碼,connect對應(yīng)1病往。 NULL是一個(gè)字節(jié)所有為零捣染。

        如果請求被授予,則SOCKSserver建立與目標(biāo)主機(jī)的指定端口的連接停巷。
        建立此連接后或當(dāng)請求被拒絕或操作失敗時(shí)耍攘,將回復(fù)數(shù)據(jù)包發(fā)送到客戶端。數(shù)據(jù)包格式如下:

          +----+----+----+----+----+----+----+----+
           | VN | CD | DSTPORT |      DSTIP  |
          +----+----+----+----+----+----+----+----+
             1    1      2              4
        

        上面數(shù)字表示占用多少個(gè)字節(jié)畔勤。VN是回復(fù)代碼的版本蕾各,應(yīng)為0(從Android Socket源碼里面看,0或者4都是可以的). CD是結(jié)果庆揪,有以下取值:

        1. 90: 請求成功
        2. 91: 請求失敗或者被拒絕
        3. 92: 請求被拒絕因?yàn)镾OCKS服務(wù)器無法連接到客戶端
        4. 93: 請求被拒絕式曲,用戶ID不符
        5. 其它值 :請求失敗

      下面看一下SOCKS Protocol version 5協(xié)議的具體內(nèi)容。參考:https://tools.ietf.org/html/rfc1928缸榛。下面只講解connect的協(xié)議吝羞,bind過程自行查看上面連接文檔。

      1. 客戶端連接到服務(wù)器内颗,并發(fā)送版本標(biāo)識符/方法選擇消息:

         +----+----------+----------+
         |VER | NMETHODS | METHODS  |
         +----+----------+----------+
         | 1  |    1     | 1 to 255 |
         +----+----------+----------+
        

        VER:協(xié)議版本钧排,設(shè)置為5
        NMETHODS:METHODS占用的字節(jié)數(shù)
        METHODS:方法參數(shù)集合,取值可以看下面 METHOD的取值

      2. 服務(wù)器從METHODS給出的方法中選擇之一并發(fā)送METHOD選擇消息給客戶端均澳,格式如下:

         +----+--------+
         |VER | METHOD |
         +----+--------+
         | 1  |   1    |
         +----+--------+
        

        如果選擇的METHOD是'FF'恨溜,則沒有列出的方法,客戶端是可以接受的找前,客戶端必須關(guān)閉連接糟袁。METHOD值如下:

        X表示單個(gè)8位字節(jié)
        -----------------------------------
        X'00' NO AUTHENTICATION REQUIRED
        X'01' GSSAPI
        X'02' USERNAME/PASSWORD
        X'03' to X'7F' IANA ASSIGNED
        X'80' to X'FE' RESERVED FOR PRIVATE METHODS
        X'FF' NO ACCEPTABLE METHODS
        
      3. SOCKS請求形成如下:

        +----+-----+-------+------+----------+----------+
        |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+
        

        取值如下:

        VER    protocol version: X'05'
        CMD
        CONNECT X'01'
        BIND X'02'
        UDP ASSOCIATE X'03'
        RSV    RESERVED
        ATYP   address type of following address
        IP V4 address: X'01'
        DOMAINNAME: X'03'
        IP V6 address: X'04'
        DST.ADDR       desired destination address
        DST.PORT desired destination port in network octet order
        

        回復(fù)格式如下:

        +----+-----+-------+------+----------+----------+
        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+
        

        取值如下:

        VER    protocol version: X'05'
        REP    Reply field:
               X'00' succeeded
               X'01' general SOCKS server failure
               X'02' connection not allowed by ruleset
               X'03' Network unreachable
               X'04' Host unreachable
               X'05' Connection refused
               X'06' TTL expired
               X'07' Command not supported
               X'08' Address type not supported
               X'09' to X'FF' unassigned
        RSV    RESERVED(must be set to X'00')
        ATYP   address type of following address
        IP V4 address: X'01'
        DOMAINNAME: X'03'
        IP V6 address: X'04'
        BND.ADDR       server bound address
        BND.PORT       server bound port in network octet order
        
      4. Socket認(rèn)證
        SOCKS Protocol Version 5實(shí)現(xiàn)將在后面具體分析。

Socket客戶端實(shí)現(xiàn)

    override fun connect(ip: String, port: Int) {
        lock.lock()
        if (isConnected()){
            disConnect(false)
        }
        connectState = SState.STATE_CONNECTING
        this.ip = ip
        this.port = port
        Log.i(TAG,"connecting  ip=$ip , port = $port")
        try {
            while (true){
                try {
                    socket = Socket()
                    if (null == socket){
                        throw (Exception("connect failed,unknown error"))
                    }

                    val address = InetSocketAddress(ip,port)
                    socket!!.bind(address)
                    socket!!.keepAlive = false
                    //inputStream read 超時(shí)時(shí)間
                    socket!!.soTimeout = 2 * 3 * 60 * 1000
                    socket!!.tcpNoDelay = true
                    if (socket!!.isConnected){
                        dataInputStream = DataInputStream(socket!!.getInputStream())
                        dataOutputStream = DataOutputStream(socket!!.getOutputStream())
                        connectState = SState.STATE_CONNECTED
                        this.sCallback.onConnect()
                        break
                    }else{
                        throw (Exception("connect failed,unknown error"))
                    }
                }catch (e:Exception){
                    cRetryPolicy?.retry(e)
                    Thread.sleep(5*1000)
                    Log.i(TAG,"connect IOException =${e.message} , and retry count = ${cRetryPolicy?.getCurrentRetryCount()}")
                }
            }
        }catch (e:Exception){
            e.printStackTrace()
            Log.i(TAG,"connect IOException =  ${e.message}")
            connectState = SState.STATE_CONNECT_FAILED
            sCallback.onConnectFailed(e)
        }finally {
            lock.unlock()
        }
        if (connectState == SState.STATE_CONNECTED){
            receiveData()
        }
    }

完整代碼將在Socket系列文章完成后給出躺盛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末系吭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颗品,更是在濱河造成了極大的恐慌,老刑警劉巖沃缘,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躯枢,死亡現(xiàn)場離奇詭異,居然都是意外死亡槐臀,警方通過查閱死者的電腦和手機(jī)锄蹂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來水慨,“玉大人得糜,你說我怎么就攤上這事敬扛。” “怎么了朝抖?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵啥箭,是天一觀的道長。 經(jīng)常有香客問我治宣,道長急侥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任侮邀,我火速辦了婚禮坏怪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绊茧。我一直安慰自己铝宵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布华畏。 她就那樣靜靜地躺著鹏秋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唯绍。 梳的紋絲不亂的頭發(fā)上拼岳,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音况芒,去河邊找鬼惜纸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绝骚,可吹牛的內(nèi)容都是我干的耐版。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼压汪,長吁一口氣:“原來是場噩夢啊……” “哼粪牲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起止剖,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤腺阳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后穿香,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亭引,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年皮获,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了焙蚓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖购公,靈堂內(nèi)的尸體忽然破棺而出萌京,到底是詐尸還是另有隱情,我是刑警寧澤宏浩,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布知残,位于F島的核電站,受9級特大地震影響绘闷,放射性物質(zhì)發(fā)生泄漏橡庞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一印蔗、第九天 我趴在偏房一處隱蔽的房頂上張望扒最。 院中可真熱鬧,春花似錦华嘹、人聲如沸吧趣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽强挫。三九已至,卻和暖如春薛躬,著一層夾襖步出監(jiān)牢的瞬間俯渤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工型宝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留八匠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓趴酣,卻偏偏與公主長得像梨树,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子岖寞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會了寫python代碼仗谆,假如你寫了兩個(gè)python文件a.py和b.py指巡,分別去運(yùn)...
    go以恒閱讀 2,022評論 0 6
  • 大綱 一.Socket簡介 二.BSD Socket編程準(zhǔn)備 1.地址 2.端口 3.網(wǎng)絡(luò)字節(jié)序 4.半相關(guān)與全相...
    VD2012閱讀 2,346評論 0 5
  • 最近在學(xué)習(xí)Python看了一篇文章寫得不錯(cuò),是在腳本之家里的隶垮,原文如下厌处,很有幫助: 一、網(wǎng)絡(luò)知識的一些介紹 soc...
    qtruip閱讀 2,717評論 0 6
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,579評論 1 118
  • 1. Socket地址數(shù)據(jù)類型及相關(guān)函數(shù) IPv4和IPv6的地址格式定義在netinet/in.h中 IPv4地...
    執(zhí)著我們的執(zhí)著閱讀 1,720評論 0 0