JAVA Socket实现TCP编程(非常详细,附带实例)
在网络编程中,套接字(Socket)为网络上两台计算机之间的通信提供了一种机制。
两台计算机之间的通信通过套接字创建一条通信信道,程序员使用这条通信信道在两台计算机之间发送数据。在客户机/服务器工作模式下,将端口号与 IP 地址的组合称为网络套接字。
Java 中的套接字分为 TCP 套接字(由 Socket 类实现)和 UDP 套接字(由 DatagramSocket 类实现)两种形式。在客户机/服务器工作模式下,每个 Socket 可以进行读和写两种操作。本节重点讲解 TCP 套接字。
Socket 通信模型如下图所示:

图 1 Socket通信模型
由图 1 可知,Socket 通信分为 4 个步骤:
Socket 类的对象既可以是客户端 Socket,也可以是服务器端接收到客户端建立连接(accept() 方法)的请求后返回的服务器端 Socket。
Socket 类的构造方法如下:
Socket 类的常用方法如下:
【实例】使用 Socket 类完成标准设备的网络客户端程序。该程序向服务器发送一个问候字符串,并显示服务器端响应的字符串信息。

图 2 运行结果
通过运行结果可以看到,代码在运行时抛出了 ConnectionException 异常,产生该异常的原因是本地主机的 8080 端口目前并没有服务器程序正在运行,导致客户端尝试建立连接失败。
接下来将演示服务器端的开发方法,当客户端程序能够和服务器端程序正常建立连接时,就不会再出现该异常。
ServerSocket 类的构造方法如下:
利用 ServerSocket 类的构造方法可以在服务器上建立接收客户套接字的服务器套接字对象。
ServerSocket 类常用的方法如下:
【实例】服务器端应用程序。应用程序在 8080 端口监听,当检测到有客户端请求时,会产生一个内容为“客户,你好,我是服务器”的字符串,并输出到客户端。
整体运行情况如下图所示:

图 3 整体运行情况
【实例】多线程Java Socket编程。
服务器端程序如下:
客户端程序如下:
两台计算机之间的通信通过套接字创建一条通信信道,程序员使用这条通信信道在两台计算机之间发送数据。在客户机/服务器工作模式下,将端口号与 IP 地址的组合称为网络套接字。
Java 中的套接字分为 TCP 套接字(由 Socket 类实现)和 UDP 套接字(由 DatagramSocket 类实现)两种形式。在客户机/服务器工作模式下,每个 Socket 可以进行读和写两种操作。本节重点讲解 TCP 套接字。
Socket 通信模型如下图所示:

图 1 Socket通信模型
由图 1 可知,Socket 通信分为 4 个步骤:
- 创建 ServerSocket 和 Socket;
- 打开连接 Socket的输入/输出流;
- 按照协议对 Socket 执行读/写操作;
- 关闭输入/输出流和 Socket。
Java Socket类
在 Java 中,使用 java.net.Socket 类对套接字进行抽象,封装两台主机之间通信的端点的信息。Socket 类的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂,可以将自己配置为创建适合本地防火墙的套接字。Socket 类的对象既可以是客户端 Socket,也可以是服务器端接收到客户端建立连接(accept() 方法)的请求后返回的服务器端 Socket。
Socket 类的构造方法如下:
- Socket(String host, int port):创建一个客户端流套接字 Socket,并与运行在目标主机上的目标端口的进程建立连接。
Socket 类的常用方法如下:
- InetAddress getInetAddress():返回套接字所连接的地址;
- InputStream getInputStream():放回此套接字的输入流中;
- OutputStream getOutputStream():放回此套接字的输出流中。
【实例】使用 Socket 类完成标准设备的网络客户端程序。该程序向服务器发送一个问候字符串,并显示服务器端响应的字符串信息。
import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; public class ClientDemo { public static void main(String [] args) { String ip = "127.0.0.0.1"; // 本地主机地址 int port = 8080; try { // 创建 Socket 对象 // 尝试和 ip 主机的 port 端口上的程序建立连接 Socket client_socket = new Socket(ip, port); DataInputStream in = new DataInputStream(client_socket.getInputStream()); DataOutputStream out = new DataOutputStream(client_socket.getOutputStream()); } { out.writeUTF("hello, I'm client"); System.out.println("client has started"); // 等待读取服务器端的响应信息 String str = in.readUTF(); System.out.println("服务器端的响应信息: "+str); } catch(Exception e) { e.printStackTrace(); } } }运行结果如下图所示:

图 2 运行结果
通过运行结果可以看到,代码在运行时抛出了 ConnectionException 异常,产生该异常的原因是本地主机的 8080 端口目前并没有服务器程序正在运行,导致客户端尝试建立连接失败。
接下来将演示服务器端的开发方法,当客户端程序能够和服务器端程序正常建立连接时,就不会再出现该异常。
Java ServerSocket类
java.net.ServerSocket 类实现了服务器套接字。该类是遵循 TCP 协议的,所以必须和客户端 Socket 建立连接,这样才能完成信息的接送服务器套接字等待来自网络的请求。它先基于该请求执行某些操作,再向请求者返回结果。ServerSocket 类的构造方法如下:
- ServerSocket():创建一个服务器套接字,未绑定到端口;
- ServerSocket(int port):创建一个服务器套接字,绑定到指定的端口;
- ServerSocket(int port,int backlog):创建一个服务器套接字,并将其绑定到指定的本地端口,通过 backlog 设置连接请求的最大队列长度;
- ServerSocket(int port,int backlog,InetAddress bindAddr):用指定的端口创建一个服务器,并绑定到指定的本地地址。
利用 ServerSocket 类的构造方法可以在服务器上建立接收客户套接字的服务器套接字对象。
ServerSocket 类常用的方法如下:
- Socket accept():开始监听指定的端口(创建时绑定的端口),有客户端连接后,返回一个服务器端 Socket 对象,并基于该对象建立与客户端的连接,否则阻塞等待;
- void close():关闭该套接字,防止内存泄漏;
- ServerSocketChannel getChannel():返回与此套接字关联的独特的 ServerSocketChannel 对象;
- InetAddress getInetAddress():返回此服务器套接字的本地地址;
- int getLocalPort():返回此套接字正在监听的端口号;
- SocketAddress getLocalSocketAddress():返回此套接字绑定的端口的地址。
【实例】服务器端应用程序。应用程序在 8080 端口监听,当检测到有客户端请求时,会产生一个内容为“客户,你好,我是服务器”的字符串,并输出到客户端。
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo { public static void main(String args[]) { ServerSocket server = null; Socket client = null; String s = null; DataOutputStream out = null; DataInputStream in = null; try { server = new ServerSocket(8080); System.out.println("服务器端正常启动"); } catch (IOException e1) { System.out.println("ERROR:" + e1); } try { // 线程阻塞,等待客户端连接后继续向下执行 client = server.accept(); // 获取客户端信息 String clientInfo = client.getRemoteSocketAddress().toString(); System.out.println("收到客户端"+clientInfo+"发来的访问"); in = new DataInputStream(client.getInputStream()); out = new DataOutputStream(client.getOutputStream()); while (true) { s = in.readUTF(); if (s != null) { break; } } // 向客户端发送消息 out.writeUTF("客户端,你好,我是服务器"); out.close(); } catch (IOException e) { System.out.println("ERROR:" + e); } } }启动 ServerDemo 程序,可以看到,ServerDemo 程序在控制台中输出“服务器端正常启动”后进入阻塞状态,等待客户端主动建立连接。此时再启动前面开发的 ClientDemo 程序,可以看到异常没有再出现。ClientDemo 程序在控制台中输出收到的服务器消息后结束运行。
整体运行情况如下图所示:

图 3 整体运行情况
多线程Java Socket编程
当网络中的多个用户请求服务器服务时,服务器将利用多线程机制来实现客户请求。当服务器通过端口监听到有客户发送请求时,服务器就会启动一个线程来响应该客户的请求,当服务器在启动完线程之后又立刻进入监听状态,监听下一个客户请求并予以响应。【实例】多线程Java Socket编程。
服务器端程序如下:
import java.io.*; import java.net.*; import java.util.concurrent.*; public class MultiThreadServer { private int port = 8821; private ServerSocket serverSocket; // 线程池 private ExecutorService executorService; // 单个 CPU 线程池的大小 private final int POOL_SIZE = 10; public MultiThreadServer() throws IOException { serverSocket = new ServerSocket(port); // Runtime.availableProcessor()方法返回当前系统的 CPU 数目 executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL_SIZE); System.out.println("服务器启动"); } public void service() { while(true) { Socket socket = null; try { // 接收客户端连接请求,当客户端发起连接请求时,就会触发 accept()方法 socket = serverSocket.accept(); executorService.execute(new Handler(socket)); } catch(Exception e) { e.printStackTrace(); } } } public static void main(String[] args) throws IOException { new MultiThreadServer().service(); } class Handler implements Runnable { private Socket socket; public Handler(Socket socket) { this.socket = socket; } private PrintWriter getWriter(Socket socket) throws IOException { OutputStream socketOut = socket.getOutputStream(); return new PrintWriter(socketOut, true); } private BufferedReader getReader(Socket socket) throws IOException { InputStream socketIn = socket.getInputStream(); return new BufferedReader(new InputStreamReader(socketIn)); } public String echo(String msg) { return "echo:" + msg; } public void run() { try { System.out.println("New connection accepted" + socket.getInetAddress() + ":" + socket.getPort()); BufferedReader br = getReader(socket); PrintWriter pw = getWriter(socket); String msg = null; while ((msg = br.readLine()) != null) { System.out.println(msg); pw.println(echo(msg)); if (msg.equals("bye")) break; } } catch (IOException e) { e.printStackTrace(); } finally { try { if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } }
客户端程序如下:
public class MultiThreadClient { public static void main(String[] args) { int numTasks = 10; ExecutorService exec = Executors.newCachedThreadPool(); for (int i = 0; i < numTasks; i++) { exec.execute(createTask(i)); } } // 定义一个简单的任务 private static Runnable createTask(final int taskID) { return new Runnable() { private Socket socket = null; private int port = 8821; public void run() { System.out.println("Task " + taskID + ":start"); try { socket = new Socket("localhost", port); // 发送关闭命令 OutputStream socketOut = socket.getOutputStream(); socketOut.write("shutdown\r\n".getBytes()); // 接收服务器的反馈 BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); String msg = null; while ((msg = br.readLine()) != null) { System.out.println(msg); } } catch (IOException e) { e.printStackTrace(); } } }; } }