基础 所谓 socket 通常也称作”套接字”,用于描述 IP 地址和端口,是一个通信链的句柄。网络上具有唯一标识的 IP 地址和端口组合在一起才能构成唯一能识别的标识符套接字,应用程序通常通过”套接字”向网络发出请求或者应答网络请求。  
 
几个常见类 
InetAddress 用于标识网络上的硬件资源,主要表示 IP 地址。   
SocketAddress 表示 Sokcet 地址,不包含使用的具体协议,是一个抽象类。   
InetSocketAddress 继承了 SocketAddress,包含主机名,IP 地址和端口(hostname, InetAddress, port)。   
Socket 客户端 Socket,默认采用的传输层协议为 TCP。 
ServerSocket 服务器 Socket,默认采用的传输层协议为 TCP。等待客户端请求,并基于这些请求执行操作返回一个结果。   
SocketImpl 通用抽象类,用来创建客户端和服务端具体的 Socket。Socket 通信流程的所有方法都是通过该类和子类实现的。   
DatagramPacket 表示存放数据的数据报。  
DatagramSocket 实现了一个发送和接收数据报的 socket,传输层协议使用 UDP。客户端和服务端都使用该套接字来实现通信。   
 
传输协议 
TCP:Tranfer Control Protocol 是一种面向连接的保证可靠传输的协议,通过 TCP 协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个 socket 之间必须建立连接,以便在 TCP 协议的基础上进行通信,当一个 server socket 等待建立连接 accept 时,客户端 socket 可以要求进行连接。一旦连接起来后,它们就可以进行双向数据传输,双方都可以进行发送或接收数据。 
UDP:User Datagram Protocol 是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。传输数据时数据报大小必须限定在 64KB 之内。  
 
端口 区分一台主机的多个不同应用程序,端口号范围为 0-65535,其中 0-1023 位为系统保留。如:HTTP:80  FTP:21 Telnet:23。  
五元组 通信术语,通常是指:源 IP 地址,源端口,目的 IP 地址,目的端口和传输层协议,两台计算机通信必须要明确指定五元组的值。  
地址和端口Socket 中包含了源和目标的 IP 地址,端口。   
传输协议类型Socket 表示 TCP 协议;DatagramSocket 表示 UDP 协议。   
 
输入输出流读写 Soket 数据连接是全双工的,一旦建立连接可以得到 Socket 的输入流和输出流,主机可以使用这两个流同时发送和接受数据。流是同步的 ,也就是说当请求流读/写一段数据时,阻塞等待直到有数据。Java 还支持使用通道和缓冲区的非阻塞 I/O(NIO),暂不讨论。   
socket.getInputStream() 获取输入流,通过输入流读取 数据。   
socket.getOutputStream() 获取输出流,通过输出流写入 数据。   
 
??关闭一个流也会也会关闭一个 Sokcet 连接。??对于同一个 Socket,如果关闭了输出流,则与该输出流关联的 Socket 也会被关闭,所以一般不用关闭流,直接关闭 Socket 即可。  
Socket 原理机制
通信的两端都有 Socket   
网络通信其实就是 Socket 间的通信   
数据在两个 Socket 间通过 IO 传输   
 
四种常见异常 以下四种类型异常都是继承于 IOException,所以很多之后直接弹出 IOException 即可。  
UnkownHostException:主机名字或 IP 错误   
ConnectException:服务器拒绝连接、服务器没有启动、超出队列数,拒绝连接等   
SocketTimeoutException:连接超时   
BindException:Socket 对象无法与指定的本地 IP 地址或端口绑定   
 
Socket构造方法 1 2 3 4 5 6 Socket(String host, int  port)throws  UnknownHostException, IOException Socket(InetAddress address, int  port) throws  IOException Socket(String host, int  port, InetAddress localAddr, int  localPort) throws  IOException Socket(InetAddress address, int  port, InetAddress localAddr, int  localPort) throws  IOException ... private  Socket (SocketAddress address, SocketAddress localAddr, boolean  stream)  throws  IOException  
 
Socket 有多个构造方法,但是最终调用的是最后一个。至少需要指定目的主机名 host 或者 IP 地址,以及端口号 port。 参数解析: 
address 远程即服务端,包含目标 IP 地址,主机名,端口。不能为空,否则会抛出异常。   
localAddr 本地即客户端,包含源 IP 地址,主机名,端口。如果为空,表示系统自动分配。   
stream 为 true 表示使用流式连接即 TCP 方式;为 false 表示使用数据报即 UDP 方式。默认为 true。   
 
关闭 在 try - finally 块中采用 close-if-not-null 来关闭 Socket 连接。  
Socket 选项
TCP_NODELAY 设置为 true 可以保证无论包的大小都会尽快发送,不用缓冲到足够大的数据包。   
SO_LINGER 指定 Socket 关闭时,还没有发送的数据包如何处理。默认情况下 close 方法会立即返回,但系统会尝试发送剩余的数据。如果延迟时间设置为 0,所有未发送数据都会被丢弃。如果设置了事件,close 会阻塞到指定时间,等待数据接收和确认,如果超出时间,剩余数据将会被丢弃。   
SO_TIMEOUTSocket 在读取数据时,read() 会阻塞尽可能长的时间。如果设置后,阻塞时间不会超过指定时间,否则会抛出异常,但是 Socket 仍然是处于连接状态,下次可以继续读。   
SO_RCVBUFTCP 使用缓冲区提升性能,参数为设置接受缓冲区大小。   
SO_SNDBUF 设置发送缓冲区大小。   
SO_KEEPALIVE 默认值为 false,如果打开,在 Sokcet 没有数据传输时,每两个小时客户端会发送一个数据包确保服务器是正常的。   
SO_OOBINLINE 参数被设置时,TCP 会发送一个紧急数据包,接受方收到后会优先处理。   
SO_REUSEADDR 设置 Socket 是否可以重复使用端口,默认是也可以的。   
IP_TOS 设置数据拥堵时的处理策略。   
 
常见 API 
socket.getRemoteSocketAddress() 获取目的 IP 地址,主机名,端口。   
socket.getInetAddress() 获取目的 IP 地址,主机名。   
socket.getPort() 获取目的端口。   
socket.getLocalSocketAddress() 获取源 IP 地址,主机名,端口。   
socket.getLocalAddress() 获取源 IP 地址,主机名。   
socket.getLocalPort() 获取源端口。   
 
SocketServer 构造方法 1 2 3 4 public  ServerSocket ()  throws  IOException   public  ServerSocket (int  port)  throws  IOException  public  ServerSocket (int  port, int  backlog)  throws  IOException  public  ServerSocket (int  port, int  backlog, InetAddress bindAddr)  throws  IOException  
 
参数解析:  
port 服务器端口号,0 表示自动分配的端口。   
backlog 请求链接队列的最大长度。 
bindAddr 服务器将绑定的本地地址 InetAddress   
 
关闭 在 try - finally 块中采用 close-if-not-null 来关闭 SocketServer 连接。  
Socket 选项
SO_TIMEOUT 设置 Socket 阻塞的时间,如:accept, read, receive。   
SO_REUSEADDR 设置 Socket 是否可以重复使用端口,默认是也可以的。   
SO_RCVBUF 设置 Socket 接受数据缓冲区大小。   
 
日志 log 服务器需要形成记录日志的习惯,来记录客户端访问信息,出现的错误等等。  
多线程设计 通常服务端会有多个客户端访问,所以可以设置一个线程池来处理每个客户端的请求,避免阻塞。  
常见 API 
serverSocket.getLocalSocketAddress() 获取本地 IP 地址,主机名,端口。典型值为 0.0.0.0/0.0.0.0:***。   
serverSocket.getInetAddress() 获取本地 IP 地址,典型值为 0.0.0.0/0.0.0.0,即自动分配的本地地址。   
serverSocket.getLocalPort 获取本地端口,即 Socket 通信服务端中的目的端口。   
 
客户端和服务端的 TCP 通信步骤 通信流程图 
大概流程如下:  
服务端 SocketServer 初始化   
服务端等待并接受客户端的连接 accept   
客户端 Socket 初始化   
数据通信   
关闭输入输出流,再关闭 Socket/SocketServer   
 
服务端 SocketServer 初始化 
创建 SocketSocketImpl.java: protected abstract void create(boolean stream) throws IOException;,创建服务端 Socket。   
绑定 bindSocketImpl.java: protected abstract void bind(InetAddress host, int port) throws IOException;,显示指定或者系统自动分配本地 IP 地址及空闲端口,绑定后形成目的  IP 地址和端口。   
监听 listenSocketImpl.java: protected abstract void listen(int backlog) throws IOException;,设置请求连接队列的最大值。   
 
这三步都在 ServerSocket 的构造方法中实现,不用显示调用。  
服务端等待接受连接 accept 1 2 SocketServer.java: public  Socket accept ()  throws  IOException ; SocketImpl.java: protected  abstract  void  accept (SocketImpl s)  throws  IOException ; 
 
阻塞等待,监听和接受客户端的 Socket 连接,并返回服务端的 Socket,最终会调用 SocketImpl 的方法接受连接。  
客户端 Socket 初始化 
创建 SocketSocketImpl.java: protected abstract void create(boolean stream) throws IOException;,创建客户端 Socket。   
绑定 bindSocketImpl.java: protected abstract void bind(InetAddress host, int port) throws IOException;,显示指定或者系统自动分配本地 IP 地址及空闲端口,绑定后形成源  IP 地址和端口。   
连接 connect  1 2 3 4 5 Socket.java: public  void  connect (SocketAddress endpoint)  throws  IOException Socket.java: public  void  connect (SocketAddress endpoint, int  timeout)  throws  IOException SocketImpl.java: protected  abstract  void  connect (String host, int  port)  throws  IOException ;SocketImpl.java: protected  abstract  void  connect (InetAddress address, int  port)  throws  IOException ; SocketImpl.java: protected  abstract  void  connect (SocketAddress address, int  timeout)  throws  IOException ; 
 
SocketAddress 包含目标 IP 地址和端口,在指定时间内连接服务端的 Socket。如果 timeout 为 0,表示不限时。   
 
这三步都在 Socket 的构造方法中实现,不用显示调用。也就是说客户端在 TCP 协议的 Socket 编程中,非常简单,只需要指定目的 SocketAddress ,就可以直接获取输入输出流来读写数据。  
数据通信 
服务端建立连接后 通过输入流读取客户端发送的请求信息;通过输出流向客户端发送响应信息。   
客户端建立连接后 通过输出流向服务器端发送请求信息;通过输入流读取服务端响应的信息。 
 
关闭输入输出流,再关闭 Socket ServerSocket 和 Socket 没有先后关闭的必然顺序。ServerSocket 先关闭,并不会影响当前已经连接的 Sokcet 
TCP 通信示例流程图 
服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 public  class  TestTCPSocketServer   {    private  static  final  String QUIT = "quit" ;     private  static  final  String EXIT = "exit" ;     private  volatile  boolean  isQuited = false ;     private  ServerSocket ss = null ;     private  Socket socket = null ;     private  Thread listenQuit;     private  ListenRunnable listenRunnable;     private  ExecutorService threadPool;     private  class  ListenRunnable  implements  Runnable  {         private  ServerSocket ss;         public  ListenRunnable (ServerSocket ss)  {             this .ss = ss;         }         @Override          public  void  run ()   {             try  {                                  BufferedReader ssRead = new  BufferedReader(                         new  InputStreamReader(System.in));                 System.out.println(Thread.currentThread().getName()                         + " listen server input..." );                 String ssInput = ssRead.readLine();                 System.out.println(ssInput);                 if  (willQuit(ssInput)) {                     ssRead.close();                     isQuited = true ;                 }             } catch  (IOException e){                 System.err.println(e);             } finally  {                 if  (isQuited) {                                          if  (threadPool != null  && !threadPool.isShutdown()) {                         threadPool.shutdown();                     }                     closeServerSocket(ss);                 }             }         }     }     private  class  ClientCallable  implements  Callable <Void > {         private  Socket socket;         public  ClientCallable (Socket socket)  {             this .socket = socket;         }         @Override          public  Void call ()   {             try  {                 String origHost = socket.getInetAddress().toString();                 BufferedReader in = new  BufferedReader(                         new  InputStreamReader(socket.getInputStream()));                                                   String line = in.readLine();                 while  (line != null  && !willQuit(line)) {                     System.out.println(origHost + ", you input is : "  + line);                     line = in.readLine();                 }                                                   in.close();             } catch  (IOException e){                 System.err.println(e);             } finally  {                 closeSocket(socket);             }             return  null ;         }     }     private  void  closeSocket (Socket socket)  {         if  (socket == null ) return ;         try  {             System.out.println(socket.getInetAddress().toString() + " closed." );             socket.close();         } catch  (IOException e){             System.err.println(e);         }     }     private  void  closeServerSocket (ServerSocket ss)  {         if  (ss == null ) return ;         try  {             System.out.println("Server closed" );             ss.close();         } catch  (IOException e){             System.err.println(e);         }     }          private  boolean  willQuit (String str)  {         return  str != null  && (str.equalsIgnoreCase(QUIT)                 || str.equalsIgnoreCase(EXIT));     }     private  void  startServerDaemon (ServerSocket ss)  {         listenRunnable = new  ListenRunnable(ss);         listenQuit = new  Thread(listenRunnable);         listenQuit.setDaemon(true );         listenQuit.start();     }     private  void  run ()  {         try  {                          ss = new  ServerSocket(10000 );                          startServerDaemon(ss);             System.out.println("serverSocket.getInetAddress: "                      + ss.getInetAddress());             System.out.println("serverSocket.getLocalSocketAddress: "                      + ss.getLocalSocketAddress());             System.out.println("serverSocket.getLocalPort: "  + ss.getLocalPort());             System.out.println("The server is waiting you connect and input..." );             while  (!isQuited) {                                  socket = ss.accept();                 System.out.println("Socket: "  + socket + " connected." );                                  Callable<Void> client = new  ClientCallable(socket);                 threadPool.submit(client);                 System.out.println("ReAccept..." );             }         } catch  (IOException e) {             e.printStackTrace();         } finally  {             closeSocket(socket);             closeServerSocket(ss);         }     }     public  TestTCPSocketServer ()   {                  threadPool = Executors.newFixedThreadPool(20 );     }     public  static  void  main (String[] args)   {         TestTCPSocketServer server = new  TestTCPSocketServer();         server.run();     } } 
 
大致思路:  
初始化线程池,用于接受并执行客户端的请求任务   
创建 ServerSocket 对象,绑定监听端口   
开启后台线程,监听终端输入 exit/quit   
循环调用 accept() 方法,监听客户端请求   
接收到请求后,为每个客户端创建 Socket 专线连接   
线程池为每个客户端提供一个单独线程用于 Socket 通信   
服务器端继续等待新的客户端连接   
独立线程中,通过输入流读取客户端发送的请求信息,通过输出流向客户端发送响应信息   
客户端断开连接后,关闭相关资源,并关闭该 Socket,释放当前线程   
服务端接收到终端输入退出指令后,关闭线程池并关闭 ServerSocket   
 
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public  class  TestTCPSocketClient   {    private  static  final  String QUIT = "quit" ;     private  static  final  String EXIT = "exit" ;     Socket socket = null ;     public  TestTCPSocketClient ()   {         try  {             socket = new  Socket("127.0.0.1" , 10000 );                          System.out.println("Socket: "  + socket);             System.out.println("socket.getRemoteSocketAddress: "                      + socket.getRemoteSocketAddress());             System.out.println("socket.getInetAddress: "                      + socket.getInetAddress());             System.out.println("socket.getPort: "  + socket.getPort());             System.out.println("socket.getLocalSocketAddress: "                      + socket.getLocalSocketAddress());             System.out.println("socket.getLocalAddress: "                      + socket.getLocalAddress());             System.out.println("socket.getLocalPort: "                      + socket.getLocalPort());                                       PrintWriter out = new  PrintWriter(socket.getOutputStream()                     , true );             BufferedReader line = new  BufferedReader(                     new  InputStreamReader(System.in));             System.out.println("read line! block..." );             String read = line.readLine();             while  (!willQuit(read)) {                 out.println(read);                 read = line.readLine();             }             System.out.println("read over!" );             line.close();             out.close();                      } catch  (IOException e) {             e.printStackTrace();         } finally  {             if  (socket != null ) {                 try  {                     socket.close();                 } catch  (IOException e){                     System.err.println(e);                 }             }         }     }     private  boolean  willQuit (String str)  {         return  str != null  && (str.equalsIgnoreCase(QUIT)                 || str.equalsIgnoreCase(EXIT));     }     public  static  void  main (String[] args)   {         new  TestTCPSocketClient();     } } 
 
大致思路:  
创建 Socket 对象,指明需要连接的服务器的地址和端口号   
连接建立后,从终端读取输入信息   
通过输出流向服务器端发送读取到的信息   
通过输入流获取服务器响应的信息   
关闭相关资源,关闭 Socket   
 
DatagramPacket数据报(Datagram):将数据填充到 UDP 包中。数据报存放到 DatagramPacket 中,而 DatagramSocket 用来收发数据报。数据报的所有信息都包含在这个包中(包括发往的目标地址),Socket 只需要了解端口监听和发送。UDP 这种模式并没有像 TCP 那样两台主机有唯一连接的概念,一个 Socket 会收发所有指定端口的数据,而不会关心对方是哪个主机。 DatagramSocket 可以从多个独立主机收发数据,与 TCP 不同,这个 Socket 并不会专用于某个连接。TCP 中 Socket 把网络连接看作是一个流,通过输入输出流来收发数据。但是 UDP 处理的总是单个数据报包,填充在数据报包中的所有数据会以一个包的形式发送,这些数据要么全部接受,要么全部丢弃。一个包与另一个包并不一定相关,而且无法确认先后顺序。 对于流必须提供数据的有序队列,而数据报会尽可能快的发送到接收方。
UDP 服务端通常不需要使用多线程,不会阻塞等待客户端的响应,除非为了应对大量耗时工作才会使用多线程。
UDP 数据报是基于 IP 数据报建立的,只向其底层 IP 数据添加了很少的内容(8 个字节的首部信息)。UDP 包中数据的理论长度为 65507 个字节,但实际上很多平台限制往往是 8KB (8192 字节),大多数情况下更大的包会被简单的截取为 8KB。并且为了保证安全性,UDP 包的数据部分应该尽量少于 512 字节。
构造方法 1 2 3 4 5 6 7 8 9 10 public  DatagramPacket (byte  buf[], int  offset, int  length)   {...}public  DatagramPacket (byte  buf[], int  length)   {...}public  DatagramPacket (byte  buf[], int  offset, int  length,                           InetAddress address, int  port)   {...}public  DatagramPacket (byte  buf[], int  offset, int  length, SocketAddress address)   {...}public  DatagramPacket (byte  buf[], int  length,                           InetAddress address, int  port)   {...}public  DatagramPacket (byte  buf[], int  length, SocketAddress address)   {...}
 
注意:DatagramPacket 虽然都是构造方法,但是有 2 个是接收数据报的构造方法,剩余 4 个是发送数据报的构造方法。普通情况下构造方法主要用于不同对象提供不同的类型信息,而不是像数据报这样提供不同的功能对象。
 
参数解析:  
buf[] 保存接收或发送数据的数组,也就是不管是发送还是接受拿到的都是字节数组。   
length 数组中用于接受或发送数据的长度。   
offset 数组的偏移量,默认为 0。  
InetAddress 发送数据时接收方的 IP 地址。   
port 发送数据时接收方的端口。   
SocketAddress 发送数据时接收方的地址和端口   
 
get 方法get 方法可以获取构造方法中传进来的所有参数信息。  
1 2 3 4 5 6 7 8 9 10 public  synchronized  InetAddress getAddress ()   {...}public  synchronized  int  getPort ()  {...}public  synchronized  SocketAddress getSocketAddress ()   {...}public  synchronized  byte [] getData() {...}public  synchronized  int  getOffset ()   {...}public  synchronized  int  getLength ()   {...}
 
set 方法set 方法可以在构造方法创建数据报后,改变所有的数据报信息,相当于重新创建了一个数据报。因为 DatagramPacket 对象的重复创建和垃圾回收影响性能,所以重用对象比构造对象快的多。  
1 2 3 4 5 6 7 8 public  synchronized  void  setAddress (InetAddress iaddr)   {...}public  synchronized  void  setPort (int  iport)   {...}public  synchronized  void  setSocketAddress (SocketAddress address)   {...}public  synchronized  void  setData (byte [] buf)   {...}public  synchronized  void  setData (byte [] buf, int  offset, int  length)   {...}public  synchronized  void  setLength (int  length)   {...}
 
注意:所有的 set 方法都加了同步锁。 
 
DatagramSocket要收发数据报,需要先打开一个 DatagramSocket,而所有的 DatagramSocket 必须绑定一个本地端口,这个端口用来监听收发的数据,并写入数据报的首部。DatagramSocket 只存储本地地址和端口,所有的远程地址和端口都在 DatagramPacket 中,所以 DatagramSocket 可以同时和多台主机收发数据(只要 DatagramPacket 不同就可以了)。  
构造方法 1 2 3 4 5 6 public  class  DatagramSocket  implements  java .io .Closeable   {    public  DatagramSocket ()  throws  SocketException  {...}     public  DatagramSocket (int  port)  throws  SocketException  {...}     public  DatagramSocket (int  port, InetAddress laddr)  throws  SocketException  {...}     public  DatagramSocket (SocketAddress bindaddr)  throws  SocketException  {...} } 
 
收发数据报 1 2 public  void  send (DatagramPacket p)  throws  IOException   {...}public  synchronized  void  receive (DatagramPacket p)  throws  IOException  {...}
 
send 用来发送数据报;receive 加了同步锁,并且会阻塞 当前线程,直到有数据到达,接收一个数据报。  
常见 API 1 2 3 4 5 6 7 8 9 10 11 public  void  close ()   {...}public  SocketAddress getLocalSocketAddress ()   {...}public  InetAddress getLocalAddress ()   {...}public  int  getLocalPort ()   {...}public  void  connect (SocketAddress addr)  throws  SocketException  {...}public  void  connect (InetAddress address, int  port)   {...}public  void  disconnect ()   {...}
 
Socket 选项
SO_TIMEOUTSocket 在接收数据时,receive() 会阻塞尽可能长的时间。如果设置后,阻塞时间不会超过指定时间,否则会抛出异常。设置为 0 表示永不超时。   
SO_RCVBUFUDP 设置网络接收缓冲区大小,与 TCP 对比,UDP 应该设置足够大的缓冲区。   
SO_SNDBUF 建议发送缓冲区大小,但是操作系统可以忽略这个建议。   
SO_REUSEADDR 和 TCP Socket 的意义不同,设置后表示是否允许多个数据报同时绑定到相同的端口和地址。重用端口通常会用于 UDP 组播。 
SO_BROADCAST 控制是否允许一个 Socket 向广播地址收发包,默认是打开的。UDP 广播通常用于 DHCP 协议,路由器和网关一般不转发广播消息,但仍然会在本地网络中带来大量业务流。   
IP_TOS 设置数据拥堵时的处理策略。   
 
UDP 通信示例流程图 
服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public  class  TestDayTimeUDPServer   {    private  static  final  int  PORT = 13 ;     private  static  final  int  CAPACITY = 1024 ;     private  static  final  Logger audit = Logger.getLogger("requests" );     private  static  final  Logger errors = Logger.getLogger("errors" );     public  static  void  main (String[] args)   {         DatagramSocket datagramSocket = null ;         try  {             datagramSocket = new  DatagramSocket(PORT);             System.out.println("Source: "  + datagramSocket.getLocalSocketAddress());             while  (true ){                 try  {                     DatagramPacket request = new  DatagramPacket(                             new  byte [CAPACITY], CAPACITY);                     datagramSocket.receive(request);                     String dayTime = new  Date().toString();                     byte [] data = dayTime.getBytes("US-ASCII" );                     DatagramPacket response = new  DatagramPacket(data, data.length,                             request.getAddress(), request.getPort());                     datagramSocket.send(response);                     System.out.println("Destination: "  + request.getSocketAddress());                     audit.info(dayTime + " "  + request.getAddress());                 } catch  (IOException e){                     errors.log(Level.SEVERE, e.getMessage(), e);                 }             }         } catch  (IOException e){             errors.log(Level.SEVERE, e.getMessage(), e);         }     } } 
 
大致思路:  
创建 DatagramSocket,绑定端口号   
创建 DatagramPacket,指定目标地址和端口   
阻塞等待接收客户端发送的数据   
向客户端发送响应数据   
 
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public  class  TestUDPGetDayTime   {              private  static  final  String HOSTNAME = "127.0.0.1" ;     private  static  final  int  PORT = 38910 ;     private  static  final  int  DELAY_TIME = 10000 ;     private  static  final  int  CAPACITY = 1024 ;     public  static  void  main (String[] args)   {         DatagramSocket datagramSocket = null ;         try  {             datagramSocket = new  DatagramSocket();             datagramSocket.setSoTimeout(DELAY_TIME);             InetAddress host = InetAddress.getByName(HOSTNAME);             DatagramPacket request = new  DatagramPacket(new  byte [1 ], 1 , host, PORT);             DatagramPacket response = new  DatagramPacket(new  byte [CAPACITY], CAPACITY);             datagramSocket.send(request);             System.out.println("Source: "  + datagramSocket.getLocalSocketAddress());             System.out.println("Destination: "  + request.getSocketAddress());                          datagramSocket.receive(response);             String dayTime = new  String(response.getData(), 0 ,                     response.getLength(), "US-ASCII" );             System.out.println(dayTime);         } catch  (IOException e){             System.err.println(e);         } finally  {             if  (datagramSocket != null ) {                 datagramSocket.close();             }         }     } } 
 
大致思路:  
定义发送信息,并存储到数组中   
创建 DatagramPacket,指定目标地址和端口,数据数组   
创建 DatagramSocket,绑定本地端口   
向服务端请求数据   
从服务端获取响应数据   
 
参考文档 
Java Socket编程    
Socket和ServerSocket学习笔记    
Java网络socket编程详解    
TCP数据段格式+UDP数据段格式详解