C語言中文網 目錄
首頁 > Java教程 > Java網絡編程 閱讀:1,163

Java TCP通信:Java ServerSocket類和Socket類

TCP 網絡程序是指利用 Socket 編寫的通信程序。利用 TCP 協議進行通信的兩個應用程序是有主次之分的,一個是服務器程序,一個是客戶端程序,兩者的功能和編寫方法不太一樣。其中 ServerSocket 類表示 Socket 服務器端,Socket 類表示 Socket 客戶端,兩者之間的交互過程如下:
  1. 服務器端創建一個 ServerSocket(服務器端套接字),調用 accept() 方法等待客戶端來連接。
  2. 客戶端程序創建一個 Socket,請求與服務器建立連接。
  3. 服務器接收客戶的連接請求,同時創建一個新的 Socket 與客戶建立連接,服務器繼續等待新的請求。

ServerSocket 類

ServerSocket 類是與 Socket 類相對應的用于表示通信雙方中的服務器端,用于在服務器上開一個端口,被動地等待數據(使用 accept() 方法)并建立連接進行數據交互。

服務器套接字一次可以與一個套接字連接,如果多臺客戶端同時提出連接請求,服務器套接字會將請求連接的客戶端存入隊列中,然后從中取出一個套接字與服務器新建的套接字連接起來。若請求連接大于最大容納數,則多出的連接請求被拒絕;默認的隊列大小是 50。

下面簡單介紹一下 ServerSocket 的構造方法和常用方法。

ServerSocket 的構造方法

ServerSocket 的構造方法如下所示。
  • ServerSocket():無參構造方法。
  • ServerSocket(int port):創建綁定到特定端口的服務器套接字。
  • ServerSocket(int port,int backlog):使用指定的 backlog 創建服務器套接字并將其綁定到指定的本地端口。
  • ServerSocket(int port,int backlog,InetAddress bindAddr):使用指定的端口、監聽 backlog 和要綁定到本地的 IP 地址創建服務器。

在上述方法的參數中 port 指的是本地 TCP 端口,backlog 指的是監聽 backlog,bindAddr 指的是要將服務器綁定到的 InetAddress。

創建 ServerSocket 時可能會拋出 IOException 異常,所以要進行異常捕捉。如下所示為使用 8111 端口的 ServerSocket 實例代碼。
try
{
    ServerSocket serverSocket=new ServerSocket(8111);
}
catch(IOException e)
{
    e.printStackTrace();
}

ServerSocket 的常用方法

ServerSocket 的常用方法如下所示。
  • Server accept():監聽并接收到此套接字的連接。
  • void bind(SocketAddress endpoint):將 ServerSocket 綁定到指定地址(IP 地址和端口號)。
  • void close():關閉此套接字。
  • InetAddress getInetAddress():返回此服務器套接字的本地地址。
  • int getLocalPort():返回此套接字監聽的端口。
  • SocketAddress getLocalSoclcetAddress():返回此套接字綁定的端口的地址,如果尚未綁定則返回 null。
  • int getReceiveBufferSize():獲取此 ServerSocket 的 SO_RCVBUF 選項的值,該值是從 ServerSocket 接收的套接字的建議緩沖區大小。

調用 accept() 方法會返回一個和客戶端 Socket 對象相連接的 Socket 對象,服務器端的 Socket 對象使用 getOutputStream() 方法獲得的輸出流將指定客戶端 Socket 對象使用 getInputStream() 方法獲得那個輸入流。同樣,服務器端的 Socket 對象使用的 getInputStream() 方法獲得的輸入流將指向客戶端 Socket 對象使用的 getOutputStream() 方法獲得的那個輸出流。也就是說,當服務器向輸出流寫入信息時,客戶端通過相應的輸入流就能讀取,反之同樣如此,整個過程如圖 1 所示。

圖1 服務器與客戶端連接示意圖

例 1

了解上面的基礎知識后,下面使用 ServerSocket 類在本機上創建一個使用端口 8888 的服務器端套接字,實例代碼如下所示。
public static void main(String[] args)
{
    try
    {
        //在8888端口創建一個服務器端套接字
        ServerSocket serverSocket=new ServerSocket(8888);
        System.out.println("服務器端Socket創建成功");
        while(true)
        {
            System.out.println("等待客戶端的連接請求");
            //等待客戶端的連接請求
            Socket socket=serverSocket.accept();
            System.out.println("成功建立與客戶端的連接");
        }
    }
    catch(IOException e)
    {
        e.printStackTrace();
    }
}

如上述代碼所示,在成功建立 8888 端口的服務器端套接字之后,如果沒有客戶端的連接請求,則 accept() 方法為空,所以不會輸出“成功建立與客戶端的連接”,運行結果如下所示。
服務器端S.ocket創違成功
等待客戶端的連接請求

Socket 類

Socket 類表示通信雙方中的客戶端,用于呼叫遠端機器上的一個端口,主動向服務器端發送數據(當連接建立后也能接收數據)。下面簡單介紹一下 Socket 類的構造方法和常用方法。

Socket 的構造方法

Socket 的構造方法如下所示。
  • Socket():無參構造方法。
  • Socket(InetAddress address,int port):創建一個流套接字并將其連接到指定 IP 地址的指定端口。
  • Soclcet(InetAddress address,int port,InetAddress localAddr,int localPort):創建一個套接字并將其連接到指定遠程地址上的指定遠程端口。
  • Socket(String host,int port):創建一個流套接字并將其連接到指定主機上的指定端口。
  • Socket(String host,int port,InetAddress localAddr,int localPort):創建一個套接字并將其連接到指定遠程地址上的指定遠程端口。Socket 會通過調用 bind() 函數來綁定提供的本地地址及端口。

在上述方法的參數中,address 指的是遠程地址,port 指的是遠程端口,localAddr 指的是要將套接字綁定到的本地地址,localPort 指的是要將套接字綁定到的本地端口。

Socket 的常用方法

Socket 的常用方法如下所示。
  • void bind(SocketAddress bindpoint):將套接字綁定到本地地址。
  • void close():關閉此套接字。
  • void connect(SocketAddress endpoint):將此套接字連接到服務器。
  • InetAddress getInetAddress():返回套接字的連接地址。
  • InetAddress getLocalAddress():獲取套接字綁定的本地地址。
  • InputStream getInputStream():返回此套接字的輸入流。
  • OutputStream getOutputStream():返回此套接字的輸出流。
  • SocketAddress getLocalSocketAddress():返回此套接字綁定的端點地址,如果尚未綁定則返回 null。
  • SocketAddress getRemoteSocketAddress():返回此套接字的連接的端點地址,如果尚未連接則返回 null。
  • int getLoacalPort():返回此套接字綁定的本地端口。
  • intgetPort():返回此套接字連接的遠程端口。

例 2

編寫 TCP 程序,包括一個客戶端和一個服務器端。要求服務器端等待接收客戶端發送的內容,然后將接收到的內容輸出到控制臺并做出反饋。

(1) 創建一個類作為客戶端,首先在 main() 方法中定義一個 Socket 對象、一個 OutputStream 對象和一個 InputStream 對象并完成初始化。接著定義服務器端的 IP 地址和端口號,代碼如下所示。
public static void main(String[] args)
{
    Socket socket=null;
    OutputStream out=null;
    InputStream in=null;
    String serverIP="127.0.0.1";    //服務器端 IP 地址
    int port=5000;    //服務器端端口號
}

(2) 建立與服務器端的連接并將數據發送到服務器端,代碼如下所示。
socket=new Socket(serverIP,port);    //建立連接
out=socket.getOutputStream();    //發送數據
out.write("我是客戶端數據 ".getBytes());

(3) 從輸入流中讀出服務器的反饋信息并輸出到控制臺,代碼如下所示。
byte[] b=new byte[1024];
in=socket.getInputStream();
int len=in.read(b);
System.out.println(" 服務器端的反饋為:"+new String(b,0,len));

(4) 關閉輸入/輸出流以及 Socket 對象,代碼如下所示。
in.close();
out.close();
socket.close();

(5) 創建一個類作為服務器端,編寫 main() 方法,創建 ServerSocket、Socket、InputStream、OutputStream 以及端口號并初始化,代碼如下所示。
ServerSocket ServerSocket=null;
Socket socket=null;
InputStream in=null;
OutputStream out=null;
int port=5000;

(6) 開啟服務器并接收客戶端發送的數據,代碼如下所示。
ServerSocket=new ServerSocket(port);    //創建服務器套接字
System.out.println("服務器開啟,等待連接。。。");
socket=ServerSocket.accept();    //獲得連接
//接收客戶端發送的內容
in=socket.getInputStream();
byte[] b=new byte[1024];
int len=in.read(b);
System.out.println("客戶端發送的內容為:"+new String(b,0,len));

(7) 使用輸出流對象將信息反饋給客戶端,代碼如下所示。
out=socket.getOutputStream();
out.write("我是服務器端".getBytes());

(8) 關閉輸入/輸出流、Socket 對象以及 ServerSocket 對象,代碼如下所示。
in.close();
out.close();
ServerSocket.close();
socket.close();

(9) 運行服務器端程序代碼,運行結果如下所示。
服務器開啟,等待連接。。。

(10) 為了使程序的結果更加清晰,在步驟 (2) 的代碼最后加入一句代碼“Thread.sleep(1000);”。接著運行客戶端程序代碼,剛開始會出現如下所示的運行結果。
服務器開啟,等待連接。。。
客戶端發送的內容為:我是客戶端數據

緊接著又會出現如下所示的運行結果。
客戶端的反饋為:我是服務器端

客戶端與服務器端的簡單通信

在了解 TCP 通信中 ServerSocket 類和 Socket 類的簡單應用之后,本節將編寫一個案例實現客戶端向服務器發送信息,服務器讀取客戶端發送的信息,并將讀取的數據寫入到數據流中。

首先來看一下客戶端的代碼,如下所示:
public class SocketDemo
{
    public static void main(String[] args)
    {
        Socket socket=null;
        PrintWriter out=null;
        BufferedReader in=null;
        String serverIP="127.0.0.1";    //服務器端ip地址
        int port=5000;    //服務器端端口號
        try
        {
            socket=new Socket(serverIP,port);
            in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out=new PrintWriter(socket.getOutputStream(),true);
            while(true)
            {
                int number=(int)(Math.random()*10)+1;
                System.out.println("客戶端正在發送的內容為:"+number);
                out.println(number);
                Thread.sleep(2000);
            }
        }
        catch(IOException | InterruptedException e)
        {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }
    }
}

如上述代碼所示,客戶端代碼主要是使用 Socket 連接 IP 為 127.0.0.1(本機)的 5000 端口。在建立連接之后將隨機生成的數字使用 PrintWriter 類輸出到套接字。休眠 2 秒后,再次發送隨機數,如此循環。

再來看一個服務器端的代碼,如下所示:
package ch16;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketDemoServer1
{
    public static void main(String[] args)
    {
        ServerSocket serverSocket=null;
        Socket clientSocket=null;
        BufferedReader in=null;
        int port=5000;
        String str=null;
        try
        {
            serverSocket=new ServerSocket(port);    //創建服務器套接字
            System.out.println("服務器開啟,等待連接。。。");
            clientSocket=serverSocket.accept();// 獲得鏈接
            //接收客戶端發送的內容
            in=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            while(true)
            {
                str=in.readLine();
                System.out.println("客戶端發送的內容為:"+str);
                Thread.sleep(2000);
            }
        }
        catch(IOException | InterruptedException e)
        {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }
    }
}

如上述代碼所示,服務器端與客戶端代碼類似,首先使用 ServerSocket 在 IP為127.0.0.1(本機)的 5000 端口建立套接字監聽。在 accept() 方法接收到客戶端的 Socket 實例之后調用 BufferedReader 類的 readLine() 方法,從套接字中讀取一行作為數據,再將它輸出到控制后休眠 2 秒。

要運行本案例,必須先執行服務器端程序,然后執行客戶端程序??蛻舳嗣扛?2 秒向服務器發送一個數字,如下所示。
客戶端正在發送的內容為:10
客戶端正在發送的內容為:5
客戶端正在發送的內容為:10
客戶端正在發送的內容為:4
客戶端正在發送的內容為:3

服務器端會將客戶端發送的數據輸出到控制臺,如下所示。
服務器幵啟,等待連接。。。
客戶端發送的內容為:7
客戶端發送的內容為:2
客戶端發送的內容為:10
客戶端發送的內容為:5
客戶端發送的內容為:10
......

傳輸對象數據

經過前面的學習,掌握了如何在服務器開始一個端口監聽套接字,以及如何在客戶端連接服務器,發送簡單的數字。本次案例將實現如何在客戶端發送一個對象到服務器端,服務器如何解析對象中的數據。

例 3

第一步是創建用于保存數據的類。這里使用的 User 類是一個普通的類,包含 name 和 password 兩個成員。由于需要序列化這個對象以便在網絡上傳輸,所以需要實現 java. io.Serializable 接 P。

User 類的代碼如下:
package ch16;
public class User implements java.io.Serializable
{
    private String name;
    private String password;
    public User(String name,String password)
    {
        this.name=name;
        this.password=password;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name=name;
    }
    public String getPassword()
    {
        return password;
    }
    public void setPassword(String password)
    {
        this.password=password;
    }
}

接下來編寫服務器端的代碼。服務器的作用是接收客戶端發送過來的數據,將數據轉換成 User 對象并輸出成員信息,然后對 User 對象進行修改再輸出給客戶端。

服務器端 MyServer 類的實現代碼如下:
package ch16;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer
{
    public static void main(String[] args) throws IOException
    {
        // 監聽10000端口
        ServerSocket server=new ServerSocket(10000);
        while(true)
        {
            //接收客戶端的連接
            Socket socket=server.accept();
            //調用客戶端的數據處理方法
            invoke(socket);
        }
    }
    private static void invoke(final Socket socket) throws IOException
    {
        //開啟一個新線程
        new Thread(new Runnable()
        {
            public void run()
            {
                //創建輸入流對象
                ObjectInputStream is=null;
                //創建輸出流對象
                ObjectOutputStream os=null;
                try
                {
                    is=new ObjectInputStream(socket.getInputStream());
                    os=new ObjectOutputStream(socket.getOutputStream());
                    //讀取一個對象
                    Object obj = is.readObject();
                    //將對象轉換為User類型
                    User user=(User) obj;
                    //在服務器端輸出name成員和password成員信息
                    System.out.println("user: "+user.getName()+"/"+user.getPassword());
                    //修改當前對象的name成員數據
                    user.setName(user.getName()+"_new");
                    //修改當前對象的password對象數據
                    user.setPassword(user.getPassword()+"_new");
                    //將修改后的對象輸出給客戶端
                    os.writeObject(user);
                    os.flush();
                }
                catch(IOException|ClassNotFoundException ex)
                {
                    ex.printStackTrace();
                }
                finally
                {
                    try
                    {
                        //關閉輸入流
                        is.close();
                        //關閉輸出流
                        os.close();
                        //關閉客戶端
                        socket.close();
                    }
                    catch(Exception ex){}
                }
            }
        }).start();
    }
}

如上述代碼所示,在服務器端分別使用 ObjectInputStream 和 ObjectOutputStream 來接收和發送 socket 中的 InputStream 和OutputStream,然后轉換成 User 對象。

客戶端需要連接服務器,接收服務器輸出的數據并解析,同時需要創建 User 對象并發給服務器??蛻舳?MyClient 類的實現代碼如下:
package ch16;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class MyClient
{
    public static void main(String[] args) throws Exception
    {
        //循環100次
        for(int i=0;i<100;i++)
        {
            //創建客戶端Socket
            Socket socket=null;
            //創建輸入流
            ObjectOutputStream os=null;
            //創建輸出流
            ObjectInputStream is=null
            try
            {
                //連接服務器
                socket=new Socket("localhost",10000);
                //接收輸出流中的數據
                os=new ObjectOutputStream(socket.getOutputStream());
                //創建一個User對象
                User user=new User("user_"+i,"password_"+i);
                //將User對象寫入輸出流
                os.writeObject(user);
                os.flush();
                //接收輸入流中的數據
                is=new ObjectInputStream(socket.getInputStream());
                //讀取輸入流中的數據
                Object obj=is.readObject();
                //如果數據不空則轉換成User對象,然后輸出成員信息
                if(obj!=null)
                {
                    user=(User) obj;
                    System.out.println("user: "+user.getName()+"/"+user.getPassword());
                }
            }
            catch(IOException ex)
            {
                ex.printStackTrace();
            }
            finally
            {
                try
                {
                    //關閉輸入流
                    is.close();
                    //關閉輸出流
                    os.close();
                    //關閉客戶端
                    socket.close();
                }
                catch(Exception ex) {}
            }
        }
    }
}

仔細觀察上述代碼可以發現,客戶端與服務器端的代碼類似,同樣使用 ObjectOutputStream 和 ObjectInputStream 來處理數據。

先運行服務器端程序 MyServer,再運行客戶端程序 MyClient。此時將在客戶端看到下所示的輸出。
user:user_86_nevj/password_86_new
user:user_87_new/password_87_new
user:user_88_new/password_88_new
user:user_89_new/password_89_new
user:user_90_new/password_90_new
user:user_91_new/password_91_new
user:user_92_new/password_92_new
user:user_93_new/password_93_new
user:user_94_new/password_94_new
user:user_95_new/password_95_new
user:user_96_new/password_96_new
user:user_97_new/password_97_new
user:user_98_new/password_98_new
user:user_99_new/password_99_new

服務器端的輸出如下所示。
user:user_86/password_86
user:user_87/password_87
user:user_88/password_88
user:user_89/password_89
user:user_90/password_90
user:user_91/password_91
user:user_92/password_92
user:user_93/password_93
user:user_94/password_94
user:user_95/password_95
user:user_96/password_96
user:user_97/password_97
user:user_98/password_98
user:user_99/password_99

精美而實用的網站,提供C語言、C++、STL、Linux、Shell、Java、Go語言等教程,以及socket、GCC、vi、Swing、設計模式、JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo
极速pk10开户