一.介紹
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的并村、可靠的郭厌、基于字節(jié)流的傳輸層通信協(xié)議,由IETF的RFC 793定義拳锚。在簡化的計算機網(wǎng)絡OSI模型中氧秘,它完成第四層傳輸層所指定的功能,用戶數(shù)據(jù)報協(xié)議(UDP)是同一層內(nèi) [1]? 另一個重要的傳輸協(xié)議省撑。在因特網(wǎng)協(xié)議族(Internet protocol suite)中玉组,TCP層是位于IP層之上谎柄,應用層之下的中間層。不同主機的應用層之間經(jīng)常需要可靠的惯雳、像管道一樣的連接朝巫,但是IP層不提供這樣的流機制,而是提供不可靠的包交換石景。
二.知識點介紹
1劈猿、概述
2、TCP協(xié)議通信
3潮孽、TCP代碼實現(xiàn)
4揪荣、TCP網(wǎng)絡程序
5、文件上傳案例
6往史、文件上傳案例多線程版本
三.上課視頻對應說明文檔
1仗颈、概述
TCP協(xié)議是面向連接的通信協(xié)議,即在傳輸數(shù)據(jù)前先在發(fā)送端和接收端建立邏輯連接椎例,然后再傳輸數(shù)據(jù)挨决,它提供了兩臺計算機之間可靠無差錯的數(shù)據(jù)傳輸。在TCP連接中必須要明確客戶端與服務器端订歪,由客戶端向服務端發(fā)出連接請求脖祈,每次連接的創(chuàng)建都需要經(jīng)過“三次握手”。第一次握手刷晋,客戶端向服務器端發(fā)出連接請求盖高,等待服務器確認,第二次握手眼虱,服務器端向客戶端回送一個響應喻奥,通知客戶端收到了連接請求,第三次握手捏悬,客戶端再次向服務器端發(fā)送確認信息撞蚕,確認連接。整個交互過程如下圖所示邮破。
由于TCP協(xié)議的面向連接特性诈豌,它可以保證傳輸數(shù)據(jù)的安全性仆救,所以是一個? 被廣泛采用的協(xié)議抒和,例如在下載文件時,如果數(shù)據(jù)接收不完整彤蔽,將會導致文件數(shù)據(jù)丟失而不能被打開摧莽,因此,下載文件時必須采用TCP協(xié)議顿痪。
2镊辕、TCP協(xié)議通信
TCP通信同UDP通信一樣油够,都能實現(xiàn)兩臺計算機之間的通信,通信的兩端都需要創(chuàng)建socket對象征懈。
區(qū)別在于石咬,UDP中只有發(fā)送端和接收端,不區(qū)分客戶端與服務器端卖哎,計算機之間可以任意地發(fā)送數(shù)據(jù)鬼悠。
而TCP通信是嚴格區(qū)分客戶端與服務器端的,在通信時亏娜,必須先由客戶端去連接服務器端才能實現(xiàn)通信焕窝,服務器端不可以主動連接客戶端,并且服務器端程序需要事先啟動维贺,等待客戶端的連接它掂。
在JDK中提供了兩個類用于實現(xiàn)TCP程序,一個是ServerSocket類溯泣,用于表示服務器端虐秋,一個是Socket類,用于表示客戶端发乔。
通信時熟妓,首先創(chuàng)建代表服務器端的ServerSocket對象,該對象相當于開啟一個服務栏尚,并等待客戶端的連接起愈,然后創(chuàng)建代表客戶端的Socket對象向服務器端發(fā)出連接請求,服務器端響應請求译仗,兩者建立連接開始通信抬虽。
2.1、ServerSocket服務端
通過前面的學習知道纵菌,在開發(fā)TCP程序時阐污,首先需要創(chuàng)建服務器端程序。JDK的java.net包中提供了一個ServerSocket類咱圆,該類的實例對象可以實現(xiàn)一個服務器段的程序笛辟。通過查閱API文檔可知,ServerSocket類提供了多種構(gòu)造方法序苏,接下來就對ServerSocket的構(gòu)造和方法進行逐一地講解手幢。
ServerSocket(int port):創(chuàng)建綁定到特定端口的服務器套接字。
使用該構(gòu)造方法在創(chuàng)建ServerSocket對象時忱详,就可以將其綁定到一個指定的端口號上(參數(shù)port就是端口號)围来。
接下來學習一下ServerSocket的常用方法:
(1) Socket accept():偵聽并接受到此套接字的連接。
(2) InetAddress getInetAddress:返回此服務器的套接字的本地地址。
ServerSocket對象負責監(jiān)聽某臺計算機的某個端口號监透,在創(chuàng)建ServerSocket對象后桶错,需要繼續(xù)調(diào)用該對象的accept()方法,接收來自客戶端的請求胀蛮。當執(zhí)行了accept()方法之后院刁,服務器端程序會發(fā)生阻塞,直到客戶端發(fā)出連接請求粪狼,accept()方法才會返回一個Scoket對象用于和客戶端實現(xiàn)通信黎比,程序才能繼續(xù)向下執(zhí)行。
2.2鸳玩、Socket客戶端
講解了ServerSocket對象可以實現(xiàn)服務端程序阅虫,但只實現(xiàn)服務器端程序還不能完成通信,此時還需要一個客戶端程序與之交互不跟,為此JDK提供了一個Socket類颓帝,用于實現(xiàn)TCP客戶端程序。
通過查閱API文檔可知Socket類同樣提供了多種構(gòu)造方法窝革,接下來就對Socket的常用構(gòu)造方法進行詳細講解购城。
(1)Socket(String host,int port):創(chuàng)建一個流套接字并將其連接到指定主機上的指定端口號。
使用該構(gòu)造方法在創(chuàng)建Socket對象時虐译,會根據(jù)參數(shù)去連接在指定地址和端口上運行的服務器程序瘪板,其中參數(shù)host接收的是一個字符串類型的IP地址。
(2)Socket(InetAddress address,int port):創(chuàng)建一個流套接字并將其連接到指定IP地址的指定端口號漆诽。
該方法在使用上與第二個構(gòu)造方法類似侮攀,參數(shù)address用于接收一個InetAddress類型的對象,該對象用于封裝一個IP地址厢拭。
在以上Socket的構(gòu)造方法中兰英,最常用的是第一個構(gòu)造方法。
接下來學習一下Socket的常用方法供鸠,如表所示畦贸。
在Socket類的常用方法中,getInputStream()和getOutStream()方法分別用于獲取輸入流和輸出流楞捂。當客戶端和服務端建立連接后薄坏,數(shù)據(jù)是以IO流的形式進行交互的,從而實現(xiàn)通信寨闹。
接下來通過一張圖來描述服務器端和客戶端的數(shù)據(jù)傳輸胶坠,如下圖所示。
3鼻忠、TCP代碼實現(xiàn)
Socket客戶端和ServerSocket服務器端
完成步驟:
(1)建立客戶端和服務器端涵但。
(2)建立連接后,通過Socket中的IO流(Socket流)進行數(shù)據(jù)的傳輸帖蔓。
(3)(如果是服務器端矮瘟,則需要添加一步操作:通過Socket服務獲取Sokect再獲取其當中的IO流)
(4)關(guān)閉socket。
(5)同樣塑娇,客戶端與服務器端是兩個獨立的應用程序澈侠。
注意:
(1)服務器端開啟后等待客戶端訪問,可以不關(guān)閉埋酬。
(2)一個服務器端對應多個客戶端哨啃。
(3)不同客戶端間通信可以通過服務器端中轉(zhuǎn)信息。
4写妥、TCP網(wǎng)絡程序
了解了ServerSocket拳球、Socket類的基本用法,為了讓大家更好地掌握這兩個類的使用珍特,接下來通過一個TCP通信的案例來進一步學習祝峻。如下圖所示。
要實現(xiàn)TCP通信需要創(chuàng)建一個服務器端程序和一個客戶端程序扎筒,為了保證數(shù)據(jù)傳輸?shù)陌踩岳痴遥紫刃枰獙崿F(xiàn)服務器端程序。
4.1嗜桌、服務端
代碼示例:
/*
* TCP 服務器端
*
* 1,創(chuàng)建服務器ServerSocket對象(指定服務器端口號)
* 2奥溺,開啟服務器了,等待客戶端的連接骨宠,當客戶端連接后浮定,可以獲取到連接服務器的客戶端Socket對象
* 3,給客戶端反饋信息
* 4,關(guān)閉流資源
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1,創(chuàng)建服務器ServerSocket對象(指定服務器端口號)
ServerSocket ss = new ServerSocket(8888);
//2,開啟服務器了层亿,等待客戶端的連接壶唤,當客戶端連接后,可以獲取到連接服務器的客戶端Socket對象
Socket s = ss.accept();
//3,給客戶端反饋信息
/*
* a,獲取客戶端的輸出流
* b,在服務端端棕所,通過客戶端的輸出流寫數(shù)據(jù)給客戶端
*/
//a,獲取客戶端的輸出流
OutputStream out = s.getOutputStream();
//b,在服務端端鸠补,通過客戶端的輸出流寫數(shù)據(jù)給客戶端
out.write("你已經(jīng)連接上了服務器".getBytes());
//4,關(guān)閉流資源
out.close();
s.close();
//ss.close();? 服務器流 通常都是不關(guān)閉的
}
}
4.2、客戶端
代碼示例:
/*
* TCP 客戶端
*
* 1帆阳,創(chuàng)建客戶端Socket對象,(指定要連接的服務器地址與端口號)
* 2,獲取服務器端的反饋回來的信息
* 3,關(guān)閉流資源
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1嗤形,創(chuàng)建客戶端Socket對象,(指定要連接的服務器地址與端口號)
Socket s = new Socket("192.168.74.58", 8888);
//2,獲取服務器端的反饋回來的信息
InputStream in = s.getInputStream();
//獲取獲取流中的數(shù)據(jù)
byte[] buffer = new byte[1024];
//把流中的數(shù)據(jù)存儲到數(shù)組中,并記錄讀取字節(jié)的個數(shù)
int length = in.read(buffer);
//顯示數(shù)據(jù)
System.out.println( new String(buffer, 0 , length) );
//3,關(guān)閉流資源
in.close();
s.close();
}
}
5针贬、文件上傳案例
5.1击费、案例分析
目前大多數(shù)服務器都會提供文件上傳的功能,由于文件上傳需要數(shù)據(jù)的安全性和完整性桦他,很明顯需要使用TCP協(xié)議來實現(xiàn)蔫巩。接下來通過一個案例來實現(xiàn)圖片上傳的功能。如下圖所示。原圖:文件上傳.bmp
5.2圆仔、服務端程序
首先編寫服務器端程序垃瞧,用來接收圖片。
代碼示例:
/*
* 文件上傳? 服務器端
*
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1,創(chuàng)建服務器坪郭,等待客戶端連接
ServerSocket serverSocket = new ServerSocket(8888);
Socket clientSocket = serverSocket.accept();
//顯示哪個客戶端Socket連接上了服務器
InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址對象
String ip = ipObject.getHostAddress(); //得到IP地址字符串
System.out.println("小樣个从,抓到你了,連接我M嵛帧嗦锐!" + "IP:" + ip);
//7,獲取Socket的輸入流
InputStream in = clientSocket.getInputStream();
//8,創(chuàng)建目的地的字節(jié)輸出流? D:\\upload\\192.168.74.58(1).jpg
BufferedOutputStream fileOut =new BufferedOutputStream(new FileOutputStream("D:\\upload\\192.168.74.58(1).jpg"));
//9,把Socket輸入流中的數(shù)據(jù),寫入目的地的字節(jié)輸出流中
byte[] buffer = new byte[1024];
int len = -1;
while((len = in.read(buffer)) != -1){
//寫入目的地的字節(jié)輸出流中
fileOut.write(buffer, 0, len);
}
//-----------------反饋信息---------------------
//10,獲取Socket的輸出流, 作用:寫反饋信息給客戶端
OutputStream out = clientSocket.getOutputStream();
//11,寫反饋信息給客戶端
out.write("圖片上傳成功".getBytes());
out.close();
fileOut.close();
in.close();
clientSocket.close();
//serverSocket.close();
}
}
5.3沪曙、客戶端
編寫客戶端奕污,完成上傳圖片
代碼示例:
/*
* 文件上傳 客戶端
*
* public void shutdownOutput()? 禁用此Socket的輸出流,間接的相當于告知了服務器數(shù)據(jù)寫入完畢
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//2,創(chuàng)建客戶端Socket,連接服務器
Socket socket = new Socket("192.168.74.58", 8888);
//3,獲取Socket流中的輸出流液走,功能:用來把數(shù)據(jù)寫到服務器
OutputStream out = socket.getOutputStream();
//4,創(chuàng)建字節(jié)輸入流菊值,功能:用來讀取數(shù)據(jù)源(圖片)的字節(jié)
BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream("D:\\NoDir\\test.jpg"));
//5,把圖片數(shù)據(jù)寫到Socket的輸出流中(把數(shù)據(jù)傳給服務器)
byte[] buffer = new byte[1024];
int len = -1;
while ((len = fileIn.read(buffer)) != -1){
//把數(shù)據(jù)寫到Socket的輸出流中
out.write(buffer, 0, len);
}
//6,客戶端發(fā)送數(shù)據(jù)完畢,結(jié)束Socket輸出流的寫入操作育灸,告知服務器端
socket.shutdownOutput();
//-----------------反饋信息---------------------
//12,獲取Socket的輸入流? 作用: 讀反饋信息
InputStream in = socket.getInputStream();
//13,讀反饋信息
byte[] info = new byte[1024];
//把反饋信息存儲到info數(shù)組中腻窒,并記錄字節(jié)個數(shù)
int length = in.read(info);
//顯示反饋結(jié)果
System.out.println( new String(info, 0, length) );
//關(guān)閉流
in.close();
fileIn.close();
out.close();
socket.close();
}
}
6、文件上傳案例多線程版本
6.1磅崭、案例圖解
實現(xiàn)服務器端可以同時接收多個客戶端上傳的文件儿子。
6.2、修改服務器端代碼
代碼示例:
/*
* 文件上傳多線程版本, 服務器端
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1,創(chuàng)建服務器砸喻,等待客戶端連接
ServerSocket serverSocket = new ServerSocket(6666);
//實現(xiàn)多個客戶端連接服務器的操作
while(true){
final Socket clientSocket = serverSocket.accept();
//啟動線程柔逼,完成與當前客戶端的數(shù)據(jù)交互過程
new Thread(){
public void run() {
try{
//顯示哪個客戶端Socket連接上了服務器
InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址對象
String ip = ipObject.getHostAddress(); //得到IP地址字符串
System.out.println("小樣,抓到你了割岛,連接我S涫省!" + "IP:" + ip);
//7,獲取Socket的輸入流
InputStream in = clientSocket.getInputStream();
//8,創(chuàng)建目的地的字節(jié)輸出流? D:\\upload\\192.168.74.58(1).jpg
BufferedOutputStream fileOut =new BufferedOutputStream(new FileOutputStream("D:\\upload\\"+ip+"("+System.currentTimeMillis()+").jpg"));
//9,把Socket輸入流中的數(shù)據(jù)癣漆,寫入目的地的字節(jié)輸出流中
byte[] buffer = new byte[1024];
int len = -1;
while((len = in.read(buffer)) != -1){
//寫入目的地的字節(jié)輸出流中
fileOut.write(buffer, 0, len);
}
//-----------------反饋信息---------------------
//10,獲取Socket的輸出流, 作用:寫反饋信息給客戶端
OutputStream out = clientSocket.getOutputStream();
//11,寫反饋信息給客戶端
out.write("圖片上傳成功".getBytes());
out.close();
fileOut.close();
in.close();
clientSocket.close();
} catch(IOException e){
e.printStackTrace();
}
};
}.start();
}
//serverSocket.close();
}
}