TCP協(xié)議是面向連接的通信協(xié)議,即在傳輸數(shù)據(jù)前先在發(fā)送端和接收端建立邏輯連接,然后再傳輸數(shù)據(jù),它提供了兩臺(tái)計(jì)算機(jī)之間可靠無(wú)差錯(cuò)的數(shù)據(jù)傳輸。在TCP連接中必須要明確客戶端與服務(wù)器端,由客戶端向服務(wù)端發(fā)出連接請(qǐng)求,每次連接的創(chuàng)建都需要經(jīng)過“三次握手”。第一次握手,客戶端向服務(wù)器端發(fā)出連接請(qǐng)求,等待服務(wù)器確認(rèn),第二次握手,服務(wù)器端向客戶端回送一個(gè)響應(yīng),通知客戶端收到了連接請(qǐng)求,第三次握手,客戶端再次向服務(wù)器端發(fā)送確認(rèn)信息,確認(rèn)連接。整個(gè)交互過程如下圖所示。

由于TCP協(xié)議的面向連接特性,它可以保證傳輸數(shù)據(jù)的安全性,所以是一個(gè)被廣泛采用的協(xié)議,例如在下載文件時(shí),如果數(shù)據(jù)接收不完整,將會(huì)導(dǎo)致文件數(shù)據(jù)丟失而不能被打開,因此,下載文件時(shí)必須采用TCP協(xié)議。
TCP通信同UDP通信一樣,都能實(shí)現(xiàn)兩臺(tái)計(jì)算機(jī)之間的通信,通信的兩端都需要?jiǎng)?chuàng)建socket對(duì)象。
區(qū)別在于,UDP中只有發(fā)送端和接收端,不區(qū)分客戶端與服務(wù)器端,計(jì)算機(jī)之間可以任意地發(fā)送數(shù)據(jù)。
而TCP通信是嚴(yán)格區(qū)分客戶端與服務(wù)器端的,在通信時(shí),必須先由客戶端去連接服務(wù)器端才能實(shí)現(xiàn)通信,服務(wù)器端不可以主動(dòng)連接客戶端,并且服務(wù)器端程序需要事先啟動(dòng),等待客戶端的連接。
在JDK中提供了兩個(gè)類用于實(shí)現(xiàn)TCP程序,一個(gè)是ServerSocket類,用于表示服務(wù)器端,一個(gè)是Socket類,用于表示客戶端。
通信時(shí),首先創(chuàng)建代表服務(wù)器端的ServerSocket對(duì)象,該對(duì)象相當(dāng)于開啟一個(gè)服務(wù),并等待客戶端的連接,然后創(chuàng)建代表客戶端的Socket對(duì)象向服務(wù)器端發(fā)出連接請(qǐng)求,服務(wù)器端響應(yīng)請(qǐng)求,兩者建立連接開始通信。
示例(簡(jiǎn)單的傳輸字符串):
客戶端:
package cn.itcast.demo3;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
*? 實(shí)現(xiàn)TCP客戶端,連接到服務(wù)器
*? 和服務(wù)器實(shí)現(xiàn)數(shù)據(jù)交換
*? 實(shí)現(xiàn)TCP客戶端程序的類 java.net.Socket
*?
*? 構(gòu)造方法:
*? ? ? Socket(String host, int port)? 傳遞服務(wù)器IP和端口號(hào)
*? ? ? 注意:構(gòu)造方法只要運(yùn)行,就會(huì)和服務(wù)器進(jìn)行連接,連接失敗,拋出異常
*? ? ?
*? ? OutputStream? getOutputStream() 返回套接字的輸出流
*? ? ? 作用: 將數(shù)據(jù)輸出,輸出到服務(wù)器
*? ? ?
*? ? InputStream getInputStream() 返回套接字的輸入流
*? ? ? 作用: 從服務(wù)器端讀取數(shù)據(jù)
*? ? ?
*? ? 客戶端服務(wù)器數(shù)據(jù)交換,必須使用套接字對(duì)象Socket中的獲取的IO流,自己new流,不行
*/
public class TCPClient {
public static void main(String[] args)throws IOException {
//創(chuàng)建Socket對(duì)象,連接服務(wù)器
Socket socket = new Socket("127.0.0.1", 8888);
//通過客戶端的套接字對(duì)象Socket方法,獲取字節(jié)輸出流,將數(shù)據(jù)寫向服務(wù)器
OutputStream out = socket.getOutputStream();
out.write("服務(wù)器OK".getBytes());
//讀取服務(wù)器發(fā)回的數(shù)據(jù),使用socket套接字對(duì)象中的字節(jié)輸入流
InputStream in = socket.getInputStream();
byte[] data = new byte[1024];
int len = in.read(data);
System.out.println(new String(data,0,len));
socket.close();
}
}
服務(wù)端:
package cn.itcast.demo3;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
*? 實(shí)現(xiàn)TCP服務(wù)器程序
*? 表示服務(wù)器程序的類 java.net.ServerSocket
*? 構(gòu)造方法:
*? ? ServerSocket(int port) 傳遞端口號(hào)
*?
*? 很重要的事情: 必須要獲得客戶端的套接字對(duì)象Socket
*? ? Socket? accept()
*/
public class TCPServer {
public static void main(String[] args) throws IOException{
ServerSocket server = new ServerSocket(8888);
//調(diào)用服務(wù)器套接字對(duì)象中的方法accept() 獲取客戶端套接字對(duì)象
Socket socket = server.accept();
//通過客戶端套接字對(duì)象,socket獲取字節(jié)輸入流,讀取的是客戶端發(fā)送來(lái)的數(shù)據(jù)
InputStream in = socket.getInputStream();
byte[] data = new byte[1024];
int len = in.read(data);
System.out.println(new String(data,0,len));
//服務(wù)器向客戶端回?cái)?shù)據(jù),字節(jié)輸出流,通過客戶端套接字對(duì)象獲取字節(jié)輸出流
OutputStream out = socket.getOutputStream();
out.write("收到,謝謝".getBytes());
socket.close();
server.close();
}
}
示例(上傳圖片):
客戶端:
package cn.itcast.demo4;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
*? 實(shí)現(xiàn)TCP圖片上傳客戶端
*? 實(shí)現(xiàn)步驟:
*? ? 1. Socket套接字連接服務(wù)器
*? ? 2. 通過Socket獲取字節(jié)輸出流,寫圖片
*? ? 3. 使用自己的流對(duì)象,讀取圖片數(shù)據(jù)源
*? ? ? ? FileInputStream
*? ? 4. 讀取圖片,使用字節(jié)輸出流,將圖片寫到服務(wù)器
*? ? ? 采用字節(jié)數(shù)組進(jìn)行緩沖
*? ? 5. 通過Socket套接字獲取字節(jié)輸入流
*? ? ? 讀取服務(wù)器發(fā)回來(lái)的上傳成功
*? ? 6. 關(guān)閉資源
*/
public class TCPClient {
public static void main(String[] args) throws IOException{
Socket socket = new Socket("127.0.0.1", 8000);
//獲取字節(jié)輸出流,圖片寫到服務(wù)器
OutputStream out = socket.getOutputStream();
//創(chuàng)建字節(jié)輸入流,讀取本機(jī)上的數(shù)據(jù)源圖片
FileInputStream fis = new FileInputStream("c:\\t.jpg");
//開始讀寫字節(jié)數(shù)組
int len = 0 ;
byte[] bytes = new byte[1024];
while((len = fis.read(bytes))!=-1){
out.write(bytes, 0, len);
}
//給服務(wù)器寫終止序列
socket.shutdownOutput();
//獲取字節(jié)輸入流,讀取服務(wù)器的上傳成功
InputStream in = socket.getInputStream();
len = in.read(bytes);
System.out.println(new String(bytes,0,len));
fis.close();
socket.close();
}
}
服務(wù)端:
package cn.itcast.demo4;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
/*
*? TCP圖片上傳服務(wù)器
*? 1. ServerSocket套接字對(duì)象,監(jiān)聽端口8000
*? 2. 方法accept()獲取客戶端的連接對(duì)象
*? 3. 客戶端連接對(duì)象獲取字節(jié)輸入流,讀取客戶端發(fā)送圖片
*? 4. 創(chuàng)建File對(duì)象,綁定上傳文件夾
*? ? ? 判斷文件夾存在, 不存,在創(chuàng)建文件夾
*? 5. 創(chuàng)建字節(jié)輸出流,數(shù)據(jù)目的File對(duì)象所在文件夾
*? 6. 字節(jié)流讀取圖片,字節(jié)流將圖片寫入到目的文件夾中
*? 7. 將上傳成功會(huì)寫客戶端
*? 8. 關(guān)閉資源
*? ? ?
*/
public class TCPServer {
public static void main(String[] args) throws IOException{
ServerSocket server = new ServerSocket(8000);
Socket socket = server.accept();
//通過客戶端連接對(duì)象,獲取字節(jié)輸入流,讀取客戶端圖片
InputStream in = socket.getInputStream();
//將目的文件夾封裝到File對(duì)象
File upload = new File("d:\\upload");
if(!upload.exists())
upload.mkdirs();
//防止文件同名被覆蓋,從新定義文件名字
//規(guī)則:? 域名+毫秒值+6位隨機(jī)數(shù)
String filename="itcast"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
//創(chuàng)建字節(jié)輸出流,將圖片寫入到目的文件夾中? ? ? ? ? ? ? ? ? ? ? ?
FileOutputStream fos = new FileOutputStream(upload+File.separator+filename);
//讀寫字節(jié)數(shù)組
byte[] bytes = new byte[1024];
int len = 0 ;
while((len = in.read(bytes))!=-1){
fos.write(bytes, 0, len);
}
//通過客戶端連接對(duì)象獲取字節(jié)輸出流
//上傳成功寫回客戶端
socket.getOutputStream().write("上傳成功".getBytes());
fos.close();
socket.close();
server.close();
}
}
開啟子線程:
package cn.itcast.demo4;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPThreadServer {
public static void main(String[] args) throws IOException{
ServerSocket server = new ServerSocket(8000);
while(true){
//獲取到一個(gè)客戶端,必須開啟新線程
Socket socket = server.accept();
new Thread( new Upload(socket) ).start();
}
}
}
子線程具體方法(上傳):
package cn.itcast.demo4;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class Upload implements Runnable{
private Socket socket;
public Upload(Socket socket){this.socket=socket;}
public void run() {
try{
//通過客戶端連接對(duì)象,獲取字節(jié)輸入流,讀取客戶端圖片
InputStream in = socket.getInputStream();
//將目的文件夾封裝到File對(duì)象
File upload = new File("d:\\upload");
if(!upload.exists())
upload.mkdirs();
//防止文件同名被覆蓋,從新定義文件名字
//規(guī)則:? 域名+毫秒值+6位隨機(jī)數(shù)
String filename="itcast"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
//創(chuàng)建字節(jié)輸出流,將圖片寫入到目的文件夾中? ? ? ? ? ? ? ? ? ? ? ?
FileOutputStream fos = new FileOutputStream(upload+File.separator+filename);
//讀寫字節(jié)數(shù)組
byte[] bytes = new byte[1024];
int len = 0 ;
while((len = in.read(bytes))!=-1){
fos.write(bytes, 0, len);
}
//通過客戶端連接對(duì)象獲取字節(jié)輸出流
//上傳成功寫回客戶端
socket.getOutputStream().write("上傳成功".getBytes());
fos.close();
socket.close();
}catch(Exception ex){
}
}
}