1. 计算机网络结构
五层模型
层 | 该层的协议 |
---|---|
应用层 | HTTP、FTP、SMTP… |
传输层 | TCP、UDP… |
网络层 | IP、ICMP… |
链路层 | FDDI、PPP… |
物理层 | IEEE… |
各层次协议的数据单元
- 应用层:报文(message)
- 传输层:报文段(segment):TCP段,UDP数据报
- 网络层:分组packet(如果无连接方式:数据报datagram)
- 数据链路层:帧(frame)
- 物理层:位(bit)
两个端点通信的过程
图片来源 中科大郑烇、杨坚全套《计算机网络》
2. Socket 概念
Socket是两个主机间会话关系的标识(通过ip、port实现)。
Socket位于应用层和传输层之间,是传输层提供给应用层的服务,实现形式是Socket API。
在程序中Socket就是一个int 数(指向某一个会话,相当于句柄),这样是为了减少层间传输的信息量(不用每次要传本机ip、本机port、目标ip、目标port),简单,便于管理。
例:1001,1002表示了两个会话
Scoket数 | 本机ip | 本机port | 目标ip | 目标port |
---|---|---|---|---|
1001 | 1.1.1.1 | 8888 | 2.2.2.2 | 80 |
1002 | 1.1.1.1 | 8888 | 2.2.2.2 | 433 |
注:表中的两个会话虽然使用了同一个本地端口,但是能通过socket区分,所以可以同时进行通信。
TCP Socket
TCP Socket基于传输层的TCP协议,是一个四元组,包含本机ip、本机port、目标ip、目标port。
在基于TCP时,应用层向传输层传输时要提供 TCP Socekt 和 报文 两部分。在传输层被封装为报文段,网络层会通过TCP Socekt 提取目标ip+port进行寻址。
UDP Socket
UDP Socket基于传输层的UDP协议,是个二元组,包含本机ip、本机port。
在基于UDP时,应用层向传输层传输时要提供 UDP Socekt 、报文和目标ip+目标port 三部分。UDO Socekt和报文会被封装为数据报,目标ip+port则会通过传输层交给网络层进行寻址。
3. Java代码实现Socket通信
3.1 TCP通信
服务端
package com.alibaba.socket.server;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Date: 2022/4/18 17:21
* @author: ZHX
* @Description: 服务端简单使用TCPSocket进行通信,不考虑应用层的协议。
* 步骤:
* 1.创建Socket
* 2.绑定服务端的ip、port.
* 3.监听端口,阻塞式等待建立连接
* 4.读
* 5.处理
* 6.发
*/
public class TCPSocketServer {
public static void main(String[] args) throws IOException {
method();
}
public static void method() throws IOException {
//1.创建服务器Socket,并绑定本机ip+port 端口号默认是0.0.0.0 + 8888.
// 客户端ip+port默认是*:*,接受所有ip的所有端口
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
//2.监听端口, 获取监听到的Socket对象。阻塞式
Socket accept = serverSocket.accept();
//3.接受客户端请求.
InputStream inputStream = accept.getInputStream();
BufferedInputStream bis = new BufferedInputStream(inputStream);
byte[] bytes = new byte[8192];
bis.read(bytes);
//4.处理数据
String s = new String(bytes).toUpperCase();
//5.处理完,响应给客户端
OutputStream outputStream = accept.getOutputStream();
outputStream.write(s.getBytes());
}
}
}
客户端
package com.alibaba.socket.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* @Date: 2022/4/18 17:21
* @author: ZHX
* @Description: 使用Socket简单实现客户端发送、接受数据。
* 步骤:
* 1.创建Socket对象
* 2.绑定本机ip port 对方ip port
* 3.建立连接
* 4.发送请求
* 5.输出响应内容
*/
public class TCPSocketClient {
public static void main(String[] args) throws IOException {
method();
}
public static void method() throws IOException {
//1.创建Socket绑定本机ip+port和目标ip+port。并进行尝试连接
// 本机ip+port 不写默认分配0.0.0.0:空闲的端口 .0.0.0.0
// 服务器端的ip+port localhost:8888
Socket socket = new Socket("localhost", 8888);//绑定目标ip+port,连接失败报异常
//InetAddress不允许使用,我猜可能是因为DHCP ip不确定?
//new Socket("localhost", 8888,new InetAddress(),7777);
//2.连接成功,向服务器发送请求
OutputStream outputStream = socket.getOutputStream();
String content = "aaabbAbcd";
outputStream.write(content.getBytes());
//socket.shutdownOutput();//禁用该socket的输出流,不能再写了
//outputStream.close();会同时关闭socket流.
//3.接受服务器响应的数据并打印
byte[] bytes = new byte[8192];
InputStream inputStream = socket.getInputStream();
/*
//不能使用while != -1 因为不知道服务器发来的数据什么时候会发完,网络流无结束,可以自定义结束符http就是这样做的.
int len = 0;
while ((len = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes));
}*/
inputStream.read(bytes);//这里定义的数组足够大,都能读进来
System.out.println(new String(bytes));
//socket.shutdownInput();//禁用该socket的输入流,inputStream流并没有被关闭之后再读的数据都会被抛弃
//5.因为这里我定义的bytes足够大,一次肯定都读完了就把socket流关闭。按理要和结束符配合的。
outputStream.close();
inputStream.close();
socket.close();
}
}
3.2 UDP通信
服务端
package com.alibaba.socket.server;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
/**
* @Date: 2022/4/18 17:21
* @author: ZHX
* @Description: 服务端简单使用UPD Socket进行通信,不考虑应用层的协议。
*/
public class UDPSocketServer {
public static void main(String[] args) throws IOException {
method();
}
public static void method() throws IOException {
//1.创建数据包套接字,绑定服务器本地的ip+port
DatagramSocket socket = new DatagramSocket(new InetSocketAddress("localhost", 8888));
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, 1024);
while (true) {
//2.监听端口8888,阻塞式的等待数据报
socket.receive(receivePacket);
//拿到数据报,简单打印下.
byte[] data = receivePacket.getData();
String s = new String(data, StandardCharsets.UTF_8);
System.out.println(s);
//3.处理数据
s = s.toUpperCase();//小写转大写
//4.响应给客户端,通过接受的数据报中的客户端Socket的ip和port(交给传输层时自动封装的的。).
byte[] b = s.getBytes();
DatagramPacket sendPacket = new DatagramPacket(b, b.length, receivePacket.getAddress(), receivePacket.getPort());
socket.send(sendPacket);
}
}
}
客户端
package com.alibaba.socket.client;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
/**
* @Date: 2022/4/18 17:21
* @author: ZHX
* @Description: 使用Socket简单实现客户端发送、接受数据。
* 步骤:UDP 无连接 直接发
* 1. 创建UDP Socket对象
* 2. 发送数据
* 3. 关闭socket流
*/
public class UDPSocketClient {
public static void main(String[] args) throws IOException {
method();
}
public static void method() throws IOException {
//UDP Socket是二元组,本地ip+port
//1.创建UDP Socket(数据包套接字),绑定本机ip + 端口
DatagramSocket socket = new DatagramSocket();//不写默认分配空闲端口
//2.准备请求的数据 (如果要是应用层还要按照应用层协议封装数据)
String content = "aaabbAbcd";
byte[] bytes = content.getBytes();
//3.将输入放入数据报,同时提供服务器的ip+port
DatagramPacket datagramPacket = new DatagramPacket(content.getBytes(), bytes.length,new InetSocketAddress("localhost",8888));
//4.直接发送,不用建立连接
socket.send(datagramPacket);
//5.接受服务器响应的数据报,阻塞式
byte[] result = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(result,1024);
socket.receive(receivePacket);
//提取数据报中的数据,存到字节数组中
byte[] data = receivePacket.getData();
//简单打印下
String s = new String(data, StandardCharsets.UTF_8);
System.out.println(s);
//4.关闭socket.
socket.close();
}
}