使用TCP/IP的套接字(Socket)進行通信

套接字Socket的引入

  爲了能夠方便地開發網絡應用軟件,由美國伯克利大學在Unix上推出了一種應用程序訪問通信協議的操作系統用調用socket(套接字)

  socket的出現,使程序員可以很方便地訪問TCP/IP,從而開發各種網絡應用的程序。

  隨着Unix的應用推廣,套接字在編寫網絡軟件中得到了極大的普及。後來,套接字又被引進了Windows等操作系統中。Java語言也引入了套接字編程模型。

 

什麼是Socket?

  Socket是連接運行在網絡上的兩個程序間的雙向通訊的端點。

 

使用Socket進行網絡通信的過程

  服務器程序將一個套接字綁定到一個特定的端口,並通過此套接字等待和監聽客戶的連接請求。

  客戶程序根據服務器程序所在的主機名和端口號發出連接請求。

 

  如果一切正常,服務器接受連接請求。並獲得一個新的綁定到不同端口地址的套接字。(不可能有兩個程序同時佔用一個端口)。

  客戶和服務器通過讀寫套接字進行通訊。

 

 

  使用ServerSocketSocket實現服務器端和客戶端的Socket通信。

  

 

  其中:

  左邊ServerSocket類的構造方法可以傳入一個端口值來構建對象。

  accept()方法監聽向這個socket的連接並接收連接。它將會阻塞直到連接被建立好。連接建立好後它會返回一個Socket對象。

  連接建立好後,服務器端和客戶端的輸入流和輸出流就互爲彼此,即一端的輸出流是另一端的輸入流。

 

總結:使用ServerSocket和Socket實現服務器端和客戶端的Socket通信

  (1)建立Socket連接

  (2)獲得輸入/輸出流

  (3)讀/寫數據

  (4)關閉輸入/輸出流

  (5)關閉Socket

 

通信程序測試

  建立服務器端和客戶端如下: 

 

複製代碼
package com.example.network;

import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer
{
    public static void main(String[] args) throws Exception
    {
        // 創建服務器端的socket對象
        ServerSocket ss = new ServerSocket(5000);

        // 監聽連接
        Socket socket = ss.accept();
        // 直到連接建立好之後代碼纔會往下執行

        System.out.println("Connected Successfully!");

    }

}
複製代碼

 

複製代碼
package com.example.network;

import java.net.Socket;

public class TcpClient
{
    public static void main(String[] args) throws Exception
    {
        Socket socket = new Socket("127.0.0.1", 5000);
    }

}
複製代碼

 

  然後先運行服務器端,再運行客戶端,可以看到,運行客戶端之後輸出服務器端的後續代碼。

  表明連接建立後纔會往下執行。

 

  一個比較簡陋的通信程序:

 

複製代碼
package com.example.network;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer
{
    public static void main(String[] args) throws Exception
    {
        // 創建服務器端的socket對象
        ServerSocket ss = new ServerSocket(5000);

        // 監聽連接
        Socket socket = ss.accept();
        // 直到連接建立好之後代碼纔會往下執行

        System.out.println("Connected Successfully!");

        // 獲得服務器端的輸入流,從客戶端接收信息
        InputStream is = socket.getInputStream();
        // 服務器端的輸出流,向客戶端發送信息
        OutputStream os = socket.getOutputStream();

        byte[] buffer = new byte[200];

        int length = 0;
        length = is.read(buffer);
        String str = new String(buffer, 0, length);
        System.out.println(str);

        // 服務器端的輸出
        os.write("Welcome".getBytes());

        // 關閉資源
        is.close();
        os.close();
        socket.close();

    }

}
複製代碼

 

複製代碼
package com.example.network;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class TcpClient
{
    public static void main(String[] args) throws Exception
    {
        Socket socket = new Socket("127.0.0.1", 5000);

        // 客戶端的輸出流
        OutputStream os = socket.getOutputStream();

        // 將信息寫入流,把這個信息傳遞給服務器
        os.write("hello world".getBytes());


        // 從服務器端接收信息

        InputStream is = socket.getInputStream();

        byte[] buffer = new byte[200];

        int length = is.read(buffer);
        String str = new String(buffer, 0, length);
        System.out.println(str);

        // 關閉資源
        is.close();
        os.close();
        socket.close();
    }

}
複製代碼

 

 

  先運行服務器,再運行客戶端。之後可以在服務器和客戶端的控制檯上進行輸入操作,另一端將會收到輸入的信息並輸出。

 

 

使用線程實現服務器端與客戶端的雙向通信

  用兩個線程,一個線程專門用於處理服務器端的讀,另一個線程專門用於處理服務器端的寫。

  客戶端同理。

  代碼如下,程序共有六個類。

  服務器端和其輸入輸出線程:

 

複製代碼
package com.example.network;

import java.net.ServerSocket;
import java.net.Socket;

public class MainServer
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket serverSocket = new ServerSocket(4000);

        while (true)
        {
            // 一直處於監聽狀態,這樣可以處理多個用戶
            Socket socket = serverSocket.accept();

            // 啓動讀寫線程
            new ServerInputThread(socket).start();
            new ServerOutputThread(socket).start();

        }

    }

}
複製代碼

 

複製代碼
package com.example.network;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

public class ServerInputThread extends Thread
{
    private Socket socket;

    public ServerInputThread(Socket socket)
    {
        super();
        this.socket = socket;
    }

    @Override
    public void run()
    {
        try
        {
            // 獲得輸入流
            InputStream is = socket.getInputStream();

            while (true)
            {
                byte[] buffer = new byte[1024];

                int length = is.read(buffer);

                String str = new String(buffer, 0, length);

                System.out.println(str);

            }

        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

}
複製代碼
複製代碼
package com.example.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class ServerOutputThread extends Thread
{
    private Socket socket;

    public ServerOutputThread(Socket socket)
    {
        super();
        this.socket = socket;
    }

    @Override
    public void run()
    {
        try
        {

            OutputStream os = socket.getOutputStream();

            while (true)
            {
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(System.in));

                String line = reader.readLine();

                os.write(line.getBytes());
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

    }

}
複製代碼

 

 

  客戶端和其輸入輸出線程(其輸入輸出線程和服務器端的完全一樣):  

複製代碼
package com.example.network;

import java.net.Socket;

public class MainClient
{

    public static void main(String[] args) throws Exception
    {
        Socket socket = new Socket("127.0.0.1", 4000);

        new ClientInputThread(socket).start();
        new ClientOutputThread(socket).start();

    }
}
複製代碼
複製代碼
package com.example.network;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

public class ClientInputThread extends Thread
{
    private Socket socket;

    public ClientInputThread(Socket socket)
    {
        super();
        this.socket = socket;
    }

    @Override
    public void run()
    {
        try
        {
            // 獲得輸入流
            InputStream is = socket.getInputStream();

            while (true)
            {
                byte[] buffer = new byte[1024];

                int length = is.read(buffer);

                String str = new String(buffer, 0, length);

                System.out.println(str);

            }

        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

}
複製代碼
複製代碼
package com.example.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class ClientOutputThread extends Thread
{
    private Socket socket;

    public ClientOutputThread(Socket socket)
    {
        super();
        this.socket = socket;
    }

    @Override
    public void run()
    {
        try
        {

            OutputStream os = socket.getOutputStream();

            while (true)
            {
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(System.in));

                String line = reader.readLine();

                os.write(line.getBytes());
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

    }

}
複製代碼

 

  經測試成功。即從服務器端控制檯輸入,可以從客戶端接收到並輸出;也可以反過來,從客戶端控制檯輸入,那麼服務器端會同時輸出。

 

多個客戶端的程序實驗

  可以啓動多個客戶端,同時與服務器進行交互。這裏還是採用上面的MainServer和MainClient及其輸入輸出線程代碼。

  這部分做實驗的時候需要使用命令行,因爲Eclipse裏面每次Run的時候都會重新啓動程序,即想要Run第二個客戶端的時候總是先關閉第一個客戶端(因爲它們運行的是同一個程序),這樣,即只能有一個客戶端存在。

  在命令行運行的方法如下:

  因爲源文件帶有包名,所以編譯採用:

  javac –d . 源文件名.java

  注意d和.之間有一個空格。

  可以使用通配符編譯所有的源文件,即使用:

  javac –d . *.java

  編譯之後執行:

  java 完整包名+類名

  先啓動服務器程序,之後新開命令行窗口啓動客戶端程序,結果如下:

  

 (一個客戶端時交互正常)

  

  

  (多個客戶端交互異常)

  

  經實驗,發現在一個服務器多個客戶端的情況下,客戶端可以流暢地向服務器發送信息,但是當服務器發送信息時,就會出現問題,並不是每一個客戶端都能收到信息。

  如圖中,當服務器發送語句時,第一個客戶端收到了(並且是發送後多按下一個回車才收到),第二個客戶端沒有收到。

  後面試驗了幾個語句都是這樣:

  

 

 

實現服務器支持多客戶機通信

  服務器端的程序需要爲每一個與客戶機連接的socket建立一個線程,來解決同時通信的問題。

  服務器端應該管理一個socket的集合。

  即要完成一個功能完善的客戶端和服務器通信程序,代碼還是需要進一步完善的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章