Java基于TCP協(xié)議的Socket通信
簡介
TCP簡介
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的、基于字節(jié)流
的傳輸層通信協(xié)議,由IETF的RFC 793定義。在簡化的計算機(jī)網(wǎng)絡(luò)OSI模型中,它完成第四層傳輸層
所指定的功能,用戶數(shù)據(jù)報協(xié)議(UDP,下一篇博客會實(shí)現(xiàn))是同一層內(nèi) 另一個重要的傳輸協(xié)議。在因特網(wǎng)協(xié)議族(Internet protocol suite)中,TCP層是位于IP層之上,應(yīng)用層之下的中間層。不同主機(jī)的應(yīng)用層之間經(jīng)常需要可靠的、像管道一樣的連接,但是IP層不提供這樣的流機(jī)制,而是提供不可靠的包交換。
應(yīng)用層向TCP層發(fā)送用于網(wǎng)間傳輸?shù)?、?位字節(jié)表示的數(shù)據(jù)流,然后TCP把數(shù)據(jù)流分區(qū)成適當(dāng)長度的報文段(通常受該計算機(jī)連接的網(wǎng)絡(luò)的數(shù)據(jù)鏈路層的最大傳輸單元( MTU)的限制)。之后TCP把結(jié)果包傳給IP層,由它來通過網(wǎng)絡(luò)將包傳送給接收端實(shí)體的TCP層。TCP為了保證不發(fā)生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實(shí)體的包的按序接收。然后接收端實(shí)體對已成功收到的包發(fā)回一個相應(yīng)的確認(rèn)(ACK);如果發(fā)送端實(shí)體在合理的往返時延(RTT)內(nèi)未收到確認(rèn),那么對應(yīng)的數(shù)據(jù)包就被假設(shè)為已丟失將會被進(jìn)行重傳。TCP用一個校驗(yàn)和函數(shù)來檢驗(yàn)數(shù)據(jù)是否有錯誤;在發(fā)送和接收時都要計算校驗(yàn)和。
JAVA Socket簡介
所謂socket 通常也稱作”套接字
“,用于描述IP地址和端口,是一個通信鏈的句柄。應(yīng)用程序通常通過”套接字”向網(wǎng)絡(luò)發(fā)出請求或者應(yīng)答網(wǎng)絡(luò)請求
。
以J2SDK-1.3為例,Socket和ServerSocket類庫位于java.net包中。ServerSocket用于服務(wù)器端,Socket是建立網(wǎng)絡(luò)連接時使用的
。在連接成功時,應(yīng)用程序兩端都會產(chǎn)生一個Socket實(shí)例,操作這個實(shí)例,完成所需的會話。對于一個網(wǎng)絡(luò)連接來說,套接字是平等的,并沒有差別,不因?yàn)樵诜?wù)器端或在客戶端而產(chǎn)生不同級別。不管是Socket還是ServerSocket它們的工作都是通過SocketImpl類及其子類完成
的。
重要的Socket API:
java.net.Socket繼承于java.lang.Object,有八個構(gòu)造器,其方法并不多,下面介紹使用最頻繁的三個方法,其它方法大家可以見JDK-1.3文檔。
- . Accept方法用于產(chǎn)生”阻塞”,直到接受到一個連接,并且返回一個客戶端的Socket對象實(shí)例。”阻塞”是一個術(shù)語,它使程序運(yùn)行暫時”停留”在這個地方,直到一個會話產(chǎn)生,然后程序繼續(xù);通?!弊枞笔怯裳h(huán)產(chǎn)生的。
- . getInputStream方法獲得網(wǎng)絡(luò)連接輸入,同時返回一個InputStream對象實(shí)例。
- . getOutputStream方法連接的另一端將得到輸入,同時返回一個OutputStream對象實(shí)例。
注意:其中g(shù)etInputStream和getOutputStream方法均會產(chǎn)生一個IOException,它必須被捕獲,因?yàn)樗鼈兎祷氐牧鲗ο?,通常都會被另一個流對象使用。
SocketImpl介紹
既然不管是Socket還是ServerSocket它們的工作都是通過SocketImpl類及其子類完成的,那么當(dāng)然要介紹啦。
抽象類 SocketImpl 是實(shí)際實(shí)現(xiàn)套接字的所有類的通用超類。創(chuàng)建客戶端和服務(wù)器套接字都可以使用它。
具體JDK見:
http://www.dbjr.com.cn/softs/214120.html
由于它是超類具體代碼實(shí)現(xiàn)還是見下面的Socket
TCP 編程
構(gòu)造ServerSocket
具體API見:http://www.dbjr.com.cn/article/232094.htm
構(gòu)造方法:
- ServerSocket() ~創(chuàng)建非綁定服務(wù)器套接字。
- ServerSocket(int port) ~創(chuàng)建綁定到特定端口的服務(wù)器套接字。
- ServerSocket(int port, int backlog) ~利用指定的 backlog 創(chuàng)建服務(wù)器套接字并將其綁定到指定的本地端口號。
- ServerSocket(int port, int backlog, InetAddress bindAddr) ~使用指定的端口、偵聽 backlog 和要綁定到的本地 IP 地址創(chuàng)建服務(wù)器。
1.1 綁定端口
除了第一個不帶參數(shù)的構(gòu)造方法以外, 其他構(gòu)造方法都會使服務(wù)器與特定端口綁定, 該端口有參數(shù) port 指定. 例如, 以下代碼創(chuàng)建了一個與 80 端口綁定的服務(wù)器:
ServerSocket serverSocket = new ServerSocket(80);
如果運(yùn)行時無法綁定到 80 端口, 以上代碼會拋出 IOException, 更確切地說, 是拋出 BindException, 它是 IOException 的子類. BindException 一般是由以下原因造成的:
- 端口已經(jīng)被其他服務(wù)器進(jìn)程占用;
- 在某些操作系統(tǒng)中, 如果沒有以超級用戶的身份來運(yùn)行服務(wù)器程序, 那么操作系統(tǒng)不允許服務(wù)器綁定到 1-1023 之間的端口.
如果把參數(shù) port 設(shè)為 0, 表示由操作系統(tǒng)來為服務(wù)器分配一個任意可用的端口. 有操作系統(tǒng)分配的端口也稱為匿名端口. 對于多數(shù)服務(wù)器, 會使用明確的端口, 而不會使用匿名端口, 因?yàn)榭蛻舫绦蛐枰孪戎婪?wù)器的端口, 才能方便地訪問服務(wù)器.
1.2 設(shè)定客戶連接請求隊(duì)列的長度
當(dāng)服務(wù)器進(jìn)程運(yùn)行時, 可能會同時監(jiān)聽到多個客戶的連接請求. 例如, 每當(dāng)一個客戶進(jìn)程執(zhí)行以下代碼:
Socket socket = new Socket("www.javathinker.org", 80);
就意味著在遠(yuǎn)程 www.javathinker.org 主機(jī)的 80 端口上, 監(jiān)聽到了一個客戶的連接請求. 管理客戶連接請求的任務(wù)是由操作系統(tǒng)來完成的. 操作系統(tǒng)把這些連接請求存儲在一個先進(jìn)先出的隊(duì)列中. 許多操作系統(tǒng)限定了隊(duì)列的最大長度, 一般為 50 . 當(dāng)隊(duì)列中的連接請求達(dá)到了隊(duì)列的最大容量時, 服務(wù)器進(jìn)程所在的主機(jī)會拒絕新的連接請求. 只有當(dāng)服務(wù)器進(jìn)程通過 ServerSocket 的 accept() 方法從隊(duì)列中取出連接請求, 使隊(duì)列騰出空位時, 隊(duì)列才能繼續(xù)加入新的連接請求.
對于客戶進(jìn)程, 如果它發(fā)出的連接請求被加入到服務(wù)器的請求連接隊(duì)列中, 就意味著客戶與服務(wù)器的連接建立成功, 客戶進(jìn)程從 Socket 構(gòu)造方法中正常返回. 如果客戶進(jìn)程發(fā)出的連接請求被服務(wù)器拒絕, Socket 構(gòu)造方法就會拋出 ConnectionException.
Tips: 創(chuàng)建綁定端口的服務(wù)器進(jìn)程后, 當(dāng)客戶進(jìn)程的 Socket構(gòu)造方法返回成功, 表示客戶進(jìn)程的連接請求被加入到服務(wù)器進(jìn)程的請求連接隊(duì)列中. 雖然客戶端成功返回 Socket對象, 但是還沒跟服務(wù)器進(jìn)程形成一條通信線路. 必須在服務(wù)器進(jìn)程通過 ServerSocket 的 accept() 方法從請求連接隊(duì)列中取出連接請求, 并返回一個Socket 對象后, 服務(wù)器進(jìn)程這個Socket 對象才與客戶端的 Socket 對象形成一條通信線路.
ServerSocket 構(gòu)造方法的 backlog 參數(shù)用來顯式設(shè)置連接請求隊(duì)列的長度, 它將覆蓋操作系統(tǒng)限定的隊(duì)列的最大長度. 值得注意的是, 在以下幾種情況中, 仍然會采用操作系統(tǒng)限定的隊(duì)列的最大長度:
- backlog 參數(shù)的值大于操作系統(tǒng)限定的隊(duì)列的最大長度;
- backlog 參數(shù)的值小于或等于0;
- 在ServerSocket 構(gòu)造方法中沒有設(shè)置 backlog 參數(shù).
以下的 Client.java 和 Server.java 用來演示服務(wù)器的連接請求隊(duì)列的特性.
Client.java
import java.net.Socket; public class Client { public static void main(String[] args) throws Exception{ final int length = 100; String host = "localhost"; int port = 1122; Socket[] socket = new Socket[length]; for(int i = 0;i<length;i++){ socket[i] = new Socket(host,port); System.out.println("第"+(i+1)+"次連接成功!"); } Thread.sleep(3000); for(int i=0;i<length;i++){ socket[i].close(); } } }
Server.java
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { private int port = 1122; private ServerSocket serverSocket; public Server() throws Exception{ serverSocket = new ServerSocket(port,3); System.out.println("服務(wù)器啟動!"); } public void service(){ while(true){ Socket socket = null; try { socket = serverSocket.accept(); System.out.println("New connection accepted "+ socket.getInetAddress()+":"+socket.getPort()); } catch (IOException e) { e.printStackTrace(); }finally{ if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws Exception{ Server server = new Server(); Thread.sleep(60000*10); server.service(); } }
Client 試圖與 Server 進(jìn)行 100 次連接. 在 Server 類中, 把連接請求隊(duì)列的長度設(shè)為 3. 這意味著當(dāng)隊(duì)列中有了 3 個連接請求時, 如果Client 再請求連接, 就會被 Server 拒絕.? 下面按照以下步驟運(yùn)行 Server 和 Client 程序.
1.3 設(shè)定綁定的IP 地址
如果主機(jī)只有一個IP 地址, 那么默認(rèn)情況下, 服務(wù)器程序就與該IP 地址綁定. ServerSocket 的第 4 個構(gòu)造方法 ServerSocket(int port, int backlog, InetAddress bingAddr) 有一個 bindAddr 參數(shù), 它顯式指定服務(wù)器要綁定的IP 地址, 該構(gòu)造方法適用于具有多個IP 地址的主機(jī). 假定一個主機(jī)有兩個網(wǎng)卡, 一個網(wǎng)卡用于連接到 Internet, IP為 222.67.5.94, 還有一個網(wǎng)卡用于連接到本地局域網(wǎng), IP 地址為 192.168.3.4. 如果服務(wù)器僅僅被本地局域網(wǎng)中的客戶訪問, 那么可以按如下方式創(chuàng)建 ServerSocket:
ServerSocket serverSocket = new ServerSocket(8000, 10, InetAddress.getByName(“192.168.3.4”));
1.4 默認(rèn)構(gòu)造方法的作用
ServerSocket 有一個不帶參數(shù)的默認(rèn)構(gòu)造方法. 通過該方法創(chuàng)建的 ServerSocket 不與任何端口綁定, 接下來還需要通過 bind() 方法與特定端口綁定.
這個默認(rèn)構(gòu)造方法的用途是, 允許服務(wù)器在綁定到特定端口之前, 先設(shè)置ServerSocket 的一些選項(xiàng). 因?yàn)橐坏┓?wù)器與特定端口綁定, 有些選項(xiàng)就不能再改變了.比如:SO_REUSEADDR 選項(xiàng)
在以下代碼中, 先把 ServerSocket 的 SO_REUSEADDR 選項(xiàng)設(shè)為 true, 然后再把它與 8000 端口綁定:
ServerSocket serverSocket = new ServerSocket(); serverSocket.setReuseAddress(true); //設(shè)置 ServerSocket 的選項(xiàng) serverSocket.bind(new InetSocketAddress(8000)); //與8000端口綁定
如果把以上程序代碼改為:
ServerSocket serverSocket = new ServerSocket(8000); serverSocket.setReuseAddress(true);//設(shè)置 ServerSocket 的選項(xiàng)
那么 serverSocket.setReuseAddress(true) 方法就不起任何作用了, 因?yàn)?SO_REUSEADDR 選項(xiàng)必須在服務(wù)器綁定端口之前設(shè)置才有效.
多線程示例
客戶端:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; /* * 客戶端 */ public class Client { public static void main(String[] args) { try { //1.創(chuàng)建客戶端Socket,指定服務(wù)器地址和端口 Socket socket=new Socket("localhost", 8888); //2.獲取輸出流,向服務(wù)器端發(fā)送信息 OutputStream os=socket.getOutputStream();//字節(jié)輸出流 PrintWriter pw=new PrintWriter(os);//將輸出流包裝為打印流 pw.write("用戶名:whf;密碼:789"); pw.flush(); socket.shutdownOutput();//關(guān)閉輸出流 //3.獲取輸入流,并讀取服務(wù)器端的響應(yīng)信息 InputStream is=socket.getInputStream(); BufferedReader br=new BufferedReader(new InputStreamReader(is)); String info=null; while((info=br.readLine())!=null){ System.out.println("我是客戶端,服務(wù)器說:"+info); } //4.關(guān)閉資源 br.close(); is.close(); pw.close(); os.close(); socket.close(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
服務(wù)器:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /* * 基于TCP協(xié)議的Socket通信,實(shí)現(xiàn)用戶登陸 * 服務(wù)器端 */ public class Server { public static void main(String[] args) { try { //1.創(chuàng)建一個服務(wù)器端Socket,即ServerSocket,指定綁定的端口,并監(jiān)聽此端口 ServerSocket serverSocket=new ServerSocket(8888); Socket socket=null; //記錄客戶端的數(shù)量 int count=0; System.out.println("***服務(wù)器即將啟動,等待客戶端的連接***"); //循環(huán)監(jiān)聽等待客戶端的連接 while(true){ //調(diào)用accept()方法開始監(jiān)聽,等待客戶端的連接 socket=serverSocket.accept(); //創(chuàng)建一個新的線程 ServerThread serverThread=new ServerThread(socket); //啟動線程 serverThread.start(); count++;//統(tǒng)計客戶端的數(shù)量 System.out.println("客戶端的數(shù)量:"+count); InetAddress address=socket.getInetAddress(); System.out.println("當(dāng)前客戶端的IP:"+address.getHostAddress()); } } catch (IOException e) { e.printStackTrace(); } } }
服務(wù)器處理類:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; /* * 服務(wù)器線程處理類 */ public class ServerThread extends Thread { // 和本線程相關(guān)的Socket Socket socket = null; public ServerThread(Socket socket) { this.socket = socket; } //線程執(zhí)行的操作,響應(yīng)客戶端的請求 public void run(){ InputStream is=null; InputStreamReader isr=null; BufferedReader br=null; OutputStream os=null; PrintWriter pw=null; try { //獲取輸入流,并讀取客戶端信息 is = socket.getInputStream(); isr = new InputStreamReader(is); br = new BufferedReader(isr); String info=null; while((info=br.readLine())!=null){//循環(huán)讀取客戶端的信息 System.out.println("我是服務(wù)器,客戶端說:"+info); } socket.shutdownInput();//關(guān)閉輸入流 //獲取輸出流,響應(yīng)客戶端的請求 os = socket.getOutputStream(); pw = new PrintWriter(os); pw.write("歡迎您!"); pw.flush();//調(diào)用flush()方法將緩沖輸出 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //關(guān)閉資源 try { if(pw!=null) pw.close(); if(os!=null) os.close(); if(br!=null) br.close(); if(isr!=null) isr.close(); if(is!=null) is.close(); if(socket!=null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
以上所述是小編給大家介紹的Java基于TCP協(xié)議的Socket通信,希望對大家有所幫助。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法
這篇文章主要介紹了java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法,較為詳細(xì)的分析了JavaMail發(fā)送郵件的用法,是非常實(shí)用的技巧,需要的朋友可以參考下2015-01-01深入理解Netty?FastThreadLocal優(yōu)缺點(diǎn)及實(shí)現(xiàn)邏輯
本文以線上詭異問題為切入點(diǎn),通過對比JDK ThreadLocal和Netty FastThreadLocal實(shí)現(xiàn)邏輯以及優(yōu)缺點(diǎn),并深入解讀源碼,由淺入深理解Netty FastThreadLocal2023-10-10史上最全最強(qiáng)SpringMVC詳細(xì)示例實(shí)戰(zhàn)教程(圖文)
這篇文章主要介紹了史上最全最強(qiáng)SpringMVC詳細(xì)示例實(shí)戰(zhàn)教程(圖文),需要的朋友可以參考下2016-12-12Java以struts2為例介紹如何實(shí)現(xiàn)圖片上傳
這篇文章主要介紹了Java struts2中如何實(shí)現(xiàn)圖片上傳的相關(guān)資料,需要的朋友可以參考下2015-11-11springboot項(xiàng)目如何使用切面記錄用戶操作日志
這篇文章主要介紹了springboot項(xiàng)目如何使用切面記錄用戶操作日志,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10