Socket:Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。
Socket API 分类:
1.UDP数据报套接字编程:
DatagramSocket 构造方法:
方法签名 方法说明 DatagramSocket() 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口
(一般用于客户端)DatagramSocket(int
port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用
于服务端)
DatagramSocket 方法:
方法签名 方法说明 void
receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻
塞等待)void send(DatagramPacket
p)从此套接字发送数据报包(不会阻塞等待,直接发送) void close() 关闭此数据报套接字 DatagramPacket 构造方法:
方法签名 方法说明 DatagramPacket(byte[]
buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在
字节数组(第一个参数buf)中,接收指定长度(第二个参数
length)DatagramPacket(byte[]
buf, int offset, int length,
SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节
数组(第一个参数buf)中,从0到指定长度(第二个参数
length)。address指定目的主机的IP和端口号DatagramPacket 方法:
方法签名 方法说明 InetAddress
getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取
接收端主机IP地址int getPort() 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获
取接收端主机端口号byte[] getData() 获取数据报中的数据
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用InetSocketAddress 来创建。InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法:
方法签名 方法说明 InetSocketAddress(InetAddress addr, int port) 创建一个Socket地址,包含IP地址和端口号
示例一:实现一个回显服务(EchoServer)=》一发一收(发什么就返回什么)
UDP服务端代码实现:
package network; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; // 站在服务器的角度: // 1. 源 IP: 服务器程序本机的 IP // 2. 源端口: 服务器绑定的端口 (此处手动指定了 9090) // 3. 目的 IP: 包含在收到的数据报中. (客户端的IP) // 4. 目的端口: 包含在收到的数据报中. (客户端的端口) // 5. 协议类型: UDP public class UdpEchoServer { // 进行网络编程, 第一步就需要先准备好 socket 实例~ 这是进行网络编程的大前提. private DatagramSocket socket = null; public UdpEchoServer(int port) throws SocketException { socket = new DatagramSocket(port); } // 启动服务器. public void start() throws IOException { System.out.println("启动服务器!"); // UDP 不需要建立连接, 直接接收从客户端来的数据即可 while (true) { // 1. 读取客户端发来的请求 DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024); socket.receive(requestPacket); // 为了接受数据, 需要先准备好一个空的 DatagramPacket 对象, 由 receive 来进行填充数据 // 把 DatagramPacket 解析成一个 String String request = new String(requestPacket.getData(), 0, requestPacket.getLength(), "UTF-8"); // 2. 根据请求计算响应(由于咱们这是一个回显服务, 2 省略) String response = process(request); // 3. 把响应写回到客户端 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); socket.send(responsePacket); System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response); } } // 由于是回显服务, 响应就和请求一样了. // 实际上对于一个真实的服务器来说, 这个过程是最复杂的. 为了实现这个过程, 可能需要几万行, 几十万行代码.... public String process(String request) { return request; } public static void main(String[] args) throws IOException { UdpEchoServer server = new UdpEchoServer(9090); server.start(); } }
代码解析:
UDP客户端代码实现:
package network; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.util.Scanner; public class UdpEchoClient { private DatagramSocket socket = null; private String serverIP; private int serverPort; // 站在客户端的角度: // 源 IP: 本机 IP // 源端口: 系统分配的端口 // 目的 IP: 服务器的 IP // 目的端口: 服务器的端口 // 协议类型: UDP public UdpEchoClient(String ip, int port) throws SocketException { // 此处的 port 是服务器的端口. // 客户端启动的时候, 不需要给 socket 指定端口. 客户端自己的端口是系统随机分配的~~ socket = new DatagramSocket(); serverIP = ip; serverPort = port; } public void start() throws IOException { Scanner scanner = new Scanner(System.in); while (true) { // 1. 先从控制台读取用户输入的字符串 System.out.print("-> "); String request = scanner.next(); // 2. 把这个用户输入的内容, 构造成一个 UDP 请求, 并发送. // 构造的请求里包含两部分信息: // 1) 数据的内容. request 字符串 // 2) 数据要发给谁~ 服务器的 IP + 端口 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIP), serverPort); socket.send(requestPacket); // 3. 从服务器读取响应数据, 并解析 DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024); socket.receive(responsePacket); String response = new String(responsePacket.getData(), 0, responsePacket.getLength(), "UTF-8"); // 4. 把响应结果显示到控制台上. System.out.printf("req: %s, resp: %s\n", request, response); } } public static void main(String[] args) throws IOException { // 由于服务器和客户端在同一个机器上, 使用的 IP 仍然是 127.0.0.1 . 如果是在不同的机器上, 当然就需要更改这里的 IP 了 UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090); client.start(); } }
代码解析:
示例二:写一个简单的翻译程序(使用UDP协议)
UDP服务器代码:
package network; import java.io.IOException; import java.net.SocketException; import java.util.HashMap; public class UdpDictServer extends UdpEchoServer { private HashMap
dict = new HashMap<>(); public UdpDictServer(int port) throws SocketException { super(port); // 简单构造几个词 dict.put("cat", "小猫"); dict.put("dog", "小狗"); dict.put("fuck", "卧槽"); dict.put("pig", "小猪"); } @Override public String process(String request) { return dict.getOrDefault(request, "该词无法被翻译!"); } public static void main(String[] args) throws IOException { UdpDictServer server = new UdpDictServer(9090); server.start(); } } 注意:如果想要启动多个客户端,可以设置一下IDEA
2.TCP流套接字编程:
- ServerSocket API
- Socket API
ServerSocket API: ServerSocket 是创建TCP服务端Socket的API
ServerSocket 构造方法:
方法签名 方法说明 ServerSocket(int port) 创建一个服务端流套接字Socket,并绑定到指定端口 ServerSocket 方法:
方法签
名方法说明 Socket
accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket
对象,并基于该Socket建立与客户端的连接,否则阻塞等待void
close()关闭此套接字 Socket API: Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket 构造方法:
方法签名 方法说明 Socket(String host, int
port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的
进程建立连接
Socket 方法:
方法签名 方法说明 InetAddress getInetAddress() 返回套接字所连接的地址 InputStream getInputStream() 返回此套接字的输入流 OutputStream getOutputStream() 返回此套接字的输出流
示例一:写一个回显服务(使用TCP)
TCP服务端代码:
package network; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class TcpEchoServer { // listen => 英文原意 监听~~ // 但是在 Java socket 中是体现不出来 "监听" 的含义的~~ // 之所以这么叫, 其实是 操作系统原生的 API 里有一个操作叫做 listen // private ServerSocket listenSocket = null; private ServerSocket serverSocket = null; public TcpEchoServer(int port) throws IOException { serverSocket = new ServerSocket(port); } public void start() throws IOException { System.out.println("服务器启动!"); while (true) { // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话) // accept 就是在 "接电话", 接电话的前提是, 有人给你打了~~, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞. // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的. // 进一步讲, serverSocket 就干了一件事, 接电话~~ Socket clientSocket = serverSocket.accept(); processConnection(clientSocket); } } private void processConnection(Socket clientSocket) { System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); // 接下来来处理请求和响应 // 这里的针对 TCP socket 的读写就和文件读写是一模一样的!! try (InputStream inputStream = clientSocket.getInputStream()) { try (OutputStream outputStream = clientSocket.getOutputStream()) { // 循环的处理每个请求, 分别返回响应 Scanner scanner = new Scanner(inputStream); while (true) { // 1. 读取请求 if (!scanner.hasNext()) { System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); break; } // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的 String request = scanner.next(); // 2. 根据请求, 计算响应 String response = process(request); // 3. 把这个响应返回给客户端 // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下 PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(response); // 刷新缓冲区, 如果没有这个刷新, 可能客户端就不能第一时间看到响应结果. printWriter.flush(); System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response); } } } catch (IOException e) { e.printStackTrace(); } finally { // 此处要记得来个关闭操作. try { clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } private String process(String request) { return request; } public static void main(String[] args) throws IOException { TcpEchoServer server = new TcpEchoServer(9090); server.start(); } }
代码解析:
TCP客户端代码:
package network; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; public class TcpEchoClient { // 用普通的 socket 即可, 不用 ServerSocket 了 // 此处也不用手动给客户端指定端口号, 让系统自由分配. private Socket socket = null; public TcpEchoClient(String serverIP, int serverPort) throws IOException { // 其实这里是可以给的. 但是这里给了之后, 含义是不同的. ~~ // 这里传入的 ip 和 端口号 的含义表示的不是自己绑定, 而是表示和这个 ip 端口建立连接!! // 调用这个构造方法, 就会和服务器建立连接 (打电话拨号了) socket = new Socket(serverIP, serverPort); } public void start() { System.out.println("和服务器连接成功!"); Scanner scanner = new Scanner(System.in); try (InputStream inputStream = socket.getInputStream()) { try (OutputStream outputStream = socket.getOutputStream()) { while (true) { // 要做的事情, 仍然是四个步骤 // 1. 从控制台读取字符串 System.out.print("-> "); String request = scanner.next(); // 2. 根据读取的字符串, 构造请求, 把请求发给服务器 PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(request); printWriter.flush(); // 如果不刷新, 可能服务器无法及时看到数据. // 3. 从服务器读取响应, 并解析 Scanner respScanner = new Scanner(inputStream); String response = respScanner.next(); // 4. 把结果显示到控制台上. System.out.printf("req: %s, resp: %s\n", request, response); } } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090); client.start(); } }
代码解析:
问题:
方法一:使用多线程
示例二:完成一个简单的翻译程序(使用TCP)
同样的也只需要修改TCP服务端即可
TCP服务端代码:
package network; import java.io.IOException; import java.util.HashMap; public class TcpDictServer extends TcpThreadPoolEchoServer { private HashMap
dict = new HashMap<>(); public TcpDictServer(int port) throws IOException { super(port); dict.put("cat", "小猫"); dict.put("dog", "小狗"); dict.put("pig", "小猪"); dict.put("fuck", "卧槽"); } @Override public String process(String request) { return dict.getOrDefault(request, "当前的词无法翻译"); } public static void main(String[] args) throws IOException { TcpDictServer server = new TcpDictServer(9090); server.start(); } } 代码解析: