Java的網(wǎng)絡編程主要涉及到的內容是Socket編程,那么什么是Socket呢纬朝?簡單地說收叶,Socket,套接字共苛,就是兩臺主機之間邏輯連接的端點判没。TPC/IP協(xié)議是傳輸層協(xié)議蜓萄,主要解決數(shù)據(jù)如何在網(wǎng)絡中傳輸,而HTTP是應用層協(xié)議澄峰,主要解決如何包裝數(shù)據(jù)嫉沽。Socket,本質上就是一組接口俏竞,是對TCP/IP協(xié)議的封裝和應用(程序員層面上)绸硕。
套接字使用TCP提供了兩臺計算機之間的通信機制。 客戶端程序創(chuàng)建一個套接字胞此,并嘗試連接服務器的套接字臣咖。
當連接建立時跃捣,服務器會創(chuàng)建一個 Socket 對象漱牵。客戶端和服務器現(xiàn)在可以通過對 Socket 對象的寫入和讀取來進行通信疚漆。
java.net.Socket 類代表一個套接字酣胀,并且 java.net.ServerSocket 類為服務器程序提供了一種來監(jiān)聽客戶端,并與他們建立連接的機制娶聘。
以下步驟在兩臺計算機之間使用套接字建立TCP連接時會出現(xiàn):
服務器實例化一個 ServerSocket 對象闻镶,表示通過服務器上的端口通信。
服務器調用 ServerSocket 類的 accept() 方法丸升,該方法將一直等待铆农,直到客戶端連接到服務器上給定的端口。
服務器正在等待時狡耻,一個客戶端實例化一個 Socket 對象墩剖,指定服務器名稱和端口號來請求連接。
Socket 類的構造函數(shù)試圖將客戶端連接到指定的服務器和端口號夷狰。如果通信被建立岭皂,則在客戶端創(chuàng)建一個 Socket 對象能夠與服務器進行通信。
在服務器端沼头,accept() 方法返回服務器上一個新的 socket 引用爷绘,該 socket 連接到客戶端的 socket。
連接建立后进倍,通過使用 I/O 流在進行通信土至,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流猾昆,而客戶端的輸入流連接到服務器端的輸出流陶因。
TCP 是一個雙向的通信協(xié)議,因此數(shù)據(jù)可以通過兩個數(shù)據(jù)流在同一時間發(fā)送.以下是一些類提供的一套完整的有用的方法來實現(xiàn) socket毡庆。
整體流程
Socket編程主要涉及到客戶端和服務器端兩個方面坑赡,首先是在服務器端創(chuàng)建一個服務器套接字(ServerSocket)烙如,并把它附加到一個端口上,服務器從這個端口監(jiān)聽連接毅否。端口號的范圍是0到65536亚铁,但是0到1024是為特權服務保留的端口號,我們可以選擇任意一個當前沒有被其他進程使用的端口螟加。
客戶端請求與服務器進行連接的時候徘溢,根據(jù)服務器的域名或者IP地址,加上端口號捆探,打開一個套接字然爆。當服務器接受連接后,服務器和客戶端之間的通信就像輸入輸出流一樣進行操作黍图。
實例
下面是一個客戶端和服務器端進行數(shù)據(jù)交互的簡單例子曾雕,客戶端輸入正方形的邊長,服務器端接收到后計算面積并返回給客戶端助被,通過這個例子可以初步對Socket編程有個把握剖张。
服務器端
public class SocketServer {
??? public static void main(String[] args) throws IOException {
??????? // 端口號
??????? int port = 7000;
??????? // 在端口上創(chuàng)建一個服務器套接字
??????? ServerSocket serverSocket = new ServerSocket(port);
??????? // 監(jiān)聽來自客戶端的連接
??????? Socket socket = serverSocket.accept();
??????? DataInputStream dis = new DataInputStream(
??????????????? new BufferedInputStream(socket.getInputStream()));
??????? DataOutputStream dos = new DataOutputStream(
??????????????? new BufferedOutputStream(socket.getOutputStream()));
??????? do {
??????????? double length = dis.readDouble();
??????????? System.out.println("服務器端收到的邊長數(shù)據(jù)為:" + length);
??????????? double result = length * length;
??????????? dos.writeDouble(result);
??????????? dos.flush();
??????? } while (dis.readInt() != 0);
??????? socket.close();
??????? serverSocket.close();
??? }
}
客戶端
public class SocketClient {
??? public static void main(String[] args) throws UnknownHostException, IOException {
? ? ? ?int port = 7000;
??????? String host = "localhost";
??????? // 創(chuàng)建一個套接字并將其連接到指定端口號
??????? Socket socket = new Socket(host, port);
??????? DataInputStream dis = new DataInputStream(
??????????????? new BufferedInputStream(socket.getInputStream()));
??????? DataOutputStream dos = new DataOutputStream(
??????????????? new BufferedOutputStream(socket.getOutputStream()));
??????? Scanner sc = new Scanner(System.in);
??????? boolean flag = false;
??????? while (!flag) {
??????????? System.out.println("請輸入正方形的邊長:");
??????????? double length = sc.nextDouble();
??????????? dos.writeDouble(length);
??????????? dos.flush();
??????????? double area = dis.readDouble();
??????????? System.out.println("服務器返回的計算面積為:" + area);
??????????? while (true) {
??????????????? System.out.println("繼續(xù)計算?(Y/N)");
??????????????? String str = sc.next();
??????????????? if (str.equalsIgnoreCase("N")) {
??????????????????? dos.writeInt(0);
??????????????????? dos.flush();
??????????????????? flag = true;
??????????????????? break;
??????????????? } else if (str.equalsIgnoreCase("Y")) {
??????????????????? dos.writeInt(1);
??????????????????? dos.flush();
??????????????????? break;
??????????????? }
??????????? }
??????? }
??????? socket.close();
??? }
}
實例二
可以看到上面的服務器端程序和客戶端程序是一對一的關系揩环,為了能讓一個服務器端程序能同時為多個客戶提供服務搔弄,可以使用多線程機制,每個客戶端的請求都由一個獨立的線程進行處理丰滑。下面是改寫后的服務器端程序顾犹。
public class SocketServerM {
??? public static void main(String[] args) throws IOException {
??????? int port = 7000;
??????? int clientNo = 1;
??????? ServerSocket serverSocket = new ServerSocket(port);
??????? // 創(chuàng)建線程池
??????? ExecutorService exec = Executors.newCachedThreadPool();
??????? try {
??????????? while (true) {
??????????????? Socket socket = serverSocket.accept();
??????????????? exec.execute(new SingleServer(socket, clientNo));
??????????????? clientNo++;
??????????? }
??????? } finally {
??????????? serverSocket.close();
??????? }
??? }
}
class SingleServer implements Runnable {
??? private Socket socket;
??? private int clientNo;
??? public SingleServer(Socket socket, int clientNo) {
??????? this.socket = socket;
??????? this.clientNo = clientNo;
??? }
??? @Override
??? public void run() {
??????? try {
??????????? DataInputStream dis = new DataInputStream(
??????????????????? new BufferedInputStream(socket.getInputStream()));
??????????? DataOutputStream dos = new DataOutputStream(
??????????????????? new BufferedOutputStream(socket.getOutputStream()));
??????????? do {
??????????????? double length = dis.readDouble();
??????????????? System.out.println("從客戶端" + clientNo + "接收到的邊長數(shù)據(jù)為:" + length);
??????????????? double result = length * length;
??????????????? dos.writeDouble(result);
??????????????? dos.flush();
??????????? } while (dis.readInt() != 0);
??????? } catch (IOException e) {
??????????? e.printStackTrace();
??????? } finally {
??????????? System.out.println("與客戶端" + clientNo + "通信結束");
??????????? try {
??????????????? socket.close();
??????????? } catch (IOException e) {
??????????????? e.printStackTrace();
??????????? }
??????? }
??? }
}
上面改進后的服務器端代碼可以支持不斷地并發(fā)響應網(wǎng)絡中的客戶請求。關鍵的地方在于多線程機制的運用褒墨,同時利用線程池可以改善服務器程序的性能炫刷。