1 網絡編程簡介
所謂網絡編程指的就是多臺主機之間的數據通訊操作。
網絡的核心定義在於:由兩臺以上的電腦就稱爲網絡。實際上在世界上產生第一臺電腦之後就有人開始去思考如何可以將更多的電腦生產出來並且進行有效的連接。
網絡連接的目的不僅僅是爲了進行電腦的串聯,更多的情況下是爲了進行彼此之間的數據通信,包括現在所謂的網絡遊戲實質上還是網絡通信的問題,而在通訊的實現上就產生了一系列的處理協議:IP、TCP、UDP等等,也就是說,所謂的網絡編程實際上實現的就是一個數據的通訊操作而已,只不過這個通訊的操作需要分爲客戶端與服務器端。
於是針對網絡程序的開發就有了兩種模型:
(1)C/S(Client/Server、客戶端與服務器端):要開發出兩套程序,一套程序爲客戶端,另外一套程序爲服務器端,如果現在服務器端發生了改變之後客戶端也應該進行更新處理,這種開發可以由開發者自定義傳輸協議,並且使用一些比較私密的端口,所以安全性是比較高的,但是開發與維護成本比較高;
(2)B/S(Browser/Server、瀏覽器與服務器端):只開發一套服務器端的程序,而後利用瀏覽器作爲客戶端進行訪問,這種開發與維護的成本較低(只有一套程序),但是由於其使用的是公共的HTTP協議並且使用的是公共的80端口,所以其安全性相對較差,現在的開發基本上以“B/S”結構爲主。
本次要學習的網絡編程主要就是C/S程序模型,其分爲兩種開發:TCP(可靠的數據連接)、UDP(不可靠的數據連接)。
2 Echo程序模型
TCP的程序開發是網絡程序的最基本的開發模型,其核心的特點是使用兩個類實現數據的交互處理:ServerSocket(服務器端)、Socket(客戶端)。
ServerSocket的主要目的是設置服務器的監聽端口,而Socket需要指明要連接的服務器地址與端口。下面實現一個最簡單程序操作,即Echo程序實現。
範例:實現服務器端的操作
package org.lks.demo.server;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TCPServer {
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(9000);
System.out.println("waiting for connect ....");
Socket client = server.accept();
Scanner scan = new Scanner(client.getInputStream());
scan.useDelimiter("\n");
PrintStream out = new PrintStream(client.getOutputStream());
boolean flag = true;
String str = "";
while(flag) {
if(scan.hasNext()) {
str = scan.next().trim();
}
if("byebye".equals(str)) {
out.print("byebyebye...");
flag = false;
}else {
out.println("[echo]" + str);
}
}
scan.close();
out.close();
client.close();
server.close();
}
}
範例:實現客戶端的操作
package org.lks.demo.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws Exception{
Socket client = new Socket("localhost", 9000);
System.out.println("success!");
Scanner scan = new Scanner(client.getInputStream());
scan.useDelimiter("\n");
PrintStream out = new PrintStream(client.getOutputStream());
boolean flag = true;
while(flag) {
String str = input("please the info: ").trim();
out.println(str);
if(scan.hasNext()) {
System.out.println(scan.next());
}
if("byebye".equals(str)) {
flag = false;
}
}
scan.close();
out.close();
client.close();
}
private static String input(String prompt) throws Exception{
System.out.println(prompt);
String result = INPUT.readLine();
return result;
}
}
如果此時需要對程序進行測試,最好的方法是直接使用telnet命令完成,但是此命令在Windows7之後變爲了默認不開啓的狀態,所以如果要使用則必須單獨啓用此命令。
在服務器端開啓的情況下通過telnet指令輸入:open localhost 80
此時就實現了一個最基礎的客戶端與服務器端之間的數據通訊操作。
3 BIO處理模型
現在儘管已經實現了一個標準的網絡程序開發,但是在整個的開發過程之中本程序存在有嚴重的性能缺陷,因爲該服務器只能夠爲一個線程提供Echo服務,如果說現在的服務器需要有多人進行連接訪問的時候那麼其他的使用將無法連接(等待連接)。
所以現在就可以發現單線程的服務器開發本身就是一種不合理的做法,那麼此時最好的解決方案就是將每一個連接到服務器上的客戶端都通過一個線程對象來進行處理,即:服務器上啓動多個線程,每個線程單獨爲每一個客戶端實現Echo服務支持。
範例:修改服務器端程序
package org.lks.demo.server;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TCPServer {
private static class SocketThread implements Runnable{
private Socket client = null;
private Scanner scan = null;
private PrintStream out = null;
private boolean flag = true;
public SocketThread(Socket client) throws Exception{
this.client = client;
this.scan = new Scanner(client.getInputStream());
this.scan.useDelimiter("\n");
this.out = new PrintStream(client.getOutputStream());
}
@Override
public void run(){
String str = "";
while(this.flag) {
if(this.scan.hasNext()) {
str = this.scan.next().trim();
}
if("byebye".equals(str)) {
this.out.print("byebyebye...");
this.flag = false;
}else {
this.out.println("[echo]" + str);
}
}
try {
this.scan.close();
this.out.close();
this.client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(9000);
System.out.println("waiting for connect ....");
boolean flag = true;
while(flag) {
Socket client = server.accept();
if(client.isConnected()) {
new Thread(new SocketThread(client)).start();
}
}
server.close();
}
}
如果你在這類的代碼裏面再追加一些集合的數據控制,實際上就可以實現一個80年代的聊天室了。
4 UDP程序
之前所見到的都屬於TCP程序開發範疇,TCP程序最大的特點是可靠的網絡連接,但是在網絡程序開發之中還存在有一種UDP程序,基於數據報的網絡編程實現,如果想實現UDP程序需要兩個類:
(1)DatagramPacket:數據內容;
(2)DatagramSocket:網絡發送與接收。
數據報就好比發送的短消息一樣,客戶端是否接收到與發送者無關。
範例:實現一個UDP的客戶端
package org.lks.demo.client;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPClient {
public static void main(String[] args) throws Exception{ //接收數據信息
DatagramSocket client = new DatagramSocket(9000); //連接到9000端口
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length); //接收數據
System.out.println("wait info...");
client.receive(packet); //接收消息,所有的消息都在data字節數組之中
System.out.println(new String(data, 0, packet.getLength()));
client.close();
}
}
範例:實現一個UDP的服務端
package org.lks.demo.server;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPServer {
public static void main(String[] args) throws Exception{
DatagramSocket server = new DatagramSocket(9999); //連接到9000端口
String str = "lks loves hhy!"; //要發送的消息內容
DatagramPacket packet = new DatagramPacket(str.getBytes(), 0, str.length(), InetAddress.getByName("localhost"), 9000); //發送數據
server.send(packet); //發送消息
System.out.println("success!");
server.close();
}
}
UDP發送的數據一定是不可靠的,但是TCP由於需要保證可靠的連接所以所需要的服務器性能和服務器資源越多。