1、基礎(chǔ)簡(jiǎn)介
TCP協(xié)議是面向連接的篇恒、可靠的扶檐,在數(shù)據(jù)傳輸之前建立連接,以此保證數(shù)據(jù)的可靠性胁艰;其次款筑,TCP協(xié)議是有序的,并且以字節(jié)流的形式發(fā)送數(shù)據(jù)腾么。在Java中基于TCP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)通訊的類有兩個(gè)奈梳,分別是用于客戶端的Socket類及用于服務(wù)器端的ServerSocket類,Socket通訊模型如下:

兩臺(tái)主機(jī)若想實(shí)現(xiàn)通訊解虱,就必然存在著一臺(tái)作為服務(wù)器端(Server)攘须,一臺(tái)作為客戶端(Client),首先在服務(wù)器端建立一個(gè)ServerSocket殴泰,并且綁定相應(yīng)的端口進(jìn)行監(jiān)聽于宙,等待客戶端的連接浮驳;之后在客戶端創(chuàng)建Socket并向服務(wù)器端發(fā)送請(qǐng)求,此時(shí)服務(wù)器端收到客戶端發(fā)送的請(qǐng)求捞魁,并創(chuàng)建連接Socket用于與客戶端的Socket進(jìn)行通信至会;通信的過程就是借助InputStream以及OutputStream實(shí)現(xiàn)數(shù)據(jù)的發(fā)送、接收谱俭、響應(yīng)等等奉件,按照相關(guān)的協(xié)議對(duì)Socket進(jìn)行讀與寫的操作,在通信結(jié)束后需要關(guān)閉雙方的Socket及相關(guān)資源旺上,即輸入與輸出流等瓶蚂,以此斷開通信,以上便是基于TCP協(xié)議的Socket通訊進(jìn)行的整個(gè)過程宣吱。
2窃这、通過編程實(shí)現(xiàn)“用戶登錄功能”之客戶端
實(shí)現(xiàn)用戶登錄,實(shí)際上就是用戶信息征候,例如用戶名及密碼杭攻,從客戶端向服務(wù)器端發(fā)送,被服務(wù)器端接收后疤坝,再向客戶端進(jìn)行響應(yīng)兆解,例如回復(fù)歡迎登陸等信息的過程。
創(chuàng)建客戶端的具體步驟:
- 創(chuàng)建Socket對(duì)象跑揉,指明需要連接的服務(wù)器地址和端口號(hào)
- 建立連接后锅睛,通過輸出流向服務(wù)器端發(fā)送請(qǐng)求信息
- 通過輸入流獲取服務(wù)器端響應(yīng)的信息
- 關(guān)閉相關(guān)資源
示例代碼如下:
import java.io.IOException;
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 {
/*
* 創(chuàng)建客戶端Socket,指定服務(wù)器地址和端口
* 可以理解為客戶端與服務(wù)器端建立一條通道
* 該通道就是Socket流历谍,也就是客戶端對(duì)象
* Socket流中既有字節(jié)輸入流现拒,也有字節(jié)輸出流
* 本案例中,服務(wù)器端和客戶端都在本地同一臺(tái)主機(jī)中
* 因此服務(wù)器地址可填“127.0.0.1”或“l(fā)ocalhost”
* 此時(shí)會(huì)出現(xiàn)異常望侈,使用try-catch塊捕獲該異常
*/
Socket socket = new Socket("localhost", 2333);
// 獲取輸出流印蔬,向服務(wù)器端發(fā)送信息
OutputStream os = socket.getOutputStream();
// 將字節(jié)輸出流包裝為打印流
PrintWriter pw = new PrintWriter(os);
// 編寫要向服務(wù)器端發(fā)送的信息
pw.write("用戶名:admin;密碼:123456");
// 通過刷新實(shí)現(xiàn)發(fā)送
pw.flush();
// 禁用輸出流
socket.shutdownOutput();
// 關(guān)閉相關(guān)資源
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3脱衙、通過編程實(shí)現(xiàn)“用戶登錄功能”之服務(wù)器端
創(chuàng)建服務(wù)器端的具體步驟:
- 創(chuàng)建ServerSocket對(duì)象侥猬,并綁定監(jiān)聽端口
- 調(diào)用accept()方法進(jìn)行監(jiān)聽,等待客戶端的連接請(qǐng)求
- 與客戶端建立連接后捐韩,通過輸入流讀取客戶端發(fā)送的請(qǐng)求信息
- 通過輸出流向客戶端發(fā)送響應(yīng)信息
- 關(guān)閉相關(guān)資源
示例代碼如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
/*
* 創(chuàng)建服務(wù)器端Socket退唠,即ServerSocket,并綁定監(jiān)聽端口
* 指定端口時(shí)荤胁,默認(rèn)指定1023之后的端口號(hào)
* 此時(shí)會(huì)出現(xiàn)異常铜邮,使用try-catch塊捕獲該異常
*/
ServerSocket serverSocket = new ServerSocket(2333);
/*
* 調(diào)用accept()方法進(jìn)行監(jiān)聽,等待客戶端的連接
* 實(shí)際上就是獲取之前創(chuàng)建的客戶端Socket對(duì)象
* 這就保證了服務(wù)器端與客戶端使用的是同一個(gè)Socket流
* 為了便于查看結(jié)果,添加一句輸出
*/
System.out.println("****服務(wù)期即將啟動(dòng)松蒜,正在等待客戶端連接****");
// 一旦調(diào)用該方法,服務(wù)器端就會(huì)進(jìn)入阻塞狀態(tài)已旧,等待客戶端連接
Socket socket = serverSocket.accept();
// 獲取輸入流秸苗,讀取客戶端信息
InputStream is = socket.getInputStream();
// 將字節(jié)輸入流轉(zhuǎn)換為字符輸入流
InputStreamReader isr = new InputStreamReader(is);
// 為字符輸入流添加緩沖
BufferedReader br = new BufferedReader(isr);
String info = null;
// 循環(huán)讀取客戶端信息
while ((info = br.readLine()) != null) {
System.out.println("服務(wù)器端——讀取到客戶端提交如下信息:" + info);
}
// 將輸入流置于末尾
socket.shutdownInput();
// 關(guān)閉相關(guān)資源
br.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
此時(shí)就可以啟動(dòng)這兩段代碼來檢驗(yàn)是否能夠?qū)崿F(xiàn)“用戶登錄”這一功能,要注意的是必須首先啟動(dòng)服務(wù)器端运褪,結(jié)果如下:

之后在運(yùn)行客戶端代碼惊楼,服務(wù)器端顯示結(jié)果如下:

可以看到服務(wù)器端成功接收了來自客戶端的登錄請(qǐng)求。
注意:
- 關(guān)于shutdownInput()方法及shutdownOutput()方法的理解如下:編寫程序的大多數(shù)的時(shí)候是可以直接使用Socket類或輸入輸出流的close()方法關(guān)閉網(wǎng)絡(luò)連接秸讹,但有時(shí)會(huì)出現(xiàn)希望只關(guān)閉輸入輸出流檀咙,并不關(guān)閉網(wǎng)絡(luò)連接的情況。這就需要用到Socket類的shutdownInput()方法及shutdownOutput()方法璃诀,這兩個(gè)方法只會(huì)關(guān)閉相應(yīng)的輸入弧可、輸出流,而并不會(huì)同時(shí)關(guān)閉網(wǎng)絡(luò)連接劣欢,一般只有在確定不再需要網(wǎng)絡(luò)連接的時(shí)候棕诵,才會(huì)使用Socket類或輸入輸出流的close()方法關(guān)閉相關(guān)資源。
- 關(guān)于打印流的相關(guān)內(nèi)容可以到Java IO流查看凿将;
關(guān)于端口號(hào)的相關(guān)內(nèi)容可以到TCP/IP四層模型中第四部分“傳輸層”查看校套。
4、完善“用戶登錄功能”之實(shí)現(xiàn)服務(wù)器端的響應(yīng)
之前僅僅實(shí)現(xiàn)了客戶端向服務(wù)器端提交“用戶信息”牧抵,但服務(wù)器端并沒有向客戶端發(fā)送反饋信息笛匙,進(jìn)行響應(yīng),因此可在服務(wù)器端添加輸出流犀变,在客戶端添加輸入流妹孙,修改后的服務(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.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
/*
* 創(chuàng)建服務(wù)器端Socket,即ServerSocket弛作,并綁定監(jiān)聽端口
* 指定端口時(shí)涕蜂,默認(rèn)指定1023之后的端口號(hào)
* 此時(shí)會(huì)出現(xiàn)異常,使用try-catch塊捕獲該異常
*/
ServerSocket serverSocket = new ServerSocket(2333);
/*
* 調(diào)用accept()方法進(jìn)行監(jiān)聽映琳,等待客戶端的連接
* 為了便于查看結(jié)果机隙,添加一句輸出
*/
System.out.println("****服務(wù)期即將啟動(dòng),正在等待客戶端連接****");
// 一旦調(diào)用該方法萨西,服務(wù)器端就會(huì)進(jìn)入阻塞狀態(tài)有鹿,等待客戶端連接
Socket socket = serverSocket.accept();
// 獲取輸入流,讀取客戶端信息
InputStream is = socket.getInputStream();
// 將字節(jié)輸入流轉(zhuǎn)換為字符輸入流
InputStreamReader isr = new InputStreamReader(is);
// 為字符輸入流添加緩沖
BufferedReader br = new BufferedReader(isr);
String info = null;
// 循環(huán)讀取客戶端信息
while ((info = br.readLine()) != null) {
System.out.println("服務(wù)器端——讀取到客戶端提交如下信息:" + info);
}
// 將輸入流置于末尾
socket.shutdownInput();
// 獲取輸出流谎脯,響應(yīng)客戶端的請(qǐng)求
OutputStream os = socket.getOutputStream();
// 將字節(jié)輸出流包裝為打印流
PrintWriter pw = new PrintWriter(os);
// 編寫服務(wù)器端的響應(yīng)信息
pw.write("歡迎登錄葱跋!");
// 通過刷新實(shí)現(xiàn)發(fā)送
pw.flush();
// 關(guān)閉相關(guān)資源
pw.close();
os.close();
br.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改后的客戶端示例代碼如下:
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 {
/*
* 創(chuàng)建客戶端Socket,指定服務(wù)器地址和端口
* 本案例中,服務(wù)器端和客戶端都在本地同一臺(tái)主機(jī)中
* 因此服務(wù)器地址可填“127.0.0.1”或“l(fā)ocalhost”
* 此時(shí)會(huì)出現(xiàn)異常娱俺,使用try-catch塊捕獲該異常
*/
Socket socket = new Socket("localhost", 2333);
// 獲取輸出流稍味,向服務(wù)器端發(fā)送信息
OutputStream os = socket.getOutputStream();
// 將字節(jié)輸出流包裝為打印流
PrintWriter pw = new PrintWriter(os);
// 編寫要向服務(wù)器端發(fā)送的信息
pw.write("用戶名:admin;密碼:123456");
// 通過刷新實(shí)現(xiàn)發(fā)送
pw.flush();
// 禁用輸出流
socket.shutdownOutput();
// 獲取輸入流荠卷,讀取服務(wù)器端的響應(yīng)信息
InputStream is = socket.getInputStream();
// 將字節(jié)輸入流轉(zhuǎn)換為字符輸入流
InputStreamReader isr = new InputStreamReader(is);
// 為字符輸入流添加緩沖
BufferedReader br = new BufferedReader(isr);
String info = null;
// 循環(huán)讀取客戶端信息
while ((info = br.readLine()) != null) {
System.out.println("客戶端——讀取到服務(wù)器端反饋如下信息:" + info);
}
// 關(guān)閉相關(guān)資源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
此時(shí)啟動(dòng)服務(wù)器端模庐,結(jié)果如下:

之后在運(yùn)行客戶端代碼,服務(wù)器端顯示結(jié)果如下:

此時(shí)客戶端結(jié)果如下:

可以看到客戶端成功接收了來自服務(wù)器端的響應(yīng)信息油宜。
5掂碱、使用多線程實(shí)現(xiàn)服務(wù)器與多客戶端進(jìn)行通信
之前的案例僅僅實(shí)現(xiàn)了一臺(tái)客戶端與服務(wù)器進(jìn)行通信的過程,但在實(shí)際生活中慎冤,往往是在服務(wù)器上運(yùn)行一個(gè)永久的程序疼燥,而且可以與多個(gè)客戶端進(jìn)行通信,并且可以接收多個(gè)客戶端的請(qǐng)求蚁堤,提供相應(yīng)的服務(wù)醉者。
關(guān)于多線程的相關(guān)內(nèi)容可以到已完結(jié)的深入淺出Java多線程專題查看。
使用多線程實(shí)現(xiàn)的具體步驟:
- 創(chuàng)建服務(wù)器端ServerSocket對(duì)象违寿,循環(huán)調(diào)用accept()方法等待客戶端的連接
- 客戶端創(chuàng)建socket并請(qǐng)求與服務(wù)器端連接
- 服務(wù)器端接受客戶端的請(qǐng)求湃交,創(chuàng)建socket與該客戶端建立專線連接
- 建立專線連接的服務(wù)器端socket與客戶端socket在一個(gè)單獨(dú)的線程上對(duì)話
- 之后服務(wù)器端繼續(xù)等待新的客戶端連接
首先創(chuàng)建單獨(dú)的線程類,用來處理客戶端的請(qǐng)求藤巢,示例代碼如下:
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ù)器線程處理類
* 繼承Thread類
* 每當(dāng)有客戶端發(fā)送請(qǐng)求
* 服務(wù)器端都會(huì)創(chuàng)建一個(gè)Socket與之通信
*/
public class ServerThread extends Thread {
// 創(chuàng)建與本線程相關(guān)的Socket
Socket socket = null;
// 使用構(gòu)造方法初始化Socket
public ServerThread(Socket socket) {
this.socket = socket;
}
// 線程執(zhí)行操作搞莺,響應(yīng)客戶端的請(qǐng)求,重寫父類的run()方法
public void run() {
// 初始化輸入輸出流
InputStream is = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
try {
is = socket.getInputStream();
// 將字節(jié)輸入流轉(zhuǎn)換為字符輸入流
InputStreamReader isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String info = null;
// 循環(huán)讀取客戶端信息
while ((info = br.readLine()) != null) {
System.out.println("服務(wù)器端——讀取到客戶端提交如下信息:" + info);
}
// 將輸入流置于末尾
socket.shutdownInput();
os = socket.getOutputStream();
pw = new PrintWriter(os);
// 編寫服務(wù)器端的響應(yīng)信息
pw.write("歡迎登錄掂咒!");
// 通過刷新實(shí)現(xiàn)發(fā)送
pw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
/*
* 為了保證相關(guān)資源一定能夠關(guān)閉 將其放置在finally代碼塊中
* 并增加if判斷條件驗(yàn)證是否為空才沧,避免報(bào)錯(cuò)
* 最后再統(tǒng)一用try-catch塊包圍處理異常
*/
try {
if (pw != null) {
pw.close();
}
if (os != null) {
os.close();
}
if (br != null) {
br.close();
}
if (is != null) {
is.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
之后創(chuàng)建服務(wù)器端,示例代碼如下:
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
/*
* 創(chuàng)建服務(wù)器端Socket绍刮,即ServerSocket温圆,并綁定監(jiān)聽端口
* 指定端口時(shí),默認(rèn)指定1023之后的端口號(hào)
* 此時(shí)會(huì)出現(xiàn)異常孩革,使用try-catch塊捕獲該異常
*/
ServerSocket serverSocket = new ServerSocket(2333);
// 為了便于查看結(jié)果岁歉,添加一句輸出
System.out.println("****服務(wù)期即將啟動(dòng),正在等待客戶端連接****");
// 初始化Socket
Socket socket = null;
// 記錄連接過的客戶端數(shù)量
int count = 0;
// 循環(huán)監(jiān)聽等待客戶端的連接
while (true) {
/*
* 調(diào)用accept()方法進(jìn)行監(jiān)聽膝蜈,一旦調(diào)用該方法
* 服務(wù)器端就會(huì)進(jìn)入阻塞狀態(tài)锅移,等待客戶端連接
*/
socket = serverSocket.accept();
// 創(chuàng)建一個(gè)新的線程
ServerThread serverThread = new ServerThread(socket);
/*
* 設(shè)置線程優(yōu)先級(jí),范圍是[1,10]饱搏,默認(rèn)是5
* 未設(shè)置線程優(yōu)先級(jí)可能會(huì)導(dǎo)致運(yùn)行時(shí)速度非常慢
* 可降低優(yōu)先級(jí)
*/
serverThread.setPriority(4);
// 啟動(dòng)線程
serverThread.start();
// 統(tǒng)計(jì)連接過的客戶端的數(shù)量
count++;
System.out.println("已累計(jì)服務(wù)" + count + "臺(tái)客戶端非剃!");
// 獲取連接的客戶端的IP地址
InetAddress address = socket.getInetAddress();
System.out.println("當(dāng)前客戶端的IP地址是:" + address.getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
最后創(chuàng)建客戶端,示例代碼如下:
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 {
/*
* 創(chuàng)建客戶端Socket推沸,指定服務(wù)器地址和端口
* 本案例中备绽,服務(wù)器端和客戶端都在本地同一臺(tái)主機(jī)中
* 因此服務(wù)器地址可填“127.0.0.1”或“l(fā)ocalhost”
* 此時(shí)會(huì)出現(xiàn)異常券坞,使用try-catch塊捕獲該異常
*/
Socket socket = new Socket("localhost", 2333);
// 獲取輸出流,向服務(wù)器端發(fā)送信息
OutputStream os = socket.getOutputStream();
// 將字節(jié)輸出流包裝為打印流
PrintWriter pw = new PrintWriter(os);
// 編寫要向服務(wù)器端發(fā)送的信息
pw.write("用戶名:admin肺素;密碼:123456");
// 通過刷新實(shí)現(xiàn)發(fā)送
pw.flush();
// 禁用輸出流
socket.shutdownOutput();
// 獲取輸入流恨锚,讀取服務(wù)器端的響應(yīng)信息
InputStream is = socket.getInputStream();
// 將字節(jié)輸入流轉(zhuǎn)換為字符輸入流
InputStreamReader isr = new InputStreamReader(is);
// 為字符輸入流添加緩沖
BufferedReader br = new BufferedReader(isr);
String info = null;
// 循環(huán)讀取客戶端信息
while ((info = br.readLine()) != null) {
System.out.println("客戶端——讀取到服務(wù)器端反饋如下信息:" + info);
}
// 關(guān)閉相關(guān)資源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
此時(shí)啟動(dòng)服務(wù)器端,結(jié)果如下:

之后在運(yùn)行客戶端代碼倍靡,服務(wù)器端顯示結(jié)果如下:

之后修改客戶端的用戶名為“Tom”眠冈,密碼為“654321”,模擬多客戶端登錄菌瘫,重新運(yùn)行客戶端代碼,服務(wù)器端結(jié)果如下:

可見成功實(shí)現(xiàn)了服務(wù)器端與多個(gè)客戶端進(jìn)行通信布卡。
版權(quán)聲明:歡迎轉(zhuǎn)載雨让,歡迎擴(kuò)散,但轉(zhuǎn)載時(shí)請(qǐng)標(biāo)明作者以及原文出處忿等,謝謝合作栖忠! ↓↓↓