Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

TCP Socket 即时通讯 API 示例


目录

TCP 案例

SocketActivity

  1. public class SocketActivity extends ListActivity implements Client.MsgListener {
  2. public static final int PORT = 11232;
  3. public static final String HOST = "192.168.1.187"; //此 IP 为内网 IP ,所有只有在同一局域网下才能通讯(连接同一WIFI即可)
  4. private TextView msgTextView;
  5. private EditText editText;
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. String[] array = {"开启服务器",
  9. "开启客户端",
  10. "客户端发消息",
  11. "客户端下线"};
  12. setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
  13. editText = new EditText(this);
  14. getListView().addFooterView(editText);
  15. msgTextView = new TextView(this);
  16. getListView().addFooterView(msgTextView);
  17. }
  18. @Override
  19. protected void onListItemClick(ListView l, View v, int position, long id) {
  20. switch (position) {
  21. case 0:
  22. Server.SINGLETON.startServer(PORT);
  23. break;
  24. case 1:
  25. Client.SINGLETON.startClient(HOST, PORT);
  26. Client.SINGLETON.setListener(this);
  27. break;
  28. case 2:
  29. Client.SINGLETON.sendMsg(editText.getText().toString());
  30. break;
  31. case 3:
  32. Client.SINGLETON.exit();
  33. break;
  34. default:
  35. break;
  36. }
  37. }
  38. private SpannableString getSpannableString(String string, int color) {
  39. SpannableString mSpannableString = new SpannableString(string);
  40. ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
  41. mSpannableString.setSpan(colorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  42. return mSpannableString;
  43. }
  44. @Override
  45. public void onReveiveMsg(String message) {
  46. runOnUiThread(() -> msgTextView.append(getSpannableString(message + "\n", Color.BLUE)));
  47. }
  48. @Override
  49. public void onSendMsg(String message) {
  50. runOnUiThread(() -> {
  51. String text = Client.SINGLETON.getName() + " : " + editText.getText().toString() + "\n";
  52. msgTextView.append(getSpannableString(text, Color.RED));
  53. });
  54. }
  55. }

服务端 Server

  1. public enum Server {
  2. SINGLETON;
  3. public static final int MAX_TEXT_SIZE = 1024;
  4. public static final String CLIENT_EXIT_CMD = "拜拜";
  5. public static final String CHARSET = "GBK";
  6. private Set<Socket> socketSet;
  7. private boolean serverExit = false;
  8. private ServerSocket server;//服务器对象
  9. Server() {
  10. socketSet = new HashSet<>();//用户集合
  11. }
  12. public void startServer(int port) {
  13. if (server != null && !server.isClosed()) {
  14. System.out.println("服务器已开启,不需要重复开启");
  15. } else {
  16. new Thread(() -> {
  17. try {
  18. server = new ServerSocket(port);
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. System.out.println("服务器启动失败");
  22. return;
  23. }
  24. System.out.println("服务器启动成功");
  25. //循环监听
  26. while (!serverExit) {
  27. try {
  28. Socket socket = server.accept();//获取连接过来的客户端对象。阻塞方法
  29. socketSet.add(socket);
  30. sendUserMsg(socket, getName(socket) + " 上线了, 当前 " + socketSet.size() + " 人在线");
  31. listenerUserMsg(socket); //监听客户端发送的消息,并转发给其他用户
  32. } catch (IOException e) {//用户下线
  33. e.printStackTrace();
  34. }
  35. }
  36. System.out.println("服务器已关闭");
  37. }).start();
  38. }
  39. }
  40. private void listenerUserMsg(Socket socket) {
  41. new Thread(() -> {
  42. try {
  43. byte[] bytes = new byte[MAX_TEXT_SIZE];
  44. int count;
  45. boolean clinetExit = false;
  46. while (!serverExit && !clinetExit && !socket.isClosed() && (count = socket.getInputStream().read(bytes)) != -1) {
  47. String text = new String(bytes, 0, count, CHARSET);
  48. System.out.println("服务器已收到【" + getName(socket) + "】发送的信息【" + text + "】");
  49. clinetExit = CLIENT_EXIT_CMD.equals(text);
  50. sendUserMsg(socket, getName(socket) + " : " + text);
  51. }
  52. } catch (IOException e) {//关闭与此用户相关的流
  53. e.printStackTrace();
  54. System.out.println(getName(socket) + " 异常掉线");
  55. } finally {
  56. if (socket != null) {
  57. try {
  58. socket.close();
  59. } catch (IOException e) {
  60. e.printStackTrace();
  61. }
  62. socketSet.remove(socket);
  63. sendUserMsg(socket, getName(socket) + " 下线了, 当前 " + socketSet.size() + " 人在线");
  64. }
  65. }
  66. }).start();
  67. }
  68. private void sendUserMsg(Socket socket, String text) {
  69. for (Socket otherSocket : socketSet) {//遍历所有的在线用户
  70. if (otherSocket != null && !otherSocket.isClosed() && !otherSocket.equals(socket)) {
  71. try {
  72. OutputStream outputStream = otherSocket.getOutputStream();//获取相应的输出流,把信息发送过去
  73. outputStream.write(text.getBytes(CHARSET));
  74. outputStream.flush();
  75. System.out.println("服务器已转发信息【" + text + "】给【" + getName(otherSocket) + "】");
  76. } catch (IOException e) {
  77. e.printStackTrace();
  78. System.out.println(getName(socket) + " 异常");
  79. }
  80. }
  81. }
  82. }
  83. private String getName(Socket socket) {
  84. return "用户" + socket.getInetAddress().getHostAddress() + "_" + socket.getPort();
  85. }
  86. }

客户端 Client

  1. public enum Client {
  2. SINGLETON;
  3. Client() {
  4. }
  5. public static final int MAX_TEXT_SIZE = 1024;
  6. public static final String CLIENT_EXIT_CMD = "拜拜";
  7. public static final String CHARSET = "GBK";
  8. private boolean exit = false;
  9. private Socket socket;
  10. private MsgListener listener;
  11. public void startClient(String host, int port) {
  12. if (socket != null && !socket.isClosed()) {
  13. System.out.println("客户端已开启,不需要重复开启");
  14. } else {
  15. new Thread(() -> {
  16. try {
  17. socket = new Socket(host, port);//创建客户端对象
  18. listenerUserMsg(); //监听消息
  19. System.out.println("客户端已开启成功");
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. System.out.println("客户端已开启失败");
  23. }
  24. }).start();
  25. }
  26. }
  27. private void listenerUserMsg() {
  28. new Thread(() -> {
  29. try {
  30. byte[] bytes = new byte[MAX_TEXT_SIZE];
  31. int count;
  32. while (!exit && socket != null && !socket.isClosed() && (count = socket.getInputStream().read(bytes)) != -1) {
  33. String text = new String(bytes, 0, count, CHARSET);
  34. System.out.println(getName() + " 收到信息【" + text + "】");
  35. if (listener != null) {
  36. listener.onReveiveMsg(text);
  37. }
  38. }
  39. } catch (IOException e) {
  40. e.printStackTrace();
  41. }
  42. }).start();
  43. }
  44. public void sendMsg(String text) {
  45. new Thread(() -> {
  46. if (socket != null && !socket.isClosed()) {
  47. try {
  48. socket.getOutputStream().write(text.getBytes(CHARSET));//获取socket流中的输出流将指定的数据写出去
  49. if (listener != null) {
  50. listener.onSendMsg(text);
  51. }
  52. } catch (IOException e) {
  53. e.printStackTrace();
  54. }
  55. }
  56. }).start();
  57. }
  58. public void exit() {
  59. new Thread(() -> {
  60. exit = true;
  61. if (socket != null && !socket.isClosed()) {
  62. try {
  63. socket.getOutputStream().write(CLIENT_EXIT_CMD.getBytes(CHARSET));
  64. socket.close();
  65. System.out.println("客户端下线成功");
  66. } catch (IOException e) {
  67. e.printStackTrace();
  68. System.out.println("客户端下线异常");
  69. }
  70. } else {
  71. System.out.println("客户端已下线,不需要重复下线");
  72. }
  73. }).start();
  74. }
  75. public String getName() {
  76. return "用户" + socket.getInetAddress().getHostAddress() + "_" + socket.getPort();
  77. }
  78. public void setListener(MsgListener listener) {
  79. this.listener = listener;
  80. }
  81. public interface MsgListener {
  82. void onReveiveMsg(String message);
  83. void onSendMsg(String message);
  84. }
  85. }

UDP 案例

SocketActivity

  1. public class SocketActivity extends ListActivity implements Client.MsgListener {
  2. private boolean tag = true;
  3. public static final int PORT1 = 11233;
  4. public static final int PORT2 = 11234;
  5. public static final String HOST1 = "192.168.2.124";
  6. public static final String HOST2 = "192.168.2.177";
  7. private TextView msgTextView;
  8. private EditText editText;
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. String[] array = {"切换配置",
  12. "开启客户端",
  13. "客户端发消息",
  14. "客户端下线"};
  15. setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
  16. editText = new EditText(this);
  17. getListView().addFooterView(editText);
  18. msgTextView = new TextView(this);
  19. getListView().addFooterView(msgTextView);
  20. }
  21. @Override
  22. protected void onListItemClick(ListView l, View v, int position, long id) {
  23. switch (position) {
  24. case 0:
  25. tag = !tag;
  26. break;
  27. case 1:
  28. Client.SINGLETON.startClient(tag ? PORT1 : PORT2);
  29. Client.SINGLETON.setListener(this);
  30. break;
  31. case 2:
  32. try {
  33. //getLocalHost(),getByName("127.0.0.1"),getByName("192.168.0.255"),getByAddress(new byte[]{127,0,0,1})
  34. InetAddress address = InetAddress.getByName(tag ? HOST2 : HOST1);
  35. Client.SINGLETON.sendMsg(editText.getText().toString(), address, tag ? PORT2 : PORT1);
  36. } catch (UnknownHostException e) {
  37. e.printStackTrace();
  38. }
  39. break;
  40. case 3:
  41. Client.SINGLETON.exit();
  42. break;
  43. default:
  44. break;
  45. }
  46. }
  47. private SpannableString getSpannableString(String string, int color) {
  48. SpannableString mSpannableString = new SpannableString(string);
  49. ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
  50. mSpannableString.setSpan(colorSpan, 0, mSpannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  51. return mSpannableString;
  52. }
  53. @Override
  54. public void onReveiveMsg(String message) {
  55. runOnUiThread(() -> msgTextView.append(getSpannableString(message + "\n", Color.BLUE)));
  56. }
  57. @Override
  58. public void onSendMsg(String message) {
  59. runOnUiThread(() -> {
  60. String text = Client.SINGLETON.getName() + " : " + editText.getText().toString() + "\n";
  61. msgTextView.append(getSpannableString(text, Color.RED));
  62. });
  63. }
  64. }

Client

  1. public enum Client {
  2. SINGLETON;
  3. Client() {
  4. }
  5. public static final int MAX_TEXT_SIZE = 1024;
  6. public static final String CHARSET = "GBK";
  7. private boolean exit = false;
  8. private DatagramSocket send;
  9. private DatagramSocket receiver;
  10. private MsgListener listener;
  11. public void startClient(int port) {
  12. if (send != null && !send.isClosed()) {
  13. System.out.println("客户端" + port + "已开启,不需要重复开启");
  14. } else {
  15. new Thread(() -> {
  16. try {
  17. send = new DatagramSocket();
  18. receiver = new DatagramSocket(port);
  19. listenerUserMsg(); //监听消息
  20. System.out.println("客户端" + port + "已开启成功");
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. System.out.println("客户端" + port + "已开启失败");
  24. }
  25. }).start();
  26. }
  27. }
  28. private void listenerUserMsg() {
  29. new Thread(() -> {
  30. try {
  31. byte[] bytes = new byte[MAX_TEXT_SIZE];
  32. DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
  33. while (!exit && receiver != null && !receiver.isClosed()) {
  34. receiver.receive(packet);//持续接收数据
  35. String text = new String(packet.getData(), 0, packet.getLength(), CHARSET);
  36. System.out.println(getName() + " 收到信息【" + text + "】");
  37. if (listener != null) {
  38. listener.onReveiveMsg(text);
  39. }
  40. }
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. }
  44. }).start();
  45. }
  46. public void sendMsg(String text, InetAddress address, int port) {
  47. new Thread(() -> {
  48. if (send != null && !send.isClosed()) {
  49. try {
  50. String content = getName() + " : " + text;
  51. byte[] buf = content.getBytes(CHARSET);
  52. DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
  53. send.send(packet);
  54. if (listener != null) {
  55. listener.onSendMsg(text);
  56. }
  57. } catch (IOException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }).start();
  62. }
  63. public void exit() {
  64. new Thread(() -> {
  65. exit = true;
  66. if (send != null && !send.isClosed()) {
  67. send.close();
  68. }
  69. if (receiver != null && !receiver.isClosed()) {
  70. receiver.close();
  71. }
  72. System.out.println("客户端下线成功");
  73. }).start();
  74. }
  75. public String getName() {
  76. return "用户" + receiver.getLocalPort();
  77. }
  78. public void setListener(MsgListener listener) {
  79. this.listener = listener;
  80. }
  81. public interface MsgListener {
  82. void onReveiveMsg(String message);
  83. void onSendMsg(String message);
  84. }
  85. }

常见异常

  • 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。

DatagramSocket

构造方法

  • 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以上的版本才能运行。

服务端

  1. import java.io.IOException;
  2. import java.util.concurrent.CopyOnWriteArraySet;
  3. import javax.websocket.*;
  4. import javax.websocket.server.ServerEndpoint;
  5. /**
  6. * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
  7. * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
  8. */
  9. @ServerEndpoint("/websocket")
  10. public class WebSocketTest {
  11. private static int onlineCount = 0; //当前在线连接数
  12. //存放每个客户端对应的WebSocket对象
  13. private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
  14. private Session session;//与某个客户端的连接会话,需要通过它来给客户端发送数据
  15. /**
  16. * 连接建立成功调用的方法
  17. * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
  18. */
  19. @OnOpen
  20. public void onOpen(Session session){
  21. this.session = session;
  22. webSocketSet.add(this); //加入set中
  23. onlineCount++; //在线数加1
  24. System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
  25. }
  26. /**
  27. * 连接关闭调用的方法
  28. */
  29. @OnClose
  30. public void onClose(){
  31. webSocketSet.remove(this); //从set中删除
  32. onlineCount--; //在线数减1
  33. System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
  34. }
  35. /**
  36. * 收到客户端消息后调用的方法
  37. * @param message 客户端发送过来的消息
  38. * @param session 可选的参数
  39. */
  40. @OnMessage
  41. public void onMessage(String message, Session session) {
  42. System.out.println("来自客户端的消息:" + message);
  43. //群发消息
  44. for(WebSocketTest item: webSocketSet){
  45. try {
  46. item.sendMessage(message);
  47. } catch (IOException e) {
  48. e.printStackTrace();
  49. continue;
  50. }
  51. }
  52. }
  53. /**
  54. * 发生错误时调用
  55. * @param session
  56. * @param error
  57. */
  58. @OnError
  59. public void onError(Session session, Throwable error){
  60. System.out.println("发生错误");
  61. error.printStackTrace();
  62. }
  63. /**
  64. * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
  65. * @param message
  66. * @throws IOException
  67. */
  68. public void sendMessage(String message) throws IOException{
  69. this.session.getBasicRemote().sendText(message);
  70. //this.session.getAsyncRemote().sendText(message);
  71. }
  72. public static synchronized int getOnlineCount() {
  73. return onlineCount;
  74. }
  75. public static synchronized void subOnlineCount() {
  76. WebSocketTest.onlineCount--;
  77. }
  78. }

客户端

  1. public enum WebClient {
  2. SINGLETON;
  3. private static final String CHARSET = "UTF-8";
  4. private WebSocketClient client = null;
  5. private MsgListener listener;
  6. public void startClient(URI serverUri) {
  7. if (client != null && !client.isClosed()) {
  8. System.out.println("客户端" + getName() + "已开启,不需要重复开启");
  9. } else {
  10. new Thread(() -> {
  11. client = new WebSocketClient(serverUri) {
  12. @Override
  13. public void onOpen(ServerHandshake handshakedata) {
  14. System.out.println("onOpen,握手");
  15. Iterator<String> it = handshakedata.iterateHttpFields();
  16. while (it.hasNext()) {
  17. String key = it.next();
  18. System.out.println(key + ":" + handshakedata.getFieldValue(key));
  19. }
  20. }
  21. @Override
  22. public void onMessage(String message) {
  23. System.out.println("onMessage,接收到消息【" + message + "】");
  24. if (listener != null) {
  25. listener.onReveiveMsg(message);
  26. }
  27. }
  28. @Override
  29. public void onClose(int code, String reason, boolean remote) {
  30. System.out.println("onClose,关闭:code=" + code + ",reason=" + reason + ",remote=" + remote);
  31. }
  32. @Override
  33. public void onError(Exception ex) {
  34. ex.printStackTrace();
  35. }
  36. };
  37. //client.connect(); //非阻塞方法
  38. try {
  39. boolean success = client.connectBlocking();//阻塞方法
  40. System.out.println("客户端" + getName() + "连接" + (success ? "成功" : "失败"));
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. System.out.println("客户端" + getName() + "连接异常");
  44. }
  45. }).start();
  46. }
  47. }
  48. public void sendMsg(String text) {
  49. new Thread(() -> {
  50. if (client != null && !client.isClosed()) {
  51. try {
  52. String content = getName() + " : " + text;
  53. client.send(content.getBytes(CHARSET));
  54. if (listener != null) {
  55. listener.onSendMsg(text);
  56. }
  57. } catch (IOException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }).start();
  62. }
  63. public void exit() {
  64. new Thread(() -> {
  65. if (client != null && !client.isClosed()) {
  66. client.close();
  67. }
  68. System.out.println("客户端下线成功");
  69. }).start();
  70. }
  71. public String getName() {
  72. return "用户" + client.getLocalSocketAddress().getPort();
  73. }
  74. public void setListener(MsgListener listener) {
  75. this.listener = listener;
  76. }
  77. public interface MsgListener {
  78. void onReveiveMsg(String message);
  79. void onSendMsg(String message);
  80. }
  81. }

2018-11-23

TCP UDP Socket 即时通讯 API 示例 MD的更多相关文章

  1. QQ--基于TCP/UDP协议的通讯原理

    QQ是一个基于TCP/UDP协议的通讯软件  发送消息的时候是UDP打洞,登陆的时候使用HTTP~因为登陆服务器其实就是一个HTTP服 务器,只不过不是常用的那些,那个服务器是腾讯自行开发的!   一 ...

  2. TCP&UDP&Socket讲解(上)

    这两天我将整理TCP&UDP&Socket,大约花大家10-15分钟之间,希望本篇文章让大家对TCP使用的理解提高一个层次. 建议大家拿出纸和笔,画一下!!! 一.TCP 1. TCP ...

  3. TCP UDP socket http webSocket 之间的关系

    ---恢复内容开始--- OSI&TCP/IP模型 要弄清tcp udp socket http websocket之间的关系,首先要知道经典的OSI七层模型,与之对应的是TCP/IP的四层模 ...

  4. TCP/UDP,SOCKET,HTTP,FTP 简析

    (一)TCP/UDP,SOCKET,HTTP,FTP简析 TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层: 网络层:IP协议.ICMP协议.ARP协议.RARP协议和BOOTP协议 传 ...

  5. Bash Shell 下打开一个TCP / UDP SOCKET

    Bash Shell 下打开一个TCP / UDP SOCKET http://jingyan.baidu.com/article/636f38bb6166c3d6b84610d1.html

  6. HTTP TCP UDP Socket 关系的几个经典图

      从上图可以看到,TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层. 在网络层有IP协议.ICMP协议.ARP协议.RARP协议和BOOTP协议. 在传输层中有TCP协议与UDP协议. ...

  7. TCP/UDP socket

    TCP socket:有链接,绑定端口,接着去侦听,若有请求,那么accept(),获得新的socket,并且去接收/发送数据报. UDP socket:无连接,不需要侦听,也不用一个新的socket ...

  8. How To: Perl TCP / UDP Socket Programming using IO::Socket::INET

    http://www.thegeekstuff.com/2010/07/perl-tcp-udp-socket-programming/ In this article, let us discuss ...

  9. TCP/UDP Socket调试工具提供了TCP Server,TCP Client,UDP Server,UDP Client,UDP Group 五种Socket调试方案。

    一.TCP通信测试: 1)   创建TCP Server: 选中左方的TCP Server, 然后点击”创建”按钮,软件弹出监听端口输入框 输入监听端口后,即创建了一个在指定端口上进行监听的TCP S ...

随机推荐

  1. java 获取当前方法名

    String _thisMethodName = new Exception().getStackTrace()[0].getMethodName();// 获得当前的方法名

  2. [HDU5713]K个联通块

    [HDU5713]K个联通块 题目大意: 有一张\(n(n\le14)\)个点,\(m\)条边无重边的无向图,求有多少个边集,使得删掉边集里的边后,图里恰好有\(k\)个连通块. 思路: 一个显然的动 ...

  3. Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem H. Parallel Worlds 计算几何

    Problem H. Parallel Worlds 题目连接: http://opentrains.snarknews.info/~ejudge/team.cgi?SID=c75360ed7f2c7 ...

  4. hdu 5784 How Many Triangles 计算几何,平面有多少个锐角三角形

    How Many Triangles 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=5784 Description Alice has n poin ...

  5. 使用Apache commons-codec Base64实现加解密(转)

    commons-codec是Apache下面的一个加解密开发包 官方地址为:http://commons.apache.org/codec/ 官方下载地址:http://commons.apache. ...

  6. .net下的span和memory

    .net core 2.1的重头戏就是性能,其中最重要的两个类就是span和memory,本文这里简单的介绍一下这两个类的使用. 什么是 Span<T> Span<T> 是新一 ...

  7. JS实现各种复制到剪贴板

    一.实现点击按钮,复制文本框中的的内容                         <script type="text/javascript"> function ...

  8. D3D9 effect (hlsl)(转)

      转:http://blog.csdn.net/leonwei/article/details/8212800 effect其实整合了shader和render state的控制两大部分内容 9.1 ...

  9. TCPIP网络协议层对应的RFC文档

    原文地址:TCPIP网络协议层对应的RFC文档作者:西木 RFC - Request For Comments 请求注解 TCP/IP层 网络协议 RFC文档 Physical Layer Data ...

  10. java入门学习(十四)运算语句for

    循环可用来重复执行一条语句或者含有多条语句的语句块.在大多数程序中都会需要重复执行一块语句. for 循环的基本语法是: for (表达式1:表达式2:表达式3) { 若干语句 } for语句由关键字 ...