第3章 TCP協議
區別在於,UDP中只有發送端和接收端,不區分客戶端與服務器端,計算機之間可以任意地發送數據。
而TCP通信是嚴格區分客戶端與服務器端的,在通信時,必須先由客戶端去連接服務器端才能實現通信,服務器端不可以主動連接客戶端,並且服務器端程序需要事先啟動,等待客戶端的連接。
在JDK中提供了兩個類用於實現TCP程序,一個是ServerSocket類,用於表示服務器端,一個是Socket類,用於表示客戶端。
通信時,首先創建代表服務器端的ServerSocket對象,該對象相當於開啟一個服務,並等待客戶端的連接,然後創建代表客戶端的Socket對象向服務器端發出連接請求,服務器端響應請求,兩者建立連接開始通信。
通過前面的學習知道,在開發TCP程序時,首先需要創建服務器端程序。JDK的java.net包中提供了一個ServerSocket類,該類的實例對象可以實現一個服務器段的程序。通過查閱API文檔可知,ServerSocket類提供了多種構造方法,接下來就對ServerSocket的構造方法進行逐一地講解。
使用該構造方法在創建ServerSocket對象時,就可以將其綁定到一個指定的端口號上(參數port就是端口號)。
接下來學習一下ServerSocket的常用方法,如表所示。
ServerSocket對象負責監聽某臺計算機的某個端口號,在創建ServerSocket對象後,需要繼續調用該對象的accept()方法,接收來自客戶端的請求。當執行了accept()方法之後,服務器端程序會發生阻塞,直到客戶端發出連接請求,accept()方法才會返回一個Scoket對象用於和客戶端實現通信,程序才能繼續向下執行。
講解了ServerSocket對象可以實現服務端程序,但只實現服務器端程序還不能完成通信,此時還需要一個客戶端程序與之交互,為此JDK提供了一個Socket類,用於實現TCP客戶端程序。
通過查閱API文檔可知Socket類同樣提供了多種構造方法,接下來就對Socket的常用構造方法進行詳細講解。
使用該構造方法在創建Socket對象時,會根據參數去連接在指定地址和端口上運行的服務器程序,其中參數host接收的是一個字符串類型的IP地址。
該方法在使用上與第二個構造方法類似,參數address用於接收一個InetAddress類型的對象,該對象用於封裝一個IP地址。
在以上Socket的構造方法中,最常用的是第一個構造方法。
方法聲明
功能描述
int getPort()
該方法返回一個int類型對象,該對象是Socket對象與服務器端連接的端口號
InetAddress getLocalAddress()
該方法用於獲取Socket對象綁定的本地IP地址,並將IP地址封裝成InetAddress類型的對象返回
void close()
該方法用於關閉Socket連接,結束本次通信。在關閉socket之前,應將與socket相關的所有的輸入/輸出流全部關閉,這是因為一個良好的程序應該在執行完畢時釋放所有的資源
InputStream getInputStream()
該方法返回一個InputStream類型的輸入流對象,如果該對象是由服務器端的Socket返回,就用於讀取客戶端發送的數據,反之,用於讀取服務器端發送的數據
OutputStream getOutputStream()
該方法返回一個OutputStream類型的輸出流對象,如果該對象是由服務器端的Socket返回,就用於向客戶端發送數據,反之,用於向服務器端發送數據
在Socket類的常用方法中,getInputStream()和getOutStream()方法分別用於獲取輸入流和輸出流。當客戶端和服務端建立連接後,數據是以IO流的形式進行交互的,從而實現通信。
接下來通過一張圖來描述服務器端和客戶端的數據傳輸,如下圖所示。
1.3 TCP協議實現1.3.1 案例代碼三:
package com.itheima_04;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/*
* 使用TCP協議發送數據
創建發送端Socket對象(創建連接)
獲取輸出流對象
發送數據
釋放資源
Socket(InetAddress address, int port)
Exception in thread "main" java.net.ConnectException: Connection refused: connect
*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
//創建發送端Socket對象(創建連接)
Socket s = new Socket(InetAddress.getByName("itheima"),10086);
//獲取輸出流對象
OutputStream os = s.getOutputStream();
//發送數據
String str = "hello tcp,im comming!!!";
os.write(str.getBytes());
//釋放資源
//os.close();
s.close();
}
}
package com.itheima_04;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 使用TCP協議接收數據
創建接收端Socket對象
監聽(阻塞)
獲取輸入流對象
獲取數據
輸出數據
釋放資源
ServerSocket:接收端,服務端Socket
ServerSocket(int port)
Socket accept()
*/
public class ServerDemo {
public static void main(String[] args) throws IOException {
//創建接收端Socket對象
ServerSocket ss = new ServerSocket(10086);
//監聽(阻塞)
Socket s = ss.accept();
//獲取輸入流對象
InputStream is = s.getInputStream();
//獲取數據
byte[] bys = new byte[1024];
int len;//用於存儲讀到的字節個數
len = is.read(bys);
//輸出數據
InetAddress address = s.getInetAddress();
System.out.println("client ---> " + address.getHostName());
System.out.println(new String(bys,0,len));
//釋放資源
s.close();
//ss.close();
}
}
1.4 TCP相關案例1.4.1 案例代碼四:
使用TCP協議發送數據,服務端將接收到的數據轉換成大寫返回給客戶端
package com.itheima_05;[/font][/align] [font=微軟雅黑]
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/*
需求:使用TCP協議發送數據,並將接收到的數據轉換成大寫返回
客戶端發出數據
服務端接收數據
服務端轉換數據
服務端發出數據
客戶端接收數據
*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
//創建客戶端Socket對象
Socket s = new Socket(InetAddress.getByName("itheima"),10010);
//獲取輸出流對象
OutputStream os = s.getOutputStream();
//發出數據
os.write("tcp,im comming again!!!".getBytes());
//獲取輸入流對象
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len;//用於存儲讀取到的字節個數
//接收數據
len = is.read(bys);
//輸出數據
System.out.println(new String(bys,0,len));
//釋放資源
s.close();
}
}
package com.itheima_05;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
//創建服務端Socket對象
ServerSocket ss = new ServerSocket(10010);
//監聽
Socket s = ss.accept();
//獲取輸入流對象
InputStream is = s.getInputStream();
//獲取數據
byte[] bys = new byte[1024];
int len;//用於存儲讀取到的字節個數
len = is.read(bys);
String str = new String(bys,0,len);
//輸出數據
System.out.println(str);
//轉換數據
String upperStr = str.toUpperCase();
//獲取輸出流對象
OutputStream os = s.getOutputStream();
//返回數據(發出數據)
os.write(upperStr.getBytes());
//釋放資源
s.close();
//ss.close();//服務端一般不關閉
}
}
1.4.2 案例代碼五:
客戶端:
1.提示用戶輸入用戶名和密碼,將用戶輸入的用戶名和密碼發送給服務端
2.接收服務端驗證完用戶名和密碼的結果
服務端:
1.接收客戶端發送過來的用戶名和密碼
2.如果用戶名不是itheima或者 密碼不是123456,就向客戶端寫入”登錄失敗”
否則向客戶端寫入登錄成功
package com.itheima_06;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/*
* 模擬用戶登錄
*/
public class ClientTest {
public static void main(String[] args) throws IOException {
//創建客戶端Socket對象
//Socket s = new Socket(InetAddress.getByName("itheima"),8888);
Socket s = new Socket("itheima",8888);
//獲取用戶名和密碼
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("請輸入用戶名:");
String username = br.readLine();
System.out.println("請輸入密碼:");
String password = br.readLine();
//獲取輸出流對象
//BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//寫出數據
out.println(username);
out.println(password);
//獲取輸入流對象
BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
//獲取服務器返回的數據
String result = serverBr.readLine();
System.out.println(result);
//釋放資源
s.close();
}
}
1.4.3 案例代碼六:
將用戶名和密碼封裝到一個User類中,提供對應的構造方法和getter/setter方法
新建一個UserDB類裏面定義一個集合,在集合中添加以下User對象
new User("zhangsan","123456");
new User("lisi","654321");
new User("itheima","itheima");
new User("admin","password");
客戶端:
1.提示用戶輸入用戶名和密碼,將用戶輸入的用戶名和密碼發送給服務端
2.接收服務端驗證完用戶名和密碼的結果
服務端:
- 服務端將客戶端發送過來的用戶名密碼封裝成User對象
- 集合中如果包括這個User對象,想客戶端寫入” 登錄成功”
否則向客戶端寫入”登錄失敗”
package com.itheima_06;[/font][/align] [font=微軟雅黑]
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerTest {
public static void main(String[] args) throws IOException {
//創建服務器端Socket對象
ServerSocket ss = new ServerSocket(8888);
//監聽
Socket s = ss.accept();
//獲取輸入流對象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//獲取用戶名和密碼
String username = br.readLine();
String password = br.readLine();
//判斷用戶名和密碼是否正確
boolean flag = false;
if("itheima".equals(username) && "123456".equals(password)) {
flag = true;
}
//獲取輸出流對象
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//返回判斷信息
if(flag) {
out.println("登陸成功");
}
else {
out.println("登陸失敗");
}
//釋放資源
s.close();
//ss.close();//服務器一般不關閉
}
}
package com.itheima_07;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/*
* 模擬用戶登錄改寫(面向對象版本)
*/
public class ClientTest {
public static void main(String[] args) throws IOException {
//創建客戶端Socket對象
Socket s = new Socket("itheima",8888);
//獲取用戶名和密碼
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("請輸入用戶名:");
String username = br.readLine();
System.out.println("請輸入密碼:");
String password = br.readLine();
//獲取輸出流對象
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//寫出數據
out.println(username);
out.println(password);
//獲取輸入流對象
BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
//獲取服務器返回的數據
String result = serverBr.readLine();
System.out.println(result);
//釋放資源
s.close();
}
}
package com.itheima_07;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
public class ServerTest {
public static void main(String[] args) throws IOException {
//創建服務器端Socket對象
ServerSocket ss = new ServerSocket(8888);
//監聽
Socket s = ss.accept();
//獲取輸入流對象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//獲取用戶名和密碼
String username = br.readLine();
String password = br.readLine();
//判斷用戶名和密碼是否正確
boolean flag = false;
/*if("itheima".equals(username) && "123456".equals(password)) {
flag = true;
}*/
List<User> users = UserDB.getUsers();
User user = new User(username,password);
if(users.contains(user)) {
//匹配成功
flag = true;
}
//獲取輸出流對象
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//返回判斷信息
if(flag) {
out.println("登陸成功");
}
else {
out.println("登陸失敗");
}
//釋放資源
s.close();
//ss.close();//服務器一般不關閉
}
}
第3章 TCP協議