搜尋
首頁Javajava教程Java中Socket的程式設計原理以及程式碼教學分享

我們的主角Socket,它是應用層之下,傳輸層之上的一個接口層,也就是操作系統提供給用戶訪問網絡的系統接口,我們可以藉助於Socket接口層,對傳輸層,網際層以及網路介面層進行操作,來實現我們不同的應用層協議,舉幾個例子,如HTTP是基於TCP實現的,ping和tracerouter是基於ICMP實現的,libpcap(用wireshare做過網絡抓包的可能更熟悉)則是直接讀取了網路介面層的數據,但是他們的實現,都是藉助於Socket完成的。可見,對於應用層,我們想實現網路功能,歸根究底都是要透過Socket來實現的,否則,我們無法存取處於作業系統的傳輸層,網際層以及網路介面層。考慮的本文以及本文之後的其他文章,我們暫時只關注Socket訪問傳輸層的流程,其他的方面可以參看相關書籍,這裡推薦《TCP/IP詳解卷一》以及《UNIX網絡編程》(UNP),雖然是Linux C語言開發的書,但是對網路程式設計講解的很透徹。這裡也向這位大家致敬,雖英年早逝,留給大家的財富不可估量。

        再來了解TCP和UDP,兩者最大的區別在於,TCP是可靠的,也就是說,我們透過TCP發送的數據,網路協定堆疊會保證資料可靠的傳輸到對端,而UDP是不可靠的,如果出現丟包,協定棧不會做任何處理,可靠性的保證交由應用層處理。因此,TCP的表現會比UDP低,但可靠性會比UDP好很多。除此之外,兩者在傳輸資料時,也有形式上的不同,TCP的資料是流,大家可以類比文件流,而UDP則是基於資料包,也就是說資料會被打成包發送,可能大家會有疑問,這個有什麼差別嗎?當然有,一個最大的問題就是,TCP沒有資料邊界,每次接收資料以位元組為單位,如果想區分兩次發送的數據,除非在資料中加入分割字元(如http的\r\n\r \n),否則,TCP無法區分資料邊界,而UDP每次發送的資料都被打為一個獨立的資料包,因此幾次發送的資料邊界很清晰,我們每次接收也是按照資料包為單位進行接收。對於不同的程式語言或平台,雖然Socket的介面可能不同,但是都提供了基於TCP發送資料的介面以及基於UDP發送資料的介面。

         另外,還有一個概念,對於一個Socket以 客戶端的IP及連接埠號碼伺服器端的IP以及連接埠號碼 唯一識別。

        由於後面幾篇文章基本上都是基於TCP的,所以這裡為了讓大家知道Socket是個什麼東西,先以UDP為例,後面再詳細了解基於TCP的Socket程式設計。我們使用Java Socket,基於UDP協議,實現字母的大寫轉換,整個流程是客戶端發送字串給伺服器,伺服器將字串轉換為全部大寫後,發送回客戶端,客戶端進行顯示。

對於Java Socket程式設計而言,有兩個概念,一個是ServerSocket,一個是Socket。服務端和客戶端之間透過Socket建立連接,之後它們就可以進行通訊了。首先ServerSocket將在服務端監聽某個端口,當發現客戶端有Socket來試圖連接它時,它會accept該Socket的連接請求,同時在服務端建立一個對應的Socket與之進行通信。這樣就有兩個Socket了,客戶端和服務端各一個。

對於Socket之間的通訊其實很簡單,服務端往Socket的輸出流裡面寫東西,客戶端就可以透過Socket的輸入流讀取對應的內容。 Socket與Socket之間是雙向連通的,所以客戶端也可以往對應的Socket輸出流裡面寫東西,然後服務端對應的Socket的輸入流就可以讀出對應的內容。以下來看一些服務端與客戶端通訊的範例:

1、客戶端寫入服務端讀取

服務端程式碼##

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。

客戶端程式碼

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之间是双向通信的,它既可以接收数据,同时也可以发送数据。

服务端代码

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已经是不能用的了,唯一能用的就是读到的数据了。针对这种情况,通常我们都会约定一个结束标记,当客户端发送过来的数据包含某个结束标记时就说明当前的数据已经发送完毕了,这个时候我们就可以进行循环的跳出了。那么改进后的代码会是这个样子:

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”时就会结束数据的接收,终止循环,这样后续的代码又可以继续进行了。

客户端代码

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循环,也就没有之后的服务端返回数据给客户端,客户端也就不可能接收到服务端返回的数据。解决方法如服务端第二段代码所示,在客户端发送数据完毕后,往输出流里面写入结束标记告诉服务端数据已经发送完毕了,同样服务端返回数据完毕后也发一个标记告诉客户端。那么修改后的客户端代码就应该是这个样子:

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、多个客户端连接同一个服务端

像前面讲的两个例子都是服务端接收一个客户端的请求之后就结束了,不能再接收其他客户端的请求了,这往往是不能满足我们的要求的。通常我们会这样做:

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就能接收来自所有客户端的连接请求了,并且与它们进行通信了。这就实现了一个简单的一个服务端与多个客户端进行通信的模式。

上述例子中虽然实现了一个服务端跟多个客户端进行通信,但是还存在一个问题。在上述例子中,我们的服务端处理客户端的连接请求是同步进行的,每次接收到来自客户端的连接请求后,都要先跟当前的客户端通信完之后才能再处理下一个连接请求。这在并发比较多的情况下会严重影响程序的性能,为此,我们可以把它改为如下这种异步处理与客户端通信的方式:

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来一次读一行,如:

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一下,这样数据才会真正的从缓冲区里面写入。对应上面的代码我们的客户端程序应该这样写:

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秒后还没有读到数据就中断阻塞的话我们可以这样做:

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、接收数据乱码

对于这种服务端或客户端接收中文乱码的情况通常是因为数据发送时使用的编码跟接收时候使用的编码不一致。比如有下面这样一段服务端代码:

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编码来接收数据的话也极有可能会出现数据乱码的情况。所以,对于上述服务端代码,为使我们的程序能够读取对方发送过来的数据,而不出现乱码情况,我们的客户端应该是这样的:

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();  
   }  
}

以上是Java中Socket的程式設計原理以及程式碼教學分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

如何將Java的RMI(遠程方法調用)用於分佈式計算?如何將Java的RMI(遠程方法調用)用於分佈式計算?Mar 11, 2025 pm 05:53 PM

本文解釋了用於構建分佈式應用程序的Java的遠程方法調用(RMI)。 它詳細介紹了接口定義,實現,註冊表設置和客戶端調用,以解決網絡問題和安全性等挑戰。

如何使用Java的插座API進行網絡通信?如何使用Java的插座API進行網絡通信?Mar 11, 2025 pm 05:53 PM

本文詳細介紹了用於網絡通信的Java的套接字API,涵蓋了客戶服務器設置,數據處理和關鍵考慮因素,例如資源管理,錯誤處理和安全性。 它還探索了性能優化技術,我

如何在Java中創建自定義網絡協議?如何在Java中創建自定義網絡協議?Mar 11, 2025 pm 05:52 PM

本文詳細介紹了創建自定義Java網絡協議。 它涵蓋協議定義(數據結構,框架,錯誤處理,版本控制),實現(使用插座),數據序列化和最佳實踐(效率,安全性,維護

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。