轉:Java Socket編程。本站提示廣大學習愛好者:(轉:Java Socket編程)文章只能為提供參考,不一定能成為您想要的結果。以下是轉:Java Socket編程正文
關於Java Socket編程而言,有兩個概念,一個是ServerSocket,一個是Socket。服務端和客戶端之間經過Socket樹立銜接,之後它們就可以停止通訊了。首先ServerSocket將在服務端監聽某個端口,當發現客戶端有Socket來試圖銜接它時,它會accept該Socket的銜接懇求,同時在服務端樹立一個對應的Socket與之停止通訊。這樣就有兩個Socket了,客戶端和服務端各一個。
關於Socket之間的通訊其實很復雜,服務端往Socket的輸入流外面寫東西,客戶端就可以經過Socket的輸出流讀取對應的內容。Socket與Socket之間是雙向連通的,所以客戶端也可以往對應的Socket輸入流外面寫東西,然後服務端對應的Socket的輸出流就可以讀出對應的內容。上面來看一些服務端與客戶端通訊的例子:
1、客戶端寫服務端讀
服務端代碼
Java代碼
- public class Server {
-
- public static void main(String args[]) throws IOException {
- //為了復雜起見,一切的異常信息都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在端口8899上
- ServerSocket server = new ServerSocket(port);
- //server嘗試接納其他Socket的銜接懇求,server的accept辦法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端樹立好銜接之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過去的信息了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new StringBuilder();
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from client: " + sb);
- reader.close();
- socket.close();
- server.close();
- }
-
- }
服務端從Socket的InputStream中讀取數據的操作也是阻塞式的,假如從輸出流中沒有讀取到數據順序會不斷在那裡不動,直到客戶端往Socket的輸入流中寫入了數據,或封閉了Socket的輸入流。當然,關於客戶端的Socket也是異樣如此。在操作完當前,整個順序完畢前記得封閉對應的資源,即封閉對應的IO流和Socket。
客戶端代碼
Java代碼
- public class Client {
-
- public static void main(String args[]) throws Exception {
- //為了復雜起見,一切的異常都直接往外拋
- String host = "127.0.0.1"; //要銜接的服務端IP地址
- int port = 8899; //要銜接的服務端對應的監聽端口
- //與服務端樹立銜接
- Socket client = new Socket(host, port);
- //樹立銜接後就可以往服務端寫數據了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.flush();//寫完後要記得flush
- writer.close();
- client.close();
- }
-
- }
關於客戶端往Socket的輸入流外面寫數據傳遞給服務端要留意一點,假如寫操作之後順序不是對應著輸入流的封閉,而是停止其他阻塞式的操作(比方從輸出流外面讀數據),記住要flush一下,只要這樣服務端才干收到客戶端發送的數據,否則能夠會惹起兩邊有限的相互等候。在稍後講到客戶端和服務端同時讀和寫的時分會說到這個問題。
2、客戶端和服務端同時讀和寫
後面曾經說了Socket之間是雙向通訊的,它既可以接納數據,同時也可以發送數據。
服務端代碼
Java代碼
- public class Server {
-
- public static void main(String args[]) throws IOException {
- //為了復雜起見,一切的異常信息都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在端口8899上
- ServerSocket server = new ServerSocket(port);
- //server嘗試接納其他Socket的銜接懇求,server的accept辦法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端樹立好銜接之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過去的信息了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new StringBuilder();
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- server.close();
- }
-
- }
在上述代碼中首先我們從輸出流中讀取客戶端發送過去的數據,接上去我們再往輸入流外面寫入數據給客戶端,接上去封閉對應的資源文件。而實踐上上述代碼能夠並不會依照我們事後想象的方式運轉,由於從輸出流中讀取數據是一個阻塞式操作,在上述的while循環中當讀到數據的時分就會執行循環體,否則就會阻塞,這樣前面的寫操作就永遠都執行不了了。除非客戶端對應的Socket封閉了阻塞才會中止,while循環也會跳出。針對這種能夠永遠無法執行下去的狀況的處理辦法是while循環需求在外面有條件的跳出來,縱觀上述代碼,在不時變化的也只要取到的長度len和讀到的數據了,len曾經是不能用的了,獨一能用的就是讀到的數據了。針對這種狀況,通常我們都會商定一個完畢標志,當客戶端發送過去的數據包括某個完畢標志時就闡明以後的數據曾經發送終了了,這個時分我們就可以停止循環的跳出了。那麼改良後的代碼會是這個樣子:
Java代碼
- public class Server {
-
- public static void main(String args[]) throws IOException {
- //為了復雜起見,一切的異常信息都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在端口8899上
- ServerSocket server = new ServerSocket(port);
- //server嘗試接納其他Socket的銜接懇求,server的accept辦法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端樹立好銜接之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過去的信息了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就完畢接納
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- server.close();
- }
-
- }
在上述代碼中,當服務端讀取到客戶端發送的完畢標志,即“eof”時就會完畢數據的接納,終止循環,這樣後續的代碼又可以持續停止了。
客戶端代碼
Java代碼
- public class Client {
-
- public static void main(String args[]) throws Exception {
- //為了復雜起見,一切的異常都直接往外拋
- String host = "127.0.0.1"; //要銜接的服務端IP地址
- int port = 8899; //要銜接的服務端對應的監聽端口
- //與服務端樹立銜接
- Socket client = new Socket(host, port);
- //樹立銜接後就可以往服務端寫數據了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.flush();
- //寫完當前停止讀操作
- Reader reader = new InputStreamReader(client.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuffer sb = new StringBuffer();
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from server: " + sb);
- writer.close();
- reader.close();
- client.close();
- }
-
- }
在上述代碼中我們先是給服務端發送了一段數據,之後讀取服務端前往來的數據,跟之前的服務端一樣在讀的進程中有能夠招致順序不斷掛在那裡,永遠跳不出while循環。這段代碼配合服務端的第一段代碼就正好讓我們剖析服務端永遠在那裡接納數據,永遠跳不出while循環,也就沒有之後的服務端前往數據給客戶端,客戶端也就不能夠接納到服務端前往的數據。處理辦法如服務端第二段代碼所示,在客戶端發送數據終了後,往輸入流外面寫入完畢標志通知服務端數據曾經發送終了了,異樣服務端前往數據終了後也發一個標志通知客戶端。那麼修正後的客戶端代碼就應該是這個樣子:
Java代碼
- public class Client {
-
- public static void main(String args[]) throws Exception {
- //為了復雜起見,一切的異常都直接往外拋
- String host = "127.0.0.1"; //要銜接的服務端IP地址
- int port = 8899; //要銜接的服務端對應的監聽端口
- //與服務端樹立銜接
- Socket client = new Socket(host, port);
- //樹立銜接後就可以往服務端寫數據了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.write("eof");
- writer.flush();
- //寫完當前停止讀操作
- Reader reader = new InputStreamReader(client.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuffer sb = new StringBuffer();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from server: " + sb);
- writer.close();
- reader.close();
- client.close();
- }
-
- }
-
我們日常運用的比擬多的都是這種客戶端發送數據給服務端,服務端接納數據後再前往相應的後果給客戶端這種方式。只是客戶端和服務端之間不再是這種一對一的關系,而是上面要講到的多個客戶端對應同一個服務端的狀況。
3、多個客戶端銜接同一個服務端
像後面講的兩個例子都是服務端接納一個客戶端的懇求之後就完畢了,不能再接納其他客戶端的懇求了,這往往是不能滿足我們的要求的。通常我們會這樣做:
Java代碼
- public class Server {
-
- public static void main(String args[]) throws IOException {
- //為了復雜起見,一切的異常信息都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在端口8899上
- ServerSocket server = new ServerSocket(port);
- while (true) {
- //server嘗試接納其他Socket的銜接懇求,server的accept辦法是阻塞式的
- Socket socket = server.accept();
- //跟客戶端樹立好銜接之後,我們就可以獲取socket的InputStream,並從中讀取客戶端發過去的信息了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就完畢接納
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- }
- }
-
- }
在下面代碼中我們用了一個死循環,在循環體外面ServerSocket調用其accept辦法試圖接納來自客戶端的銜接懇求。當沒有接納到懇求的時分,順序會在這裡阻塞直到接納到來自客戶端的銜接懇求,之後會跟以後樹立好銜接的客戶端停止通訊,完了後會接著執行循環體再次嘗試接納新的銜接懇求。這樣我們的ServerSocket就能接納來自一切客戶端的銜接懇求了,並且與它們停止通訊了。這就完成了一個復雜的一個服務端與多個客戶端停止通訊的形式。
上述例子中雖然完成了一個服務端跟多個客戶端停止通訊,但是還存在一個問題。在上述例子中,我們的服務端處置客戶端的銜接懇求是同步停止的,每次接納到來自客戶端的銜接懇求後,都要先跟以後的客戶端通訊完之後才干再處置下一個銜接懇求。這在並發比擬多的狀況下會嚴重影響順序的功能,為此,我們可以把它改為如下這種異步處置與客戶端通訊的方式:
Java代碼
- public class Server {
-
- public static void main(String args[]) throws IOException {
- //為了復雜起見,一切的異常信息都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在端口8899上
- ServerSocket server = new ServerSocket(port);
- while (true) {
- //server嘗試接納其他Socket的銜接懇求,server的accept辦法是阻塞式的
- Socket socket = server.accept();
- //每接納到一個Socket就樹立一個新的線程來處置它
- new Thread(new Task(socket)).start();
- }
- }
-
- /**
- * 用來處置Socket懇求的
- */
- static class Task implements Runnable {
-
- private Socket socket;
- public Task(Socket socket) {
- this.socket = socket;
- }
- public void run() {
-
- try {
-
- handleSocket();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 跟客戶端Socket停止通訊
- * @throws Exception
- */
- private void handleSocket() throws Exception {
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((len=reader.read(chars)) != -1) {
- temp = new String(chars, 0, len);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就完畢接納
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- }
- }
-
- }
在下面代碼中,每次ServerSocket接納到一個新的Socket銜接懇求後都會新起一個線程來跟以後Socket停止通訊,這樣就到達了異步處置與客戶端Socket停止通訊的狀況。
在從Socket的InputStream中接納數據時,像下面那樣一點點的讀就太復雜了,有時分我們就會換成運用BufferedReader來一次讀一行,如:
Java代碼
- public class Server {
-
- public static void main(String args[]) throws IOException {
- //為了復雜起見,一切的異常信息都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在端口8899上
- ServerSocket server = new ServerSocket(port);
- while (true) {
- //server嘗試接納其他Socket的銜接懇求,server的accept辦法是阻塞式的
- Socket socket = server.accept();
- //每接納到一個Socket就樹立一個新的線程來處置它
- new Thread(new Task(socket)).start();
- }
- }
-
- /**
- * 用來處置Socket懇求的
- */
- static class Task implements Runnable {
-
- private Socket socket;
- public Task(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- try {
- handleSocket();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 跟客戶端Socket停止通訊
- * @throws Exception
- */
- private void handleSocket() throws Exception {
- BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((temp=br.readLine()) != null) {
- System.out.println(temp);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就完畢接納
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from client: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.write("eof\n");
- writer.flush();
- writer.close();
- br.close();
- socket.close();
- }
- }
- }
這個時分需求留意的是,BufferedReader的readLine辦法是一次讀一行的,這個辦法是阻塞的,直到它讀到了一行數據為止順序才會持續往下執行,那麼readLine什麼時分才會讀到一行呢?直到順序遇到了換行符或許是對應流的完畢符readLine辦法才會以為讀到了一行,才會完畢其阻塞,讓順序持續往下執行。所以我們在運用BufferedReader的readLine讀取數據的時分一定要記得在對應的輸入流外面一定要寫入換行符(流完畢之後會自動標志為完畢,readLine可以辨認),寫入換行符之後一定記得假如輸入流不是馬上封閉的狀況下記得flush一下,這樣數據才會真正的從緩沖區外面寫入。對應下面的代碼我們的客戶端順序應該這樣寫:
Java代碼
- public class Client {
-
- public static void main(String args[]) throws Exception {
- //為了復雜起見,一切的異常都直接往外拋
- String host = "127.0.0.1"; //要銜接的服務端IP地址
- int port = 8899; //要銜接的服務端對應的監聽端口
- //與服務端樹立銜接
- Socket client = new Socket(host, port);
- //樹立銜接後就可以往服務端寫數據了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.write("eof\n");
- writer.flush();
- //寫完當前停止讀操作
- BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
- StringBuffer sb = new StringBuffer();
- String temp;
- int index;
- while ((temp=br.readLine()) != null) {
- if ((index = temp.indexOf("eof")) != -1) {
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("from server: " + sb);
- writer.close();
- br.close();
- client.close();
- }
- }
4、設置超時時間
假定有這樣一種需求,我們的客戶端需求經過Socket從服務端獲取到XX信息,然後給用戶展現在頁面上。我們知道Socket在讀數據的時分是阻塞式的,假如沒有讀到數據順序會不斷阻塞在那裡。在同步懇求的時分我們一定是不能允許這樣的狀況發作的,這就需求我們在懇求到達一定的時間後控制阻塞的中綴,讓順序得以持續運轉。Socket為我們提供了一個setSoTimeout()辦法來設置接納數據的超時時間,單位是毫秒。當設置的超時時間大於0,並且超越了這一時間Socket還沒有接納到前往的數據的話,Socket就會拋出一個SocketTimeoutException。
假定我們需求控制我們的客戶端在開端讀取數據10秒後還沒有讀到數據就中綴阻塞的話我們可以這樣做:
Java代碼
- public class Client {
-
- public static void main(String args[]) throws Exception {
- //為了復雜起見,一切的異常都直接往外拋
- String host = "127.0.0.1"; //要銜接的服務端IP地址
- int port = 8899; //要銜接的服務端對應的監聽端口
- //與服務端樹立銜接
- Socket client = new Socket(host, port);
- //樹立銜接後就可以往服務端寫數據了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.write("eof\n");
- writer.flush();
- //寫完當前停止讀操作
- BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
- //設置超時間為10秒
- client.setSoTimeout(10*1000);
- StringBuffer sb = new StringBuffer();
- String temp;
- int index;
- try {
- while ((temp=br.readLine()) != null) {
- if ((index = temp.indexOf("eof")) != -1) {
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- } catch (SocketTimeoutException e) {
- System.out.println("數據讀取超時。");
- }
- System.out.println("from server: " + sb);
- writer.close();
- br.close();
- client.close();
- }
- }
-
-
5、接納數據亂碼
關於這種服務端或客戶端接納中文亂碼的狀況通常是由於數據發送時運用的編碼跟接納時分運用的編碼不分歧。比方有上面這樣一段服務端代碼:
Java代碼
- public class Server {
-
- public static void main(String args[]) throws IOException {
- //為了復雜起見,一切的異常信息都往外拋
- int port = 8899;
- //定義一個ServerSocket監聽在端口8899上
- ServerSocket server = new ServerSocket(port);
- while (true) {
- //server嘗試接納其他Socket的銜接懇求,server的accept辦法是阻塞式的
- Socket socket = server.accept();
- //每接納到一個Socket就樹立一個新的線程來處置它
- new Thread(new Task(socket)).start();
- }
- }
-
- /**
- * 用來處置Socket懇求的
- */
- static class Task implements Runnable {
-
- private Socket socket;
- public Task(Socket socket) {
- this.socket = socket;
- }
- public void run() {
- try {
- handleSocket();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 跟客戶端Socket停止通訊
- * @throws Exception
- */
- private void handleSocket() throws Exception {
- BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
- StringBuilder sb = new StringBuilder();
- String temp;
- int index;
- while ((temp=br.readLine()) != null) {
- System.out.println(temp);
- if ((index = temp.indexOf("eof")) != -1) {//遇到eof時就完畢接納
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- System.out.println("客戶端: " + sb);
- //讀完後寫一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
- writer.write("你好,客戶端。");
- writer.write("eof\n");
- writer.flush();
- writer.close();
- br.close();
- socket.close();
- }
- }
- }
這裡用來測試我就弄的混亂了一點。在下面服務端代碼中我們在定義輸出流的時分明白定義了運用GBK編碼來讀取數據,而在定義輸入流的時分明白指定了將運用UTF-8編碼來發送數據。假如客戶端上送數據的時分不以GBK編碼來發送的話服務端接納的數據就很有能夠會亂碼;異樣假如客戶端接納數據的時分不以服務端發送數據的編碼,即UTF-8編碼來接納數據的話也極有能夠會呈現數據亂碼的狀況。所以,關於上述服務端代碼,為使我們的順序可以讀取對方發送過去的數據,而不呈現亂碼狀況,我們的客戶端應該是這樣的:
Java代碼
- public class Client {
-
- public static void main(String args[]) throws Exception {
- //為了復雜起見,一切的異常都直接往外拋
- String host = "127.0.0.1"; //要銜接的服務端IP地址
- int port = 8899; //要銜接的服務端對應的監聽端口
- //與服務端樹立銜接
- Socket client = new Socket(host, port);
- //樹立銜接後就可以往服務端寫數據了
- Writer writer = new OutputStreamWriter(client.getOutputStream(), "GBK");
- writer.write("你好,服務端。");
- writer.write("eof\n");
- writer.flush();
- //寫完當前停止讀操作
- BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
- //設置超時間為10秒
- client.setSoTimeout(10*1000);
- StringBuffer sb = new StringBuffer();
- String temp;
- int index;
- try {
- while ((temp=br.readLine()) != null) {
- if ((index = temp.indexOf("eof")) != -1) {
- sb.append(temp.substring(0, index));
- break;
- }
- sb.append(temp);
- }
- } catch (SocketTimeoutException e) {
- System.out.println("數據讀取超時。");
- }
- System.out.println("服務端: " + sb);
- writer.close();
- br.close();
- client.close();
- }
- }
轉自:
http://elim.iteye.com/blog/1979837