7.7 Socket编程

  所谓Socket通常也称作套接字,应用程序通常通过套接字向网络发出请求或者应答网络请求。Java语言中的Socket编程常用到Socket和ServerSocket这两个类,它们位于java.net包中。

7.7.1 基于TCP的Socket编程

  ServerSocket用于服务器端,而Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket,它们的工作都是通过SocketImpl类及其子类完成的。关于Socket、ServerSocket及SocketImpl类的具体方法,这里不再一一介绍,需要这些类时,请大家自行查阅JDK API文档。

  图7.7展示了基于TCP的Socket编程的示意图。

7.7 Socket编程 - 图1


图7.7 基于TCP的Socket编程

  在服务器端,创建一个ServerSocket对象,并指定一个端口号,使用ServerSocket类的accept()方法使服务器处于阻塞状态,等待用户请求。

  在客户端,通过指定一个InetAddress对象和一个端口号,创建一个Socket对象,通过这个Socket对象,连接到服务器。

  首先来看服务器端程序,具体代码如下:

  1. import java.net.*;
  2. import java.io.*;
  3. public class TestServer{
  4. public static void main(String args[]) {
  5. try{
  6. //创建一个ServerSocket对象,并指定端口号[[张彬2\]](#_msocom_2) 8888
  7. ServerSocket s = new ServerSocket(8888);
  8. while(true){
  9. //侦听并接受到此套接字的连接
  10. Socket s1 = s.accept();
  11. OutputStream os = s1.getOutputStream();
  12. DataOutputStream dos = new DataOutputStream(os);
  13. dos.writeUTF("客户端IP:" + s1.getInetAddress().getHostAddress() + "
  14. 客户端端口号:" + s1.getPort());
  15. dos.close();
  16. s1.close();
  17. }
  18. }catch (IOException e) {
  19. e.printStackTrace();
  20. System.out.println("程序运行出错!");
  21. }
  22. }
  23. }

  该服务器端程序的作用就是监听8888端口,当有发送到本机8888端口的Socket请求时,建立输出流,将通过accept()方法创建的Socket对象的IP地址和端口号输出到客户端。编译、运行程序,使服务器启动并处于监听状态。

  下面编写客户端程序,具体代码如下:

  1. import java.net.*;
  2. import java.io.*;
  3. public class TestClient{
  4. public static void main(String args[]){
  5. try{
  6. //通过IP地址和端口号,创建一个Socket对象
  7. Socket s1 = new Socket("127.0.0.1", 8888);
  8. //建立输入数据流
  9. InputStream is = s1.getInputStream();
  10. DataInputStream dis = new DataInputStream(is);
  11. System.out.println(dis.readUTF());
  12. dis.close();
  13. s1.close();
  14. }catch(ConnectException e){
  15. e.printStackTrace();
  16. System.err.println("服务器连接失败!");
  17. }catch(IOException e){
  18. e.printStackTrace();
  19. }
  20. }
  21. }

  该客户端程序通过IP地址127.0.0.1和端口号8888,创建一个客户端Socket对象,建立输入数据流,通过输入数据流读取指定IP地址和端口号上服务器端程序的输出,并在控制台将服务器的输出显示出来。编译、运行程序,运行结果如图7.8所示。

7.7 Socket编程 - 图2


图7.8 使用Java Socket编程

  在这个通过Java Socket编程实现的客户端、服务器端程序中,客户端没有请求的具体内容,只要有请求,服务器就将指定的内容发送给客户端,客户端将接收的内容显示出来。接下来对上面的案例进行调整,服务器端可以接收客户端请求的内容,并显示在服务器端控制台上。服务器端程序具体代码如下:

  1. import java.io.*;
  2. import java.net.*;
  3. public class TestSockServer {
  4. public static void main(String[] args) {
  5. InputStream in = null;
  6. OutputStream out = null;
  7. try{
  8. ServerSocket s = new ServerSocket(8888);
  9. Socket s1 = s.accept();
  10. in = s1.getInputStream();
  11. out = s1.getOutputStream();
  12. DataOutputStream dos = new DataOutputStream(out);
  13. DataInputStream dis = new DataInputStream(in);
  14. String str = null;
  15. if((str = dis.readUTF())!= null) {
  16. System.out.println("客户端输入内容:" + str);
  17. System.out.println("客户端IP:" + s1.getInetAddress().getHostAddress());
  18. System.out.println("客户端端口号:" + s1.getPort());
  19. }
  20. dos.writeUTF("服务器端反馈客户端!");
  21. dis.close();
  22. dos.close();
  23. s1.close();
  24. }catch(IOException e){
  25. e.printStackTrace();
  26. }
  27. }
  28. }

  客户端具体代码如下:

  1. import java.net.*;
  2. import java.io.*;
  3. public class TestSockClient {
  4. public static void main(String[] args) {
  5. InputStream is = null;
  6. OutputStream os = null;
  7. String s = null;
  8. try{
  9. Socket socket = new Socket("localhost",8888);
  10. is = socket.getInputStream();
  11. os = socket.getOutputStream();
  12. DataInputStream dis = new DataInputStream(is);
  13. DataOutputStream dos = new DataOutputStream(os);
  14. //客户端向服务器端发送请求的内容
  15. dos.writeUTF("客户端提交服务器");
  16. if((s = dis.readUTF()) != null)
  17. System.out.println(s);
  18. dos.close();
  19. dis.close();
  20. socket.close();
  21. }catch(UnknownHostException e){
  22. e.printStackTrace();
  23. }catch(IOException e){
  24. e.printStackTrace();
  25. }
  26. }
  27. }

  编译、运行服务器端、客户端程序,运行结果分别如图7.9和图7.10所示。

7.7 Socket编程 - 图3


图7.9 Socket编程服务器端

7.7 Socket编程 - 图4


图7.10 Socket编程客户端

7.7.2 基于UDP的Socket编程

  UDP是用户数据报协议,它提供的是无连接、不可靠信息传送服务。Java 主要提供了两个类来实现基于UDP的Socket编程。

  DatagramSocket:此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点,每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。在 DatagramSocket上总是启用UDP广播发送。

  DatagramPacket:此类表示数据报包。数据报包用来实现无连接包投递服务,每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。

  图7.11展示了基于UDP的Socket编程的示意图。

7.7 Socket编程 - 图5


图7.11 基于UDP的Socket编程

  DatagramPacket类主要有两个构造函数。

  一个用来接收数据DatagramPacket(byte[] recyBuf, int readLength),用一个字节数组接收UDP包,recyBuf数组在传递给构造函数时是空的,而readLength值用来设定要读取的字节数。

  一个用来发送数据DatagramPacket(byte[] sendBuf, int sendLength, InetAddress iaddr, int port),建立将要传输的UDP包,并指定IP地址和端口号。

  接下来通过一个案例来演示Java如何实现基于UDP的Socket编程,其中服务器端代码如下:

  1. import java.net.*;
  2. import java.io.*;
  3. public class TestUDPServer{
  4. public static void main(String args[]) throws Exception
  5. {
  6. //创建数据报包的套接字,端口号8888
  7. DatagramSocket ds = new DatagramSocket(8888);
  8. byte buf[] = new byte[1024];
  9. //创建接收的数据报包
  10. DatagramPacket dp = new DatagramPacket(buf,buf.length);
  11. System.out.println("服务器端:");
  12. while(true)
  13. {
  14. //从此套接字接收数据报包
  15. ds.receive(dp);
  16. ByteArrayInputStream bais = new ByteArrayInputStream(buf);
  17. DataInputStream dis = new DataInputStream(bais);
  18. System.out.println(dis.readLong());
  19. }
  20. }
  21. }

客户端代码如下:

  1. import java.net.*;
  2. import java.io.*;
  3. public class TestUDPClient{
  4. public static void main(String args[]) throws Exception
  5. {
  6. long n = 10000L;
  7. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  8. DataOutputStream dos = new DataOutputStream(baos);
  9. dos.writeLong(n);
  10. byte[] buf = baos.toByteArray();
  11. System.out.println("客户端:");
  12. System.out.println(buf.length);
  13. //创建数据报包的套接字,端口号9999
  14. DatagramSocket ds = new DatagramSocket(9999);
  15. //创建发送的数据报包
  16. DatagramPacket dp = new DatagramPacket(buf, buf.length,
  17. new InetSocketAddress("127.0.0.1", 8888));
  18. //从此套接字发送数据报包
  19. ds.send(dp);
  20. ds.close();
  21. }
  22. }

  编译、运行程序,运行结果如图7.12和图7.13所示。

7.7 Socket编程 - 图6


图7.12 UDP Socket编程服务器端

7.7 Socket编程 - 图7


图7.13 UDP Socket编程客户端