Socket网络编程——(一)
什么是Socket
網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。端點(diǎn)由IP地址和端口號(hào)共同組成,簡(jiǎn)單的說(shuō)它是IP地址和端口結(jié)合的協(xié)議。
常用的套接字
流式套接字:流套接字用于提供面向連接、可靠的數(shù)據(jù)傳輸服務(wù),該服務(wù)將保證數(shù)據(jù)能夠?qū)崿F(xiàn)無(wú)差錯(cuò)、無(wú)重復(fù)發(fā)送,并按順序接收(基于TCP)。
數(shù)據(jù)報(bào)套接字:數(shù)據(jù)報(bào)套接字提供了一種無(wú)連接的服務(wù)。該服務(wù)并不能保證數(shù)據(jù)傳輸?shù)目煽啃?#xff0c;數(shù)據(jù)有可能在傳輸過(guò)程中丟失或出現(xiàn)數(shù)據(jù)重復(fù),且無(wú)法保證順序地接收到數(shù)據(jù)(基于UDP)。
簡(jiǎn)單的介紹下TCP和UDP。TCP是面向連接的,保證可靠的數(shù)據(jù)傳輸,每次建立連接都需要經(jīng)歷三次握手,數(shù)據(jù)傳輸完成都需要經(jīng)歷4次揮手?jǐn)嚅_(kāi)連接,由于TCP是面向連接的所以只能用于端到端的通信。而UDP是面向無(wú)連接的通訊協(xié)議,每次發(fā)送數(shù)據(jù)不需要建立連接,因此可以用于廣播發(fā)送并不局限于端到端。
本篇主要學(xué)習(xí)ServerSocket和Socket的用法,它們分別對(duì)應(yīng)了服務(wù)端的套接字和客戶端的套接字,都是基于TCP協(xié)議封裝的。
ServerSocket構(gòu)造方法
| ServerSocket(int port) | 創(chuàng)建一個(gè)綁定到指定端口的服務(wù)器套接字。 |
| ServerSocket(int port, int backlog) | 創(chuàng)建一個(gè)綁定到指定端口的服務(wù)器套接字,backlog是客戶端連接請(qǐng)求的隊(duì)列長(zhǎng)度。 |
| ServerSocket(int port, int backlog, InetAddress bindAddr) | 創(chuàng)建一個(gè)綁定到指定端口、具有指定大小的請(qǐng)求隊(duì)列、綁定到本地IP地址的服務(wù)器套接字。 |
簡(jiǎn)單的說(shuō)明一下,第一個(gè)構(gòu)造創(chuàng)建了一個(gè)未綁定任何端口的套接字,需要我們調(diào)用bind方法手動(dòng)綁定。第2、3構(gòu)造方法在創(chuàng)建的時(shí)候就指定了要綁定的端口。第三個(gè)構(gòu)造方法在創(chuàng)建的時(shí)候就指定了要綁定的端口和本地IP,如果不指定IP會(huì)自動(dòng)分配一個(gè)可用的本機(jī)IP。
需要特別理解backlog這個(gè)參數(shù)
backlog用來(lái)設(shè)置客戶端連接請(qǐng)求隊(duì)列的長(zhǎng)度,客戶端的連接請(qǐng)求是由操作系統(tǒng)負(fù)責(zé)管理的,操作系統(tǒng)會(huì)把連接請(qǐng)求放入到一個(gè)先進(jìn)先出的隊(duì)列里,通常操作系統(tǒng)會(huì)規(guī)定隊(duì)列的最大長(zhǎng)度為50。當(dāng)隊(duì)列到達(dá)上限50后再有其他請(qǐng)求到來(lái)就會(huì)被拒絕掉。只有當(dāng)服務(wù)器執(zhí)行serverSocket.accept(),把請(qǐng)求取出后,隊(duì)列才能騰出空位加入新的請(qǐng)求。如果連接請(qǐng)求被拒絕,則會(huì)拋出ConnectionException。
ServerSocket相關(guān)API
bind(SocketAddress endpoint):綁定到特定的地址(IP地址和端口)。
accept() : 監(jiān)聽(tīng),當(dāng)有連接請(qǐng)求到來(lái)就接收它返回一個(gè)新的Socket來(lái)和客戶端通信,這個(gè)方法會(huì)阻塞直到連接請(qǐng)求到來(lái)
setSoTimeout(int timeout):設(shè)置連接超時(shí),當(dāng)設(shè)置了該選項(xiàng),accept()方法在該時(shí)間段沒(méi)有連接請(qǐng)求到來(lái)會(huì)拋出異常。
setReuseAddress(boolean on):
設(shè)置端口重用。當(dāng)ServerSocket關(guān)閉時(shí),如果網(wǎng)絡(luò)上還有發(fā)送到該ServerSocket的數(shù)據(jù),這個(gè)ServerSocket不會(huì)立即釋放本地端口,而是等待一段時(shí)間,以確保接收到了網(wǎng)絡(luò)上發(fā)送過(guò)來(lái)的延遲數(shù)據(jù),然后再釋放端口。為了確保一個(gè)進(jìn)程關(guān)閉了ServerSocket 后,即使操作系統(tǒng)還沒(méi)釋放端口,同一個(gè)主機(jī)上的其他進(jìn)程任然可以立刻重用該端口,可以調(diào)用ServerSocket .setResuseAddress(true)方法。需要注意兩點(diǎn):1、該方法必須在綁定到指定端口之前調(diào)用,否則無(wú)效。2、綁定到該公用端口的其他套接字也需要調(diào)用setReuseAddress方法。
setReceiveBufferSize (int size):
為所有accept方法返回的socket對(duì)象設(shè)置接收緩存區(qū)大小,單位為字節(jié),默認(rèn)大小與操作系統(tǒng)有關(guān)。一般說(shuō)來(lái),傳輸大的連續(xù)的數(shù)據(jù)塊(基于HTTP或FTP協(xié)議的數(shù)據(jù)傳輸)可以使用較大的緩沖區(qū),這可以減少傳輸數(shù)據(jù)的次數(shù),從而提高傳輸數(shù)據(jù)的效率。而對(duì)于交互式的通信如Telent或者希望即時(shí)獲取數(shù)據(jù)如游戲,則應(yīng)該采用小的緩沖區(qū),確保能及時(shí)把小批量的數(shù)據(jù)發(fā)送給對(duì)方。
setPerformancePreferences(int connectionTime,int latency,int bandwidth):
設(shè)置服務(wù)器套接字的性能首選項(xiàng)。用來(lái)設(shè)置短連接時(shí)間、低延遲和高帶寬的相對(duì)重要性。
Socket構(gòu)造方法
| Socket(Proxy proxy) | 創(chuàng)建一個(gè)未連接的套接字,該套接字使用特定的代理 。 |
| Socket(SocketImpl impl) | 使用用戶指定的socketimpl創(chuàng)建未連接的套接字。 |
| Socket(String host, int port) | 創(chuàng)建流套接字并將其連接到指定主機(jī)上的指定端口號(hào)。 |
| Socket(InetAddress address, int port) | 創(chuàng)建流套接字并將其連接到指定IP地址處的指定端口號(hào)。 |
| Socket(String host, int port, InetAddress localAddr, int localPort) | 創(chuàng)建套接字并將其連接到指定遠(yuǎn)程主機(jī)上的指定端口。套接字還將綁定到指定的本地地址和端口。 |
構(gòu)造方法還有其他,用法都類似,需要注意的是創(chuàng)建未連接的套接字需要我們自己手都調(diào)用socket.connect()方法進(jìn)行連接,而如果調(diào)用那些創(chuàng)建時(shí)就進(jìn)行連接的構(gòu)造不需要再調(diào)用connect()方法進(jìn)行連接。通常都是創(chuàng)建未連接的套接字,因?yàn)槲覀兺枰谶B接之前對(duì)它進(jìn)行一些初始化的配置。
Socket相關(guān)API
很多api的用法和ServerSocket的一致,只說(shuō)幾個(gè)特殊的。
setSoTimeout(int timeout):設(shè)置讀超時(shí),當(dāng)此選項(xiàng)設(shè)置為非零時(shí)間,對(duì)此套接字關(guān)聯(lián)的輸入流的read()調(diào)用將僅阻塞此時(shí)間量,超時(shí)將拋出SocketTimeoutException: Read timed out。
setKeepAlive(boolean on):
當(dāng)設(shè)置為true的時(shí)候,底層的TCP實(shí)現(xiàn)會(huì)監(jiān)視該連接是否有效。當(dāng)通信的兩端套接字兩個(gè)小時(shí)內(nèi)沒(méi)有數(shù)據(jù)交換(注意:實(shí)際值取決于實(shí)現(xiàn)),TCP實(shí)現(xiàn)會(huì)自動(dòng)向遠(yuǎn)程套接字發(fā)送keepalive探測(cè)包,如果遠(yuǎn)程套接字響應(yīng)了,說(shuō)明連接正常。如果遠(yuǎn)程套接字沒(méi)響應(yīng),TCP實(shí)現(xiàn)會(huì)繼續(xù)探測(cè),一定時(shí)間后依舊沒(méi)響應(yīng)代表遠(yuǎn)程服務(wù)器可能崩潰,TCP嘗試關(guān)閉socket連接。默認(rèn)值為 false, 表示TCP 不會(huì)監(jiān)視連接是否有效, 不活動(dòng)的客戶端套接字可能會(huì)永遠(yuǎn)存在下去, 而不會(huì)注意到服務(wù)器已經(jīng)關(guān)閉??赡艿?種情況
1、遠(yuǎn)程套接字以預(yù)期的ACK響應(yīng)。TCP將在另外2小時(shí)不活動(dòng)后發(fā)送另一個(gè)探測(cè)器。
2、遠(yuǎn)程套接字以RST響應(yīng),該RST告訴本地TCP對(duì)等主機(jī)已崩潰并重新啟動(dòng)。套接字已關(guān)閉。
3、對(duì)等端沒(méi)有響應(yīng)。套接字已關(guān)閉。
setOOBInline(boolean on):
默認(rèn)情況下,此選項(xiàng)被禁用,在套接字上接收的TCP緊急數(shù)據(jù)將被靜默丟棄。如果用戶希望接收緊急數(shù)據(jù),則必須啟用此選項(xiàng)。啟用后,緊急數(shù)據(jù)與正常數(shù)據(jù)一起接收。緊急數(shù)據(jù)是通過(guò)sendUrgentData發(fā)出的一個(gè)單字節(jié)數(shù)據(jù),它不經(jīng)過(guò)發(fā)送緩沖區(qū)而是直接發(fā)送出去的,需要注意的是對(duì)于服務(wù)器端緊急數(shù)據(jù)和普通數(shù)據(jù)是混在一起的,服務(wù)器并不知道客戶端發(fā)送到數(shù)據(jù)是通過(guò)sendUrgentData發(fā)出的,還是通過(guò)OutputStream發(fā)出的。
setSendBufferSize(int size):
設(shè)置發(fā)送緩沖區(qū)大小(size>0),最好不要將發(fā)送緩沖區(qū)設(shè)得太小,太小會(huì)導(dǎo)致傳輸數(shù)據(jù)過(guò)于頻繁,從而降低網(wǎng)絡(luò)傳輸?shù)男省?/p>
setTcpNoDelay(boolean on):
這個(gè)方法的作用是開(kāi)啟或關(guān)閉Nagle算法,默認(rèn)Nagle算法是開(kāi)啟的,通過(guò)setTcpNoDelay(true)可以關(guān)閉該算法。它有兩個(gè)作用:a、Nagle算法要求一個(gè)TCP連接上最多只能有一個(gè)未被確認(rèn)的小分組,在該小分組的確認(rèn)到來(lái)之前,不能發(fā)送其他小分組。也就是說(shuō)發(fā)送方發(fā)送了一個(gè)分組后,只有等到接收方回復(fù)的ack控制段才發(fā)送下一個(gè)分組;2、如果啟用該算法數(shù)據(jù)只有在寫緩存中累積到一定量之后,才會(huì)被發(fā)送出去,這樣通過(guò)減少數(shù)據(jù)傳輸次數(shù)來(lái)提高了網(wǎng)絡(luò)利用率。如果關(guān)閉該算法,在前一個(gè)分組的確認(rèn)沒(méi)到來(lái)時(shí)依舊可以發(fā)送下一個(gè)分組并且客戶端每發(fā)送一次數(shù)據(jù),無(wú)論數(shù)據(jù)包的大小都會(huì)將這些數(shù)據(jù)發(fā)送出去。
setSoLinger(boolean on, int linger):
這個(gè)設(shè)置僅影響套接字的關(guān)閉。
默認(rèn)為false,當(dāng)執(zhí)行socket的close()方法會(huì)立即返回,如果此時(shí)還有數(shù)據(jù)沒(méi)發(fā)送完將由底層系統(tǒng)來(lái)接管輸出流,嘗試將緩沖區(qū)數(shù)據(jù)發(fā)送出去。
如果設(shè)置為setSoLinger(true, 0)調(diào)用socket.close()關(guān)閉套接字會(huì)立即返回,如果緩沖區(qū)還有數(shù)據(jù)未發(fā)送直接拋棄,并發(fā)送RST結(jié)束命令給對(duì)方。
如果設(shè)置為setSoLinger(true, n),關(guān)閉套接字時(shí)如果緩沖區(qū)還有數(shù)據(jù)沒(méi)發(fā)送,最長(zhǎng)阻塞n毫秒,在這個(gè)時(shí)間內(nèi)如果緩沖區(qū)數(shù)據(jù)發(fā)送了close方法就返回,如果超過(guò)200毫秒任然沒(méi)發(fā)送完,剩下的數(shù)據(jù)直接拋棄,立即返回。
通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)認(rèn)識(shí)下Socket:
編寫了服務(wù)端Socket和客戶端Socket,客戶端連接服務(wù)器后向服務(wù)器發(fā)送數(shù)據(jù),服務(wù)端收到后回送給客戶端一個(gè)信息,當(dāng)服務(wù)端收到客戶端發(fā)送的bye也給客戶端回送一個(gè)bye,然后客戶端和服務(wù)端斷開(kāi)連接。這里的客戶端和服務(wù)端都是在本機(jī)上。
服務(wù)端:
客戶端:
public class Client {public static void main(String args[]) throws IOException {Socket socket=createSocket();initSocketConfig(socket);//連接到本地 端口為20000的服務(wù)端 連接超時(shí)5000socket.connect(new InetSocketAddress(InetAddress.getLocalHost(),20000),5000);System.out.println("成功連接到服務(wù)器");//輸入流 用于獲取服務(wù)端發(fā)送到的數(shù)據(jù) socket.getInputStream()BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));//輸出流 用于向服務(wù)端發(fā)送數(shù)據(jù) socket.getOutputStream()PrintStream writer=new PrintStream(socket.getOutputStream());//鍵盤流BufferedReader cin=new BufferedReader(new InputStreamReader(System.in));boolean flag=true;do {String line=cin.readLine();writer.println(line);//發(fā)送數(shù)據(jù)String rcv=reader.readLine();//接收數(shù)據(jù)System.out.println("Server:"+rcv);if (rcv.equalsIgnoreCase("bye")){flag=false;}}while (flag);//釋放資源System.out.println("客戶端退出");cin.close();writer.close();reader.close();socket.close();}private static void initSocketConfig(Socket socket) throws SocketException {socket.setSoTimeout(5000);//讀超時(shí)時(shí)間socket.setReuseAddress(true);socket.setKeepAlive(true);socket.setOOBInline(true);socket.setTcpNoDelay(true);socket.setSoLinger(true,200);socket.setSendBufferSize(64*1024*1024);socket.setReceiveBufferSize(64*1024*1024);}private static Socket createSocket() throws IOException {Socket socket=new Socket();return socket;} }結(jié)果:
客戶端打印:
服務(wù)端打印:
可以看到服務(wù)端和客戶端之間的通訊就是通過(guò)套接字進(jìn)行的。
學(xué)習(xí)記錄,如有錯(cuò)誤歡迎指正。
總結(jié)
以上是生活随笔為你收集整理的Socket网络编程——(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android 飞框动画,Android
- 下一篇: 75 jsp基础语法汇总