TCP UDP Socket 即時通信 API 示例 MD

Markdown版本筆記 個人GitHub首頁 個人博客 個人微信 個人郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

TCP Socket 即時通信 API 示例html


目錄

TCP 案例

SocketActivity

public class SocketActivity extends ListActivity implements Client.MsgListener {
    public static final int PORT = 11232;
    public static final String HOST = "192.168.1.187"; //此 IP 爲內網 IP ,全部只有在同一局域網下才能通信(鏈接同一WIFI便可)

    private TextView msgTextView;
    private EditText editText;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"開啓服務器",
            "開啓客戶端",
            "客戶端發消息",
            "客戶端下線"};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));

        editText = new EditText(this);
        getListView().addFooterView(editText);

        msgTextView = new TextView(this);
        getListView().addFooterView(msgTextView);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        switch (position) {
            case 0:
                Server.SINGLETON.startServer(PORT);
                break;
            case 1:
                Client.SINGLETON.startClient(HOST, PORT);
                Client.SINGLETON.setListener(this);
                break;
            case 2:
                Client.SINGLETON.sendMsg(editText.getText().toString());
                break;
            case 3:
                Client.SINGLETON.exit();
                break;
            default:
                break;
        }
    }

    private SpannableString getSpannableString(String string, int color) {
        SpannableString mSpannableString = new SpannableString(string);
        ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
        mSpannableString.setSpan(colorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return mSpannableString;
    }

    @Override
    public void onReveiveMsg(String message) {
        runOnUiThread(() -> msgTextView.append(getSpannableString(message + "\n", Color.BLUE)));
    }

    @Override
    public void onSendMsg(String message) {
        runOnUiThread(() -> {
            String text = Client.SINGLETON.getName() + " : " + editText.getText().toString() + "\n";
            msgTextView.append(getSpannableString(text, Color.RED));
        });
    }
}

服務端 Server

public enum Server {
    SINGLETON;

    public static final int MAX_TEXT_SIZE = 1024;
    public static final String CLIENT_EXIT_CMD = "拜拜";
    public static final String CHARSET = "GBK";

    private Set<Socket> socketSet;
    private boolean serverExit = false;
    private ServerSocket server;//服務器對象

    Server() {
        socketSet = new HashSet<>();//用戶集合
    }

    public void startServer(int port) {
        if (server != null && !server.isClosed()) {
            System.out.println("服務器已開啓,不須要重複開啓");
        } else {
            new Thread(() -> {
                try {
                    server = new ServerSocket(port);
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("服務器啓動失敗");
                    return;
                }

                System.out.println("服務器啓動成功");
                //循環監聽
                while (!serverExit) {
                    try {
                        Socket socket = server.accept();//獲取鏈接過來的客戶端對象。阻塞方法
                        socketSet.add(socket);
                        sendUserMsg(socket, getName(socket) + " 上線了, 當前 " + socketSet.size() + " 人在線");
                        listenerUserMsg(socket); //監聽客戶端發送的消息,並轉發給其餘用戶
                    } catch (IOException e) {//用戶下線
                        e.printStackTrace();
                    }
                }
                System.out.println("服務器已關閉");
            }).start();
        }
    }

    private void listenerUserMsg(Socket socket) {
        new Thread(() -> {
            try {
                byte[] bytes = new byte[MAX_TEXT_SIZE];
                int count;
                boolean clinetExit = false;
                while (!serverExit && !clinetExit && !socket.isClosed() && (count = socket.getInputStream().read(bytes)) != -1) {
                    String text = new String(bytes, 0, count, CHARSET);
                    System.out.println("服務器已收到【" + getName(socket) + "】發送的信息【" + text + "】");
                    clinetExit = CLIENT_EXIT_CMD.equals(text);
                    sendUserMsg(socket, getName(socket) + " : " + text);
                }
            } catch (IOException e) {//關閉與此用戶相關的流
                e.printStackTrace();
                System.out.println(getName(socket) + " 異常掉線");
            } finally {
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    socketSet.remove(socket);
                    sendUserMsg(socket, getName(socket) + " 下線了, 當前 " + socketSet.size() + " 人在線");
                }
            }
        }).start();
    }

    private void sendUserMsg(Socket socket, String text) {
        for (Socket otherSocket : socketSet) {//遍歷全部的在線用戶
            if (otherSocket != null && !otherSocket.isClosed() && !otherSocket.equals(socket)) {
                try {
                    OutputStream outputStream = otherSocket.getOutputStream();//獲取相應的輸出流,把信息發送過去
                    outputStream.write(text.getBytes(CHARSET));
                    outputStream.flush();
                    System.out.println("服務器已轉發信息【" + text + "】給【" + getName(otherSocket) + "】");
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println(getName(socket) + " 異常");
                }
            }
        }
    }

    private String getName(Socket socket) {
        return "用戶" + socket.getInetAddress().getHostAddress() + "_" + socket.getPort();
    }
}

客戶端 Client

public enum Client {

    SINGLETON;

    Client() {
    }

    public static final int MAX_TEXT_SIZE = 1024;
    public static final String CLIENT_EXIT_CMD = "拜拜";
    public static final String CHARSET = "GBK";

    private boolean exit = false;
    private Socket socket;

    private MsgListener listener;

    public void startClient(String host, int port) {
        if (socket != null && !socket.isClosed()) {
            System.out.println("客戶端已開啓,不須要重複開啓");
        } else {
            new Thread(() -> {
                try {
                    socket = new Socket(host, port);//建立客戶端對象
                    listenerUserMsg(); //監聽消息
                    System.out.println("客戶端已開啓成功");
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("客戶端已開啓失敗");
                }
            }).start();
        }
    }

    private void listenerUserMsg() {
        new Thread(() -> {
            try {
                byte[] bytes = new byte[MAX_TEXT_SIZE];
                int count;
                while (!exit && socket != null && !socket.isClosed() && (count = socket.getInputStream().read(bytes)) != -1) {
                    String text = new String(bytes, 0, count, CHARSET);
                    System.out.println(getName() + " 收到信息【" + text + "】");
                    if (listener != null) {
                        listener.onReveiveMsg(text);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    public void sendMsg(String text) {
        new Thread(() -> {
            if (socket != null && !socket.isClosed()) {
                try {
                    socket.getOutputStream().write(text.getBytes(CHARSET));//獲取socket流中的輸出流將指定的數據寫出去
                    if (listener != null) {
                        listener.onSendMsg(text);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void exit() {
        new Thread(() -> {
            exit = true;
            if (socket != null && !socket.isClosed()) {
                try {
                    socket.getOutputStream().write(CLIENT_EXIT_CMD.getBytes(CHARSET));
                    socket.close();
                    System.out.println("客戶端下線成功");
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("客戶端下線異常");
                }
            } else {
                System.out.println("客戶端已下線,不須要重複下線");
            }
        }).start();
    }

    public String getName() {
        return "用戶" + socket.getInetAddress().getHostAddress() + "_" + socket.getPort();
    }

    public void setListener(MsgListener listener) {
        this.listener = listener;
    }

    public interface MsgListener {
        void onReveiveMsg(String message);

        void onSendMsg(String message);
    }
}

UDP 案例

SocketActivity

public class SocketActivity extends ListActivity implements Client.MsgListener {
    private boolean tag = true;
    public static final int PORT1 = 11233;
    public static final int PORT2 = 11234;
    public static final String HOST1 = "192.168.2.124";
    public static final String HOST2 = "192.168.2.177";

    private TextView msgTextView;
    private EditText editText;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"切換配置",
            "開啓客戶端",
            "客戶端發消息",
            "客戶端下線"};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));

        editText = new EditText(this);
        getListView().addFooterView(editText);

        msgTextView = new TextView(this);
        getListView().addFooterView(msgTextView);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        switch (position) {
            case 0:
                tag = !tag;
                break;
            case 1:
                Client.SINGLETON.startClient(tag ? PORT1 : PORT2);
                Client.SINGLETON.setListener(this);
                break;
            case 2:
                try {
                    //getLocalHost(),getByName("127.0.0.1"),getByName("192.168.0.255"),getByAddress(new byte[]{127,0,0,1})
                    InetAddress address = InetAddress.getByName(tag ? HOST2 : HOST1);
                    Client.SINGLETON.sendMsg(editText.getText().toString(), address, tag ? PORT2 : PORT1);
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                break;
            case 3:
                Client.SINGLETON.exit();
                break;
            default:
                break;
        }
    }

    private SpannableString getSpannableString(String string, int color) {
        SpannableString mSpannableString = new SpannableString(string);
        ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
        mSpannableString.setSpan(colorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return mSpannableString;
    }

    @Override
    public void onReveiveMsg(String message) {
        runOnUiThread(() -> msgTextView.append(getSpannableString(message + "\n", Color.BLUE)));
    }

    @Override
    public void onSendMsg(String message) {
        runOnUiThread(() -> {
            String text = Client.SINGLETON.getName() + " : " + editText.getText().toString() + "\n";
            msgTextView.append(getSpannableString(text, Color.RED));
        });
    }
}

Client

public enum Client {

    SINGLETON;

    Client() {
    }

    public static final int MAX_TEXT_SIZE = 1024;
    public static final String CHARSET = "GBK";

    private boolean exit = false;
    private DatagramSocket send;
    private DatagramSocket receiver;
    private MsgListener listener;

    public void startClient(int port) {
        if (send != null && !send.isClosed()) {
            System.out.println("客戶端" + port + "已開啓,不須要重複開啓");
        } else {
            new Thread(() -> {
                try {
                    send = new DatagramSocket();
                    receiver = new DatagramSocket(port);
                    listenerUserMsg(); //監聽消息
                    System.out.println("客戶端" + port + "已開啓成功");
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("客戶端" + port + "已開啓失敗");
                }
            }).start();
        }
    }

    private void listenerUserMsg() {
        new Thread(() -> {
            try {
                byte[] bytes = new byte[MAX_TEXT_SIZE];
                DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
                while (!exit && receiver != null && !receiver.isClosed()) {
                    receiver.receive(packet);//持續接收數據
                    String text = new String(packet.getData(), 0, packet.getLength(), CHARSET);
                    System.out.println(getName() + " 收到信息【" + text + "】");
                    if (listener != null) {
                        listener.onReveiveMsg(text);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    public void sendMsg(String text, InetAddress address, int port) {
        new Thread(() -> {
            if (send != null && !send.isClosed()) {
                try {
                    String content = getName() + " : " + text;
                    byte[] buf = content.getBytes(CHARSET);
                    DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
                    send.send(packet);
                    if (listener != null) {
                        listener.onSendMsg(text);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void exit() {
        new Thread(() -> {
            exit = true;
            if (send != null && !send.isClosed()) {
                send.close();
            }
            if (receiver != null && !receiver.isClosed()) {
                receiver.close();
            }
            System.out.println("客戶端下線成功");
        }).start();
    }

    public String getName() {
        return "用戶" + receiver.getLocalPort();
    }

    public void setListener(MsgListener listener) {
        this.listener = listener;
    }

    public interface MsgListener {
        void onReveiveMsg(String message);

        void onSendMsg(String message);
    }
}

常見異常

  • BindException:Address already in use: JVM_Bind 該異常發生在服務器端進行new ServerSocket(port)操做時。異常的緣由是此port端口已經被佔用。此時用netstat –an命令,能夠看到一個Listending狀態的端口。只須要找一個沒有被佔用的端口就能解決這個問題。
  • ConnectException: Connection refused: connect 該異常發生在客戶端進行new Socket(ip, port)操做時,該異常發生的緣由是或者具備此ip地址的機器不能找到,或者是該ip存在但找不到指定的端口進行監聽。出現該問題,首先檢查客戶端的ip和port是否寫錯了,若是正確則從客戶端ping一下服務器看是否能ping通,若是能ping通再看在服務器端的監聽指定端口的程序是否啓動。
  • Socket is closed 該異常在客戶端和服務器都可能發生。異常的緣由是鏈接已被關閉後(調用了Socket的close方法)再對網絡鏈接進行讀寫操做。
  • SocketException:(Connection reset 或者 Connect reset by peer:Socket write error) 該異常在客戶端和服務器端均有可能發生,引發該異常的緣由有兩個,第一個就是若是一端的Socket被關閉,另外一端仍發送數據,發送的第一個數據包引起該異常(Connect reset by peer)。另外一個是一端退出,但退出時並未關閉該鏈接,另外一端若是在從鏈接中讀數據則拋出該異常(Connection reset)。簡單的說就是在鏈接斷開後的讀和寫操做引發的。
  • SocketException: Broken pipe 該異常在客戶端和服務器均有可能發生。在上面那種異常的第一種狀況中(也就是拋出SocketExcepton:Connect reset by peer:Socket write error),若是再繼續寫數據則拋出該異常。前兩個異常的解決方法是首先確保程序退出前關閉全部的網絡鏈接,其次是要檢測對方的關閉鏈接操做,發現對方關閉鏈接後本身也要關閉該鏈接。

API

ServerSocket

構造方法java

  • ServerSocket() 建立非綁定服務器套接字。
  • ServerSocket(int port) 建立綁定到特定端口的服務器套接字。
  • ServerSocket(int port, int backlog) 利用指定的 backlog 建立服務器套接字並將其綁定到指定的本地端口號。
  • ServerSocket(int port, int backlog, InetAddress bindAddr) 使用指定的端口、偵聽 backlog 和要綁定到的本地 IP 地址建立服務器。

經常使用方法android

  • Socket accept() 偵聽並接受到此套接字的鏈接。
  • void bind(SocketAddress endpoint) 將 ServerSocket 綁定到特定地址(IP 地址和端口號)。
  • void bind(SocketAddress endpoint, int backlog) 將 ServerSocket 綁定到特定地址(IP 地址和端口號)。
  • void close() 關閉此套接字。
  • ServerSocketChannel getChannel() 返回與此套接字關聯的惟一 ServerSocketChannel 對象(若是有)。
  • InetAddress getInetAddress() 返回此服務器套接字的本地地址。
  • int getLocalPort() 返回此套接字在其上偵聽的端口。
  • SocketAddress getLocalSocketAddress() 返回此套接字綁定的端點的地址,若是還沒有綁定則返回 null。
  • int getReceiveBufferSize() 獲取此 ServerSocket 的 SO_RCVBUF 選項的值,該值是將用於今後 ServerSocket 接受的套接字的建議緩衝區大小。
  • boolean getReuseAddress() 測試是否啓用 SO_REUSEADDR。
  • int getSoTimeout() 獲取 SO_TIMEOUT 的設置。
  • protected void implAccept(Socket s) ServerSocket 的子類使用此方法重寫 accept() 以返回它們本身的套接字子類。
  • boolean isBound() 返回 ServerSocket 的綁定狀態。
  • boolean isClosed() 返回 ServerSocket 的關閉狀態。
  • void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 設置此 ServerSocket 的性能首選項。
  • void setReceiveBufferSize(int size) 爲今後 ServerSocket 接受的套接字的 SO_RCVBUF 選項設置默認建議值。
  • void setReuseAddress(boolean on) 啓用/禁用 SO_REUSEADDR 套接字選項。
  • static void setSocketFactory(SocketImplFactory fac) 爲應用程序設置服務器套接字實現工廠。
  • void setSoTimeout(int timeout) 經過指定超時值啓用/禁用 SO_TIMEOUT,以毫秒爲單位。
  • String toString() 做爲 String 返回此套接字的實現地址和實現端口。

Socket

構造方法git

  • Socket() 經過系統默認類型的 SocketImpl 建立未鏈接套接字
  • Socket(InetAddress address, int port) 建立一個流套接字並將其鏈接到指定IP地址的指定端口號
  • Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 建立一個套接字並將其鏈接到指定遠程地址上的指定遠程端口。
  • Socket(Proxy proxy) 建立一個未鏈接的套接字並指定代理類型(若是有),該代理無論其餘設置如何都應被使用。
  • Socket(SocketImpl impl) 使用用戶指定的 SocketImpl 建立一個未鏈接 Socket。
  • Socket(String host, int port) 建立一個流套接字並將其鏈接到指定主機上的指定端口號。
  • Socket(String host, int port, InetAddress localAddr, int localPort) 建立一個套接字並將其鏈接到指定遠程主機上的指定遠程端口。

經常使用方法github

  • void bind(SocketAddress bindpoint) 將套接字綁定到本地地址。
  • void close() 關閉此套接字。
  • void connect(SocketAddress endpoint) 將此套接字鏈接到服務器。
  • void connect(SocketAddress endpoint, int timeout) 將此套接字鏈接到服務器,並指定一個超時值
  • SocketChannel getChannel() 返回與此數據報套接字關聯的惟一 SocketChannel 對象(若是有)。
  • InetAddress getInetAddress() 返回套接字鏈接的地址。
  • InputStream getInputStream() 返回此套接字的輸入流。
  • boolean getKeepAlive() 測試是否啓用 SO_KEEPALIVE。
  • InetAddress getLocalAddress() 獲取套接字綁定的本地地址。
  • int getLocalPort() 返回此套接字綁定到的本地端口。
  • SocketAddress getLocalSocketAddress() 返回此套接字綁定的端點的地址,若是還沒有綁定則返回 null。
  • boolean getOOBInline() 測試是否啓用 OOBINLINE。
  • OutputStream getOutputStream() 返回此套接字的輸出流。
  • int getPort() 返回此套接字鏈接到的遠程端口。
  • int getReceiveBufferSize() 獲取此 Socket 的 SO_RCVBUF 選項的值,該值是平臺在 Socket 上輸入時使用的緩衝區大小。
  • SocketAddress getRemoteSocketAddress() 返回此套接字鏈接的端點的地址,若是未鏈接則返回 null。
  • boolean getReuseAddress() 測試是否啓用 SO_REUSEADDR。
  • int getSendBufferSize() 獲取此 Socket 的 SO_SNDBUF 選項的值,該值是平臺在 Socket 上輸出時使用的緩衝區大小。
  • int getSoLinger() 返回 SO_LINGER 的設置。
  • int getSoTimeout() 返回 SO_TIMEOUT 的設置。
  • boolean getTcpNoDelay() 測試是否啓用 TCP_NODELAY。
  • int getTrafficClass() 爲今後 Socket 上發送的包獲取 IP 頭中的流量類別或服務類型。
  • boolean isBound() 返回套接字的綁定狀態。
  • boolean isClosed() 返回套接字的關閉狀態。
  • boolean isConnected() 返回套接字的鏈接狀態。
  • boolean isInputShutdown() 返回是否關閉套接字鏈接的半讀狀態 (read-half) 。
  • boolean isOutputShutdown() 返回是否關閉套接字鏈接的半寫狀態 (write-half) 。
  • void sendUrgentData(int data) 在套接字上發送一個緊急數據字節。
  • void setKeepAlive(boolean on) 啓用/禁用 SO_KEEPALIVE。
  • void setOOBInline(boolean on) 啓用/禁用 OOBINLINE(TCP 緊急數據的接收者) 默認狀況下,此選項是禁用的,即在套接字上接收的 TCP 緊急數據被靜默丟棄。
  • void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 設置此套接字的性能偏好。
  • void setReceiveBufferSize(int size) 將此 Socket 的 SO_RCVBUF 選項設置爲指定的值。
  • void setReuseAddress(boolean on) 啓用/禁用 SO_REUSEADDR 套接字選項。
  • void setSendBufferSize(int size) 將此 Socket 的 SO_SNDBUF 選項設置爲指定的值。
  • static void setSocketImplFactory(SocketImplFactory fac) 爲應用程序設置客戶端套接字實現工廠。
  • void setSoLinger(boolean on, int linger) 啓用/禁用具備指定逗留時間(以秒爲單位)的 SO_LINGER。
  • void setSoTimeout(int timeout) 啓用/禁用帶有指定超時值的 SO_TIMEOUT,以毫秒爲單位。
  • void setTcpNoDelay(boolean on) 啓用/禁用 TCP_NODELAY(啓用/禁用 Nagle 算法)。
  • void setTrafficClass(int tc) 爲今後 Socket 上發送的包在 IP 頭中設置流量類別 (traffic class) 或服務類型八位組 (type-of-service octet) 。
  • void shutdownInput() 此套接字的輸入流置於「流的末尾」。
  • void shutdownOutput() 禁用此套接字的輸出流。
  • String toString() 將此套接字轉換爲 String。

DatagramSocket

構造方法web

  • DatagramSocket() 構造數據報套接字並將其綁定到本地主機上任何可用的端口。
  • DatagramSocket(int port) 建立數據報套接字並將其綁定到本地主機上的指定端口。
  • DatagramSocket(int port, InetAddress laddr) 綁定到指定的本地地址。
  • DatagramSocket(SocketAddress bindaddr) 綁定到指定的本地套接字地址。
  • protected DatagramSocket(DatagramSocketImpl impl) 建立帶有指定impl 的未綁定數據報套接字

經常使用方法算法

  • void bind(SocketAddress addr) 將此 DatagramSocket 綁定到特定的地址和端口。
  • void close() 關閉此數據報套接字。
  • void connect(InetAddress address, int port) 將套接字鏈接到此套接字的遠程地址。
  • void connect(SocketAddress addr) 將此套接字鏈接到遠程套接字地址(IP 地址 + 端口號)。
  • void disconnect() 斷開套接字的鏈接。
  • boolean getBroadcast() 檢測是否啓用了 SO_BROADCAST。
  • DatagramChannel getChannel() 返回與此數據報套接字關聯的惟一 DatagramChannel 對象(若是有)。
  • InetAddress getInetAddress() 返回此套接字鏈接的地址。
  • InetAddress getLocalAddress() 獲取套接字綁定的本地地址。
  • int getLocalPort() 返回此套接字綁定的本地主機上的端口號。
  • SocketAddress getLocalSocketAddress() 返回此套接字綁定的端點的地址,若是還沒有綁定則返回 null。
  • int getPort() 返回此套接字的端口。
  • int getReceiveBufferSize() 獲取此 DatagramSocket 的 SO_RCVBUF 選項的值,該值是平臺在 DatagramSocket 上輸入時使用的緩衝區大小。
  • SocketAddress getRemoteSocketAddress() 返回此套接字鏈接的端點的地址,若是未鏈接則返回 null。
  • boolean getReuseAddress() 檢測是否啓用了 SO_REUSEADDR。
  • int getSendBufferSize() 獲取此 DatagramSocket 的 SO_SNDBUF 選項的值,該值是平臺在 DatagramSocket 上輸出時使用的緩衝區大小。
  • int getSoTimeout() 獲取 SO_TIMEOUT 的設置。
  • int getTrafficClass() 爲今後 DatagramSocket 上發送的包獲取 IP 數據報頭中的流量類別或服務類型
  • boolean isBound() 返回套接字的綁定狀態。
  • boolean isClosed() 返回是否關閉了套接字。
  • boolean isConnected() 返回套接字的鏈接狀態。
  • void receive(DatagramPacket p) 今後套接字接收數據報包。
  • void send(DatagramPacket p) 今後套接字發送數據報包。
  • void setBroadcast(boolean on) 啓用/禁用 SO_BROADCAST。
  • static void setDatagramSocketImplFactory(DatagramSocketImplFactory fac) 爲應用程序設置數據報套接字實現工廠
  • void setReceiveBufferSize(int size) 將此DatagramSocket的 SO_RCVBUF 選項設置爲指定的值
  • void setReuseAddress(boolean on) 啓用/禁用 SO_REUSEADDR 套接字選項。
  • void setSendBufferSize(int size) 將此 DatagramSocket 的 SO_SNDBUF 選項設置爲指定的值
  • void setSoTimeout(int timeout) 啓用/禁用帶有指定超時值的 SO_TIMEOUT,以毫秒爲單位。
  • void setTrafficClass(int tc) 爲今後 DatagramSocket 上發送的數據報在 IP 數據報頭中設置流量類別 (traffic class) 或服務類型八位組 (type-of-service octet)。

DatagramPacket

構造方法
接收瀏覽器

  • DatagramPacket(byte[] buf, int length) 構造 DatagramPacket,用來接收長度爲 length 的數據包。
  • DatagramPacket(byte[] buf, int offset, int length) 構造 DatagramPacket,用來接收長度爲 length 的包,在緩衝區中指定了偏移量。

發送到 InetAddress服務器

  • DatagramPacket(byte[] buf, int length, InetAddress address, int port) 構造數據報包,用來將長度爲 length 的包 buf 發送指定主機address上的指定端口號port
  • DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 構造數據報包,用來將長度爲 length 偏移量爲 offset 的包發送到指定主機上的指定端口號。

發送到 SocketAddress微信

  • DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 構造數據報包,用來將長度爲 length 偏移量爲 offset 的包發送到指定主機上的指定端口號。
  • DatagramPacket(byte[] buf, int length, SocketAddress address) 構造數據報包,用來將長度爲 length 的包發送到指定主機上的指定端口號。

經常使用方法
get 方法

  • InetAddress getAddress() 返回某臺機器的 IP 地址,此數據報將要發往該機器或者是從該機器接收到的
  • byte[] getData() 返回數據緩衝區。
  • int getLength() 返回將要發送或接收到的數據的長度。
  • int getOffset() 返回將要發送或接收到的數據的偏移量。
  • int getPort() 返回某臺遠程主機的端口號,此數據報將要發往該主機或者是從該主機接收到的。
  • SocketAddress getSocketAddress() 獲取要將此包發送到的或發出此數據報的遠程主機的 SocketAddress

set 方法

  • void setAddress(InetAddress iaddr) 設置要將此數據報發往的那臺機器的 IP 地址。
  • void setData(byte[] buf) 爲此包設置數據緩衝區。
  • void setData(byte[] buf, int offset, int length) 爲此包設置數據緩衝區。
  • void setLength(int length) 爲此包設置長度。
  • void setPort(int iport) 設置要將此數據報發往的遠程主機上的端口號。
  • void setSocketAddress(SocketAddress address) 設置要將此數據報發往的遠程主機的 SocketAddress(一般爲 IP 地址 + 端口號)。

WebSocket

簡介

參考

隨着互聯網的發展,傳統的HTTP協議已經很難知足Web應用日益複雜的需求了。近年來,隨着HTML5的誕生,WebSocket協議被提出,它實現了瀏覽器與服務器的全雙工通訊,擴展了瀏覽器與服務端的通訊功能,使服務端也能主動向客戶端發送數據

咱們知道,傳統的HTTP協議是無狀態的,每次請求(request)都要由客戶端主動發起,服務端進行處理後返回response結果,而服務端很難主動向客戶端發送數據;這種客戶端是主動方,服務端是被動方的傳統Web模式 對於信息變化不頻繁的Web應用來講形成的麻煩較小,而對於涉及實時信息的Web應用卻帶來了很大的不便,如帶有即時通訊、實時數據、訂閱推送等功能的應用。在WebSocket規範提出以前,開發人員若要實現這些實時性較強的功能,常常會使用折衷的解決方法:輪詢(polling)和Comet技術。其實後者本質上也是一種輪詢,只不過有所改進。

輪詢是最原始的實現實時Web應用的解決方案。輪詢技術要求客戶端以設定的時間間隔週期性地向服務端發送請求,頻繁地查詢是否有新的數據改動。明顯地,這種方法會致使過多沒必要要的請求,浪費流量和服務器資源。

Comet技術又能夠分爲長輪詢和流技術。長輪詢改進了上述的輪詢技術,減少了無用的請求。它會爲某些數據設定過時時間,當數據過時後纔會向服務端發送請求;這種機制適合數據的改動不是特別頻繁的狀況。流技術一般是指客戶端使用一個隱藏的窗口與服務端創建一個HTTP長鏈接,服務端會不斷更新鏈接狀態以保持HTTP長鏈接存活;這樣的話,服務端就能夠經過這條長鏈接主動將數據發送給客戶端;流技術在大併發環境下,可能會考驗到服務端的性能。

這兩種技術都是基於請求-應答模式,都不算是真正意義上的實時技術;它們的每一次請求、應答,都浪費了必定流量在相同的頭部信息上,而且開發複雜度也較大。

伴隨着HTML5推出的WebSocket,真正實現了Web的實時通訊,使B/S模式具有了C/S模式的實時通訊能力。WebSocket的工做流程是這樣的:瀏覽器經過JavaScript向服務端發出創建WebSocket鏈接的請求,在WebSocket鏈接創建成功後,客戶端和服務端就能夠經過 TCP 鏈接傳輸數據。由於WebSocket鏈接本質上是TCP鏈接,不須要每次傳輸都帶上重複的頭部數據,因此它的數據傳輸量比輪詢和Comet技術小 了不少。本文不詳細地介紹WebSocket規範,主要介紹下WebSocket在Java Web中的實現。

JavaEE 7中出了JSR-356:Java API for WebSocket規範。很多Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat從7.0.27開始支持 WebSocket,從7.0.47開始支持JSR-356,下面的Demo代碼也是須要部署在Tomcat7.0.47以上的版本才能運行。

服務端

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

/**
 * @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端,
 * 註解的值將被用於監聽用戶鏈接的終端訪問URL地址,客戶端能夠經過這個URL來鏈接到WebSocket服務器端
 */
@ServerEndpoint("/websocket")
public class WebSocketTest {

    private static int onlineCount = 0; //當前在線鏈接數

    //存放每一個客戶端對應的WebSocket對象
    private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
    private Session session;//與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據

    /**
     * 鏈接創建成功調用的方法
     * @param session  可選的參數。session爲與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
     */
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        webSocketSet.add(this);     //加入set中
        onlineCount++;           //在線數加1
        System.out.println("有新鏈接加入!當前在線人數爲" + getOnlineCount());
    }

    /**
     * 鏈接關閉調用的方法
     */
    @OnClose
    public void onClose(){
        webSocketSet.remove(this);  //從set中刪除
        onlineCount--;           //在線數減1
        System.out.println("有一鏈接關閉!當前在線人數爲" + getOnlineCount());
    }

    /**
     * 收到客戶端消息後調用的方法
     * @param message 客戶端發送過來的消息
     * @param session 可選的參數
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("來自客戶端的消息:" + message);
        //羣發消息
        for(WebSocketTest item: webSocketSet){
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    /**
     * 發生錯誤時調用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error){
        System.out.println("發生錯誤");
        error.printStackTrace();
    }

    /**
     * 這個方法與上面幾個方法不同。沒有用註解,是根據本身須要添加的方法。
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException{
        this.session.getBasicRemote().sendText(message);
        //this.session.getAsyncRemote().sendText(message);
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void subOnlineCount() {
        WebSocketTest.onlineCount--;
    }
}

客戶端

public enum WebClient {
    SINGLETON;

    private static final String CHARSET = "UTF-8";
    private WebSocketClient client = null;
    private MsgListener listener;

    public void startClient(URI serverUri) {
        if (client != null && !client.isClosed()) {
            System.out.println("客戶端" + getName() + "已開啓,不須要重複開啓");
        } else {
            new Thread(() -> {
                client = new WebSocketClient(serverUri) {
                    @Override
                    public void onOpen(ServerHandshake handshakedata) {
                        System.out.println("onOpen,握手");
                        Iterator<String> it = handshakedata.iterateHttpFields();
                        while (it.hasNext()) {
                            String key = it.next();
                            System.out.println(key + ":" + handshakedata.getFieldValue(key));
                        }
                    }

                    @Override
                    public void onMessage(String message) {
                        System.out.println("onMessage,接收到消息【" + message + "】");
                        if (listener != null) {
                            listener.onReveiveMsg(message);
                        }
                    }

                    @Override
                    public void onClose(int code, String reason, boolean remote) {
                        System.out.println("onClose,關閉:code=" + code + ",reason=" + reason + ",remote=" + remote);
                    }

                    @Override
                    public void onError(Exception ex) {
                        ex.printStackTrace();
                    }
                };
                //client.connect(); //非阻塞方法
                try {
                    boolean success = client.connectBlocking();//阻塞方法
                    System.out.println("客戶端" + getName() + "鏈接" + (success ? "成功" : "失敗"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("客戶端" + getName() + "鏈接異常");
                }
            }).start();
        }
    }

    public void sendMsg(String text) {
        new Thread(() -> {
            if (client != null && !client.isClosed()) {
                try {
                    String content = getName() + " : " + text;
                    client.send(content.getBytes(CHARSET));
                    if (listener != null) {
                        listener.onSendMsg(text);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void exit() {
        new Thread(() -> {
            if (client != null && !client.isClosed()) {
                client.close();
            }
            System.out.println("客戶端下線成功");
        }).start();
    }

    public String getName() {
        return "用戶" + client.getLocalSocketAddress().getPort();
    }

    public void setListener(MsgListener listener) {
        this.listener = listener;
    }

    public interface MsgListener {
        void onReveiveMsg(String message);

        void onSendMsg(String message);
    }
}

2018-11-23

相關文章
相關標籤/搜索