1 网络编程的理解
-
网络编程是指运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
-
网络编程从大的方面来说就是对信息的发送和接收;
-
通过操作相应Api调度计算机硬件资源,并利用传输管道(网线)进行数据交换的过程;
-
更多涉及:网络模型、套接字、数据包。
2 OSI网络模型
应用程、表示层、会话层、传输层、网络层、数据链路层、物理层。
-
基础层:物理层、数据链路层、网络层;
-
传输层:TCP-UDP协议层、Socket;
-
高级层:会话层、表示层、应用层。
3 java.net包提供的两种网络常见协议
1. TCP:TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP层位于IP层之上,应用层之下的中间层。
- TCP是面向连接的通信协议;
- 通过三次握手建立连接,通讯完成时关闭连接;
- 由于TCP是面向连接的所以只能用于端到端的通讯。
2. UDP:UDP(User Datagram Protocol,用户数据报协议),位于OSI模型的传输层。一个无连接的协议。提供了应用程序之间要发送的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。
- UDP是面向无连接的通讯协议;
- UDP数据包括目的的端口号和源端口号信息;
- 由于通讯不需要连接,所以可以实现广播发送,不局限于端到端。
4 Socket编程
4.1 Socket含义
-
简单来说是IP地址与端口的结合协议(RFC739);
-
一种地址与端口的结合描述协议;
-
TCP/IP协议的相关Api总称,是网络Api的集合实现;
-
涵盖了:Stream Socket(流)和Datagram Socket(数据报)。
4.2 Socket的作用
-
Socket在网络传输中用于唯一标示两个端点之间的连接;
-
端点:包括IP+Port; ip+port=socket
-
4个要素:客户端地址、客户端端口、服务器地址、服务器端口。
4.3 Socket编程的理解
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
-
服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
-
服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
-
服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
-
Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
-
在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。
4.4 Socket类的方法
java.net.Socket类代表客户端和服务端用来互相沟通的套接字。客户端要获得一个Socket对象通过实例化,服务端获得一个Socket对象通过accept()方法的返回值。
- 构造方法
方法 | 描述 |
---|---|
public Socket(String host, int port) throws UnknownHostException, IOException | 创建一个流套接字并将其连接到指定的主机上的指定端口 |
public Socket(InetAddress host, int port) throws IOException | 创建一个流套接字并将其连接到指定IP的指定端口 |
public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException | 创建一个流套接字并将其连接到指定的远程主机上的指定远程端口 |
public Socket() | 创建未连接的套接字 |
- Socket类常用方法
方法 | 描述 |
---|---|
public void connect(SocketAddress host, int timeout) throws IOExceptio | 将此套接字连接到指定主机,并指定超时时间 |
public InetAddress getInetAddress() | 返回套接字连接的地址 |
public int getPort() | 返回此套接字连接到的远程端口 |
public int getLocalPort() | 返回此套接字连接的本地端口 |
public SocketAddress getRemoteSocketAddress() | 返回此套接字连接的端点,若无返回null |
public InputStream getInputStream() throws IOException | 返回此套接字输入流 |
public OutputStream getOutputStream() throws IOException | 返回此套接字输出流 |
public void close() throws IOException | 关闭此套接字 |
- InetAddress类的方法
此类表示互联网协议(IP)地址。Socket编程常用方法,如下:
方法 | 描述 |
---|---|
static InetAddress getByAddress(byte[] addr) | 给定原始IP地址,返回InetAddress对象 |
static InetAddress getByAddress(String host, byte[] addr) | 根据主机名和IP地址创建InetAddress对象 |
static InetAddress getByName(String host) | 根据主机名确定主机的IP地址 |
String getHostAddress() | 返回字符串类型的IP地址 |
String getHostName() | 获取此IP地址的主机名 |
static InetAddress getLocalHost() | 返回本地主机 |
String toString() | 将IP地址转化为String |
4.5 ServerSocket类的方法
服务器应用程序通过使用java.net.ServerSocket类以获取一个端口,侦听等待客户端连接。
- 构造方法
方法 | 描述 |
---|---|
public ServerSocket(int port) throws IOException | 创建绑定到指定端口的服务器套接字 |
public ServerSocket(int port, int backlog) throws IOException | 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号 |
public ServerSocket(int port, int backlog, InetAddress address) throws IOException | 使用指定的端口、侦听backlog和要绑定到的本地IP地址创建服务器 |
public ServerSocket() throws IOException | 创建不绑定服务器的套接字 |
- ServerSocket类常用方法
方法 | 描述 |
---|---|
public int getLocalPort() | 返回此套接字在其上侦听的端口 |
public Socket accept() throws IOException | 侦听并等待套接字的连接 |
public void setSoTimeout(int timeout) | 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。 |
public void bind(SocketAddress host, int backlog) | 将ServerSocket绑定到指定的地址(ip+port) |
4.6 示例
4.6.1 Socket服务端
Socket相关接口在java.net包中已经存在,所以这里不需要再做额外的引用。
1. SocketServer.java(Socket服务端核心)
/**
* socket服务端
* @author W
* @createDate 2022/7/14
* @description:
*/
@Data
@Component
public class SocketServer {
final static Logger log = LoggerFactory.getLogger(SocketServer .class);
@Value("${socket.port}")
private Integer port;
private boolean started;
private ServerSocket serverSocket;
private ExecutorService executorService = Executors.newCachedThreadPool();
public void start(Integer port){
log.info("port: {}, {}", this.port, port);
try {
serverSocket = new ServerSocket(port == null ? this.port : port);
started = true;
log.info("Socket服务已启动,占用端口: {}", serverSocket.getLocalPort());
} catch (IOException e) {
log.error("端口冲突,异常信息:{}", e);
System.exit(0);
}
try {
while (started) {
// 接收并等待客户端连接,返回客户端套接字
Socket socket = serverSocket.accept();
// 维持连接-长连接
socket.setKeepAlive(true);
ClientSocketConnect socketConnect = new ClientSocketConnect(socket);
Thread thread = new Thread(socketConnect);
executorService.execute(thread);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2. Connection.java(连接客户端相关接口)
/**
* @author W
* @createDate 2022/7/25
* @description:
*/
public interface Connection {
/**
* 发送信息
* @param message
*/
void sendMessage(String message);
/**
* 接收信息
* @return
*/
String receiveMessage();
/**
* 指定socket资源回收
*/
void close();
/**
* 判断数据连接状态
* @return
*/
boolean isSocketClosed();
}
3. ClientSocketConnect.java(自定义连接客户端Socket类)
/**
* @author W
* @createDate 2022/7/14
* @description: 自定义封装连接客户端
*/
@Slf4j
@Data
public class ClientSocketConnect implements Connection,Runnable{
private Socket socket = null;
private BufferedReader in = null;
private BufferedWriter out = null;
private DataInputStream inputStream = null;
// socket输出流
private DataOutputStream outputStream = null;
// 获取客户端的ip
private String clientIp;
// private String key;
// private String message;
public ClientSocketConnect(){
}
public ClientSocketConnect(Socket socket){
this.socket = socket;
try {
in = new BufferedReader(new InputStreamReader(socket
.getInputStream(), "UTF-8"));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"));
inputStream = new DataInputStream(socket.getInputStream());
outputStream = new DataOutputStream(socket.getOutputStream());
clientIp = socket.getInetAddress().getHostAddress();
} catch (IOException e) {
System.out.println("客户端连接异常");
e.printStackTrace();
}
}
@Override
public void run() {
//每5秒进行一次客户端连接,判断是否需要释放资源
while (true){
try {
TimeUnit.SECONDS.sleep(5);
if (isSocketClosed()){
log.info("客户端已关闭,其IP为:{}", clientIp);
//关闭对应的服务端资源
close();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服务端发送消息
* 向指定客户端发送信息
* @param message
*/
@Override
public void sendMessage(String message){
System.out.println("服务端发送消息==:"+message);
try {
out.write(message);
out.newLine();
out.flush();
} catch (Exception e) {
System.out.println("发送信息异常:{}");
close();
}
}
/**
* 服务端接收消息
* 获取指定客户端的上传信息
* @return
*/
@Override
public String receiveMessage(){
try {
String msg = in.readLine();
return msg;
} catch (IOException e) {
e.printStackTrace();
close();
}
return null;
}
/**
* 指定Socket资源回收
*/
@Override
public void close(){
log.info("进行资源回收"+clientIp);
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
try {
this.socket.close();
} catch (Exception e) {
log.warn("close socket get excption:", e);
}
log.info("ClientSocketConnect----->close 资源回收成功");
}
/**
* 发送数据包,判断数据连接状态
* @return
*/
@Override
public boolean isSocketClosed(){
try {
//socket.sendUrgentData(1);
inputStream = new DataInputStream(socket.getInputStream());
outputStream = new DataOutputStream(socket.getOutputStream());
outputStream.write("测试1".getBytes(StandardCharsets.UTF_8));
byte[] bytes = new byte[1024];
inputStream.read(bytes);
String receive = new String(bytes, "utf-8");
System.out.println("服务端接收消息===:" + receive);
return false;
} catch (Exception e) {
return true;
}
}
}
4.6.2 Socket客户端
Client.java(模拟客户端)
/**
* @author W
* @createDate 2022/7/15
* @description: 模拟客户端
*/
public class Client {
public static void main(String[] args) {
String host = "127.0.0.1";
Integer port = 8533 ;
try {
// 与服务端建立连接
Socket socket = new Socket(host, port);
socket.setOOBInline(true);
// 建立输入输出流
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
int i = 0;
while (true){
send("客户发送",outputStream);
receive(inputStream);
i++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发送数据
* @param str
* @param outputStream
*/
public static void send(String str,DataOutputStream outputStream) throws Exception{
System.out.println("客户端发送消息:=="+str);
try {
outputStream.write(str.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 接收数据
* @param inputStream
* @return
* @throws Exception
*/
public static String receive(DataInputStream inputStream) throws Exception{
try {
byte[] bytes = new byte[1024];
inputStream.read(bytes);
String info = new String(bytes,"utf-8");
System.out.println("客户端接收信息:=="+info);
return info;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
4.6.3 Socket配置,在启动SpringBoot时启动Socket服务。
@SpringBootApplication
public class SpringbootSocketApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringbootSocketStudentApplication.class, args);
applicationContext.getBean(SocketServer.class).start(8533);
}
}
4.7 测试(双向通信)
客户端通过ip和端口,连接到指定的server,然后通过Socket获得输出流,并向其输出内容,服务器会获得消息。服务端通过serverSocket.accept()侦听等待接收客户端连接,通过Socket获得输出流,向客户端发送消息,客户端通过Socket获得输入流接收消息。客户端关闭,服务端会进行相关资源回收。
客户端:
服务端: