aliases: []
tags : " "
summary: [基于TCP/IP和UDP协议的Java Socket网络通信编程]
author : [yaenli]
notekey: [20230512-143738]
Socket 网络模型
Socket编程是在TCP/IP、UDP协议上的网络编程,在此之前,先了解下常见的网络模型:
-
OSI七层模型与TCP模型:
-
OSI七层模型详解(OSI七层模型详解)
Socket就在应用程序的传输层和应用层之间的一个抽象层:
Socket 知识
Socket概述
- 在计算机网络编程技术中,两个进程或者说两台计算机可以通过一个网络通信连接实现数据的交换,这种通信链路的端点就被称为“套接字”(Socket)。
- Socket 是网络驱动层提供给应用程序的一个接口或者说一种机制。
- Socket 方便了应用程序访问通讯协议TCP/IP、UDP 。
- 我们可以把套接字看成是 电话机,有了套接字,才有了通讯的工具。我们可以把IP地址看成是话号码, 端口号看成是分机号。
Java中Socket的实现
Socket的底层机制非常复杂,Java平台提供了一些简单但是强大的类,可以简单有效地使用Socket开发通信程序而无须了解底层机制。
java.net
包提供了若干支持基于套接字的客户端/服务器通信的类:
ServerSocket 类用来创建 TCP/IP 服务器端;
Socket 类用来创建 TCP/IP 客户端;
DatagramSocket 类用来实现 UDP 协议的客户端和服务器套接字;
DatagramPacket 类用来封装、处理 UDP 协议的数据包;
InetAddress 类用于封装IP和DNS等地址信息,在创建数据报报文和 Socket 对象时,可以使用。
Socket 编程
基于TCP/IP协议的Socket编程
(1)分别使用java.net.Socket和ServerSocket来创建客户端和服务器端套接字,它们是基于TCP协议进行工作的,工作过程如同打电话的过程,只有双方都接通了,才能开始通话。
(2)基于TCP创建的套接字叫做 流套接字。Socket通过数据流来完成数据的传递工作。
(3)Socket编程中,遵循client-server模型。服务器端相当于一个监听器,用来监听端口。
相关类
- Socket 类:用构造方法创建套接字,并将此套接字连接至指定的主机和端口。
// 常用构造
public Socket(@Nullable String host, int port ) throws UnknownHostException, IOException ;
public Socket(InetAddress address, int port ) throws IOException ;
// 常用方法
public void connect(SocketAddress host, int timeout) throws IOException;// 将此套接字连接到服务器,并指定一个超时值。
public InetAddress getInetAddress(); // 返回远程IP信息
public int getPort(); // 返回远程端口
public int getLocalPort(); // 返回本地端口
public InputStream getInputStream(); // 获取输入流
public OutputStream getOutputStream(); // 获取输出流
public void close() throws IOException // 关闭此套接字
- ServerSocket 类:等待客户端建立连接,连接建立以后进行通信。
// 常用构造方法
public ServerSocket(int port) throws IOException; // 创建指定端口的服务器套接字
public ServerSocket(int port, int backlog) throws IOException; // 指定最大连接队列
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException; // 指定绑定的本地ip地址
// 常用方法:Socket中的方法都能适用,除此之外,还有以下方法
public Socket accept() throws IOException // (阻塞方法)侦听连接请求,并返回一个新的通信套接字,该 Socket 连接到客户端的 Socket
public void bind(SocketAddress host, int backlog) // 将ServerSocket绑定到特定地址
开发流程
详细交互过程:
服务端编程步骤:
- 实例化
ServerSocket
对象,绑定指定端口; - 调用
accept()
,监听连接请求(阻塞等待),并返回通信Socket
; - 从
Socket
获取输出流输入流,从输入流中读取请求信息,向输出流中写入响应信息; - 关闭数据流和通信套接字。
客户端编程步骤:
- 实例化
Socket
对象,连接到指定服务器端; - 从
Socket
获取输出流输入流,向输出流中写入请求信息,从输入流中读取响应信息; - 关闭数据流和通信套接字。
客户端和服务器端的交互,采用一问一答的模式,先启动服务器进入监听状态,等待客户端的连接请求,连接成功以后,客户端先 “发言”,服务器给予 “回应”。
示例代码
采用多线程的方式,实现一个服务端响应多个客户端请求。
服务端代码: 使服务器端Socket一直处于监听状态。服务器端每监听到一个请求,创建一个线程对象并启动。
import java.net.*;
import java.io.*;
public class SocketServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
// 建立一个服务器Socket(ServerSocket)指定端口并开始监听
serverSocket = new ServerSocket(8800);
// 监听一直进行中
while (true) {
// 使用accept()方法等待客户发起通信
Socket socket = serverSocket.accept();
new SocketThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.net.*;
import java.io.*;
public class SocketThread extends Thread {
/*
* (1)创建服务器端线程类,run()方法中实现对一个请求的响应处理。
* (2)让服务器端Socket一直处于监听状态。
* (3)服务器端每监听到一个请求,创建一个线程对象并启动
*/
Socket socket = null;
//每启动一个线程,连接对应的Socket
public LoginThread(Socket socket) {
this.socket = socket;
}
//启动线程,即响应客户请求
public void run() {
InputStream is = null;
ObjectInputStream ois = null;
OutputStream os = null;
try {
//打开输入流
is = socket.getInputStream();
//反序列化
ois = new ObjectInputStream(is);
//获取客户端信息,即从输入流读取信息,DataObject为自定义数据类
DataObject data = (DataObject)ois.readObject();
if(data!=null){
System.out.println("我是服务端,客户端传送信息为:" + data.getMessage());
}
//给客户端一个响应,即向输出流中写入信息
os = socket.getOutputStream();
String reply = "服务端接收成功!";
os.write(reply.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
try {
os.close();
ois.close();
is.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端代码:
import java.net.*;
import java.io.*;
public class SocketClient {
/*
* 客户端通过输出流向服务器端发送请求信息
* 服务器侦听客户端的请求得到一个Socket对象,将这个Socket对象传递给线程类
* 线程类通过输入流获取客户端的请求并通过输出流向客户端发送响应信息
* 客户端通过输入流读取服务器发送的响应信息
*
*/
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
ObjectOutputStream oos = null;
InputStream is = null;
BufferedReader br = null;
try {
// 建立客户端Socket连接,指定服务器的位置为本机以及端口为8800
socket = new Socket("localhost", 8800);
// 打开输出流
os = socket.getOutputStream();
// 对象序列化
oos = new ObjectOutputStream(os);
// 发送客户端信息,即向输出流中写入信息,DataObject为自定义数据类
DataObject data = new DataObject("服务端你好,我是客户端");
oos.writeObject(data);
socket.shutdownOutput();
// 接收服务器端的响应,即从输入流中读取信息
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
String reply;
while ((reply = br.readLine()) != null) {
System.out.println("我是客户端,服务器的响应为:" + reply);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
is.close();
oos.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
基于UDP的socket编程
(1)基于TCP的网络通信是安全的,是双向的,如同打电话,需要先有服务端,建立双向连接后,才开始数据通信。
(2)基于UDP的套接字就是 数据报套接字。数据报是表示通信的一种报文类型,使用数据报进行通信时无须事先建立连接,只需要指明对方地址,然后将数据送出去。这样的网络通信是不安全的,所以只应用在如聊天系统、咨询系统等场合下。
(3)两端都要先构造好相应的数据包。数据报套接字发送成功之后,就相当于建立了一个虚连接,双方可以发送数据。
(4)Java中有两个可使用数据报实现通信的类,即DatagramPacket和DatagramSocket。
(5)DatagramPacket类起到数据容器的作用,DatagramSocket类用于发送或接收DatagramPacket,以此实现数据报通信。
UDP与TCP通信的区别:
TCP | UDP | |
---|---|---|
是否连接 | 面向连接 | 面向非连接 |
传输可靠性 | 可靠 | 不可靠 |
速度 | 慢 | 快 |
相关类
- java.net. DatagramPacket :数据电报包,用于封装发送的数据。
- java.net. DatagramSocket :数据电报套接字,不维护连接状态,不产生输入/输出数据流,用于接收和发送DatagramPacket对象封装好的数据报。
开发流程
UDP通信的两个端点程序是平等的,没有主次之分,甚至它们的代码都可以完全是一样的。
接收端编程步骤:
- 实例化
DatagramSocket
创建数据报套接字,绑定到指定端口; - 实例化
DatagramPacket
建立要接收的UDP包; - 调用
DatagramSocket.receive()
,接收UDP包; - 处理接收到的
DatagramPacket
数据包,关闭数据报套接字。
发送端编程步骤:
- 实例化
DatagramSocket
创建数据报套接字,绑定到指定端口; - 实例化
DatagramPacket
建立要发送的UDP包; - 调用
DatagramSocket.send()
,发送UDP包; - 关闭数据报套接字。
示例代码
发送方发送咨询问题,接收方回应咨询。
接收端代码:
import java.net.*;
import java.io.*;
public class UDPReceive {
public static void main(String[] args) {
/*
* 接收方实现步骤如下:
* (1)创建DatagramPacket对象,准备接收封装的数据。
* (2)创建DatagramSocket对象,接收数据保存于DatagramPacket对象中。
* (3)利用DatagramPacket对象处理数据。
*/
DatagramSocket ds = null;
DatagramPacket dp = null;
DatagramPacket dp_reply = null;
// 创建DatagramPacket对象,用来准备接收数据
byte[] buf = new byte[1024];
dp = new DatagramPacket(buf, 1024);
try {
// 创建DatagramSocket对象,接收数据
ds = new DatagramSocket(8800);
ds.receive(dp);
// 显示接收到的信息
String mess = new String(dp.getData(), 0, dp.getLength());
System.out.println(dp.getAddress().getHostAddress() + "说:" + mess);
// 给发送端返回数据,需要发送端去接受
String reply = "接收端:你好,我在,请咨询!";
// 创建DatagramPacket对象,封装数据
dp_reply = new DatagramPacket(reply.getBytes(),
reply.getBytes().length, dp.getSocketAddress());
ds.send(dp_reply);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}
发送端代码:
import java.net.*;
import java.io.*;
public class UDPSend {
/*
* 发送方实现步骤如下:
* (1)获取本地主机的InetAddress对象。
* (2)创建DatagramPacket对象,封装要发送的信息。
* (3)利用DatagramSocket对象将DatagramPacket对象数据发送出去。
*/
public static void main(String[] args) {
DatagramSocket ds = null;
InetAddress ia = null;
String mess = "发送端:你好,我想咨询一个问题。";
try {
// 获取本地主机地址
ia = InetAddress.getByName("localhost");
// 创建DatagramPacket对象,封装数据
DatagramPacket dp = new DatagramPacket(mess.getBytes(),
mess.getBytes().length, ia, 8800);
// 创建DatagramSocket对象,向服务器发送数据
ds = new DatagramSocket();
ds.send(dp);
//接受返回来的数据。
byte[] buf = new byte[1024];
DatagramPacket dpre = new DatagramPacket(buf, buf.length);
ds.receive(dpre);
// 显示接收到的信息
String reply = new String(dpre.getData(), 0, dpre.getLength());
System.out.println(dpre.getAddress().getHostAddress() + "说:"
+ reply);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}
参考文章
Java网络编程——Socket 编程
Java---Socket编程UDP/TCP-CSDN博客
JAVA进阶——Socket编程-CSDN博客
Java Socket实现简单的Http服务器