簡介
網(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); }
- 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)枣抱。
- factory
-
Socket(Proxy proxy)
創(chuàng)建具有代理功能的Socket對象熔吗。-
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)行通信等等)
總之:
- 需要在建連前就知道端口的話,需要 bind
- 需要通過指定的端口來通訊的話箕般,需要 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;
}
- 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)
創(chuàng)建套接字并將其連接到指定端口和地址上
privilegedConnect(server, serverPort, remainingMillis(deadlineMillis)).-
通過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過程自行查看上面連接文檔。
-
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é)果庆揪,有以下取值:
- 90: 請求成功
- 91: 請求失敗或者被拒絕
- 92: 請求被拒絕因?yàn)镾OCKS服務(wù)器無法連接到客戶端
- 93: 請求被拒絕式曲,用戶ID不符
- 其它值 :請求失敗
下面看一下SOCKS Protocol version 5協(xié)議的具體內(nèi)容。參考:https://tools.ietf.org/html/rfc1928缸榛。下面只講解connect的協(xié)議吝羞,bind過程自行查看上面連接文檔。
-
客戶端連接到服務(wù)器内颗,并發(fā)送版本標(biāo)識符/方法選擇消息:
+----+----------+----------+ |VER | NMETHODS | METHODS | +----+----------+----------+ | 1 | 1 | 1 to 255 | +----+----------+----------+
VER:協(xié)議版本钧排,設(shè)置為5
NMETHODS:METHODS占用的字節(jié)數(shù)
METHODS:方法參數(shù)集合,取值可以看下面 METHOD的取值 -
服務(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
-
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
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系列文章完成后給出躺盛。