Web發展中通信方式
- 簡單通信
- 不同請求
- 復雜請求
案例實操
1 簡單通信
回顧 Socket 編程給我們最大的感受,是可以在多臺電腦之間進行數據的傳輸,這就是網絡編程的開端和基礎,通過客戶端請求服務器端通信,直觀了解 Web 編程。
Server
/**
* 服務端,接收客戶端請求并給出簡單的響應
* @author Cushier
*
*/
public class Server {
public static void main(String[] args) throws IOException{
// 創建服務器,指定端口ServerSocket(int port)
ServerSocket socket = new ServerSocket(8888);
// 接收客戶端連接
Socket client = socket.accept();
System.out.println("******************");
// 獲取數據的輸入流
InputStream is = client.getInputStream();
// 使用緩沖字符輸入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg = "";
while ((msg = br.readLine()) != null) {
System.out.println(msg);
}
br.close();
}
}
Client
/**
* 客戶端:向服務器發送請求,并發送簡單的消息
* @author Cushier
*
*/
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
// 創建客戶端 必須指定服務器+端口
Socket client = new Socket("localhost", 8888);
// 發送消息 請求資源
// 獲取輸出流
OutputStream os = client.getOutputStream();
// 使用緩沖字符輸出流
BufferedWriter br = new BufferedWriter(new OutputStreamWriter(os));
// 寫出消息,發送內容
String msg = "Hello, I am Client, I need some resources";
br.write(msg);
br.close();
}
}
從上面的例子總結通信條件如下:
- 需要有服務器端(server):等待被請求,需要暴露 ip 和 port
- 需要有客戶端(client):主動發起請求,知曉服務端的 ip 和 port
- 通信規則(協議):TCP/IP 協議
ip 用于定位計算機;端口號(定位程序),用于標識進程的邏輯地址,不同進程的標志;有效端口:0~65535,其中 0~1024 由系統使用或者保留端口,開發中建議使用 1024 以上的端口。
2 不同請求
Client
/**
* 客戶端:向服務器發送請求,發送不同的請求
* @author Cushier
*
*/
public class Client {
public static void main(String[] args) throws IOException {
// 通過系統默認類型的 SocketImpl 創建未連接套接字
Socket socket = new Socket();
// 此類實現 IP 套接字地址(IP 地址 + 端口號)。它還可以是一個對(主機名 + 端口號),在此情況下,將嘗試解析主機名
SocketAddress address = new InetSocketAddress("localhost", 8898);
// 將此套接字連接到服務器,并指定一個超時值。或者不指定超時時間
socket.connect(address, 1000);
OutputStream os = socket.getOutputStream();
os.write("time".getBytes());
os.flush();
socket.close();
}
}
Server
/**
* 服務端
* public class ServerSocketextends Object:此類實現服務器套接字。
* 服務器套接字等待請求通過網絡傳入。
* 它基于該請求執行某些操作,然后可能向請求者返回結果。
*
* @author Cushier
*
*/
public class Server {
public static void main(String[] args) throws IOException {
// 創建綁定到特定端口的服務器套接字。
ServerSocket server = new ServerSocket(8898);
// Socket accept() 偵聽并接受到此套接字的連接。
Socket client = server.accept();
System.out.println("接收到連接");
InputStream is = client.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] req = new byte[1024];
// 接收客戶端請求
int len = bis.read(req);
String reqStr = new String(req, 0, len);
System.out.println(reqStr);
if (reqStr.equals("money")) {
System.out.println("here's the money");
} else if (reqStr.equals("time")) {
System.out.println("you have so much time");
}
client.close();
server.close();
}
}
3 復雜請求
Client
/**
* 客戶端
*
* @author Cushier
*
*/
public class Client {
public static void main(String[] args) throws IOException {
// 通過系統默認類型的 SocketImpl 創建未連接套接字
Socket socket = new Socket();
// 此類實現 IP 套接字地址(IP 地址 + 端口號)。它還可以是一個對(主機名 + 端口號),在此情況下,將嘗試解析主機名
SocketAddress address = new InetSocketAddress("localhost", 8898);
// 將此套接字連接到服務器,并指定一個超時值。或者不指定超時時間
socket.connect(address, 1000);
OutputStream os = socket.getOutputStream();
os.write("money".getBytes());
os.flush();
// 接收響應,顯示結果
InputStream is = socket.getInputStream();
byte[] result = new byte[1024];
int len = is.read(result);
String resultStr = new String(result, 0, len);
System.out.println(resultStr);
socket.close();
}
}
Server
/**
* 服務端
* @author Cushier
*
*/
public class Server2 {
public static void main(String[] args) throws IOException {
// 創建綁定到特定端口的服務器套接字。
ServerSocket server = new ServerSocket(8898);
// Socket accept() 偵聽并接受到此套接字的連接。
Socket client = server.accept();
System.out.println("接收到連接");
InputStream is = client.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] req = new byte[1024];
// 接收客戶端請求
int len = bis.read(req);
String reqStr = new String(req, 0, len);
System.out.println(reqStr);
// 將接收到的請求封裝成對象,傳送給請求的類
MyRequest request = new MyRequest();
MyResponse response = new MyResponse();
OutputStream os = client.getOutputStream();
if (reqStr.equals("money")) {
// 根據請求的信息,構造處理的類
MyServlet s1 = new ServletMoney();
s1.service(request, response);
// 通過client的響應,將結果響應回客戶端
os.write("here's the money".getBytes());
os.flush();
} else if (reqStr.equals("time")) {
// 根據請求的信息,構造處理的類
MyServlet s2 = new ServletTime();
s2.service(request, response);
// 通過client的響應,將結果響應回客戶端
os.write("you have somuch time".getBytes());
os.flush();
}
client.close();
server.close();
}
}
/*
* 我是一個有要求的人,你請求的這個資源必須是滿足我要求格式的類,作用:防止混亂,方便調用 這是我的標準
*/
interface MyServlet {
void service(MyRequest req, MyResponse resp);
}
class ServletMoney implements MyServlet {
@Override
public void service(MyRequest req, MyResponse resp) {
// 做出力所能及的處理
}
}
class ServletTime implements MyServlet {
@Override
public void service(MyRequest req, MyResponse resp) {
// 做出力所能及的處理
}
}
/*
* 請求信息都按規律封裝在該對象
*/
class MyRequest {
}
class MyResponse {
}
隨著客戶需求越來越復雜,需要的功能越來越多,我們的服務器端需要處理的請求越來越多,需要區分不同的請求,還需要按照不同請求進行請求數據的提取以及資源的分配和運算還有邏輯的處理,最后還需要響應給客戶端,這就使得服務器端代碼越來越復雜,實現越來越困難。
根據以往的經驗,雙方進行通信只需要遵循一定的規則就可以很明確地知道各部分數據的含義,于是出現了網絡更上層的應用協議(后面講的 HTTP 協議),規定服務器端和客戶端通信的規則。
客戶端請求服務器端和服務器端響應客戶端,都按照固定的規則,那么接收請求和響應數據這部分操作就可以固定下來,交給特定的一段代碼來執行,從而減少服務器端的代碼量,于是出現了接下來說的服務器。
擴展
服務器的出現
當客戶端請求的資源越來越豐富,需求越來越復雜,程序的核心就應該放在解決業務和計算響應數據上,于是出現了服務器統一接收客戶端數據進行處理并分發到不同的資源,由各個資源進行處理,最后結果交由服務器響應。
從上面的描述可以發現,現在所說的服務器只是負責接收請求,對請求進行分發,以及最后將獲取的數據進行相應的固定框架,至于數據怎么計算得出還得根據具體的業務需求編寫(填充)代碼。在沒有業務需求的情況下就能將服務器準備出來,現在市面上的服務器有很多,比較常用的有:Tomcat、JBOOS、IBM 的 WebSphere、BEA的 WebLogic 以及 Apache 等。