TCP Socket 即時通訊 API 示例
阿新 • • 發佈:2018-11-23
sin puts 簡單的 rri lin .cn 構造 live system
案例
SocketActivity
服務端 Server
客戶端 Client
常見異常
API
ServerSocket
Socket
Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | [email protected] |
TCP Socket 即時通訊 API 示例
目錄
目錄案例
SocketActivity
服務端 Server
客戶端 Client
常見異常
API
ServerSocket
Socket
案例
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); } }
常見異常
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
構造方法
- ServerSocket() 創建非綁定服務器套接字。
ServerSocket(int port)
創建綁定到特定端口的服務器套接字。- ServerSocket(int port, int backlog) 利用指定的 backlog 創建服務器套接字並將其綁定到指定的本地端口號。
- ServerSocket(int port, int backlog, InetAddress bindAddr) 使用指定的端口、偵聽 backlog 和要綁定到的本地 IP 地址創建服務器。
常用方法
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
構造方法
- 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) 創建一個套接字並將其連接到指定遠程主機上的指定遠程端口。
常用方法
- 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。
2018-11-23
TCP Socket 即時通訊 API 示例