什么是Socket
- 简单来说是IP地址与端口的结合协议
- 一种地址与端口的结合描述协议
- TCP/IP协议的相关API的总称;是网络API的集合实现
- 涵盖了Stream Socket/Datagram Socket
协议简介
协议相当于相互通信的程序间达成的一种约定,规定了分组报文的结构,交换方法以及怎样对报文进行解析等。
-
TCP/IP协议族中包含IP协议,TCP协议,UDP协议。其中TCP协议和UDP协议使用的地址叫做端口号,端口号用来区分统一主机上不同的应用程序,这两者协议也叫做端到端的传输协议,因为他们将数据从一个应用程序传输到另一个应用程序;而IP协议只是将数据从一个主机传输到另一个主机。
-
TCP/IP协议中,有两部分信息用来确定一个指定的程序:互联网地址和端口号:其中互联网地址由IP协议使用,而附加的端口地址信息则由传输协议TCP/IP进行解析
-
现在TCP/IP协议族中的主要socket类型为流套接字(使用TCP协议)和数据报套接字(使用UDP协议)
-
一个TCP/IP套接字由一个互联网地址,一个端对端的协议(TCP或UDP)以及一个端口号确定
-
每个端口标识了主机上一个应用程序,实际上,一个端口确定了一个主机上的一个套接字。主机中的多个程序可以同时访问同一个套接字
基本套接字
- 编写TCP客户端程序时,实例化Socket类时,要注意底层的TCP协议只能处理IP协议,如果传入的参数是主机名字而不是IP地址,Socket具体实现的时候会将其解析成相应的地址,若因为某些原因失败,构造函数会抛出IO Exception异常
- TCP协议读写数据时,read()方法在没有可读数据时会阻塞等待,直到有新的数据可读。另外TCP协议并不能确定在read()和write()方法中所发送信息的界限,接收或发送的数据可能被TCP协议分割成多个部分
- 编写TCP服务器端的程序将在accept方法处阻塞,以等待客户端的连接请求,一旦取得连接,便要为每个客户端创建新的Socket实例进行数据通信
- 在 UDP 程序中,创建 DatagramPacket 实例时,如果没有指定远程主机地址和端口,则该实例用来接收数据(尽管可以调用 setXXX()等方法指定),如果指定了远程主机地址和端口,则该实例用来发送数据
- UDP 程序在 receive()方法处阻塞,直到收到一个数据报文或等待超时。由于 UDP 协议是不可靠协议,如果数据报在传输过程中发生丢失,那么程序将会一直阻塞在 receive()方法处,这对客户端来说是肯定不行的,为了避免这个问题,我们在客户端使用 DatagramSocket 类的 setSoTimeout()方法来制定 receive()方法的最长阻塞时间,并指定重发数据报的次数,如果每次阻塞都超时,并且重发次数达到了设置的上限,则关闭客户端
- UDP 服务器为所有通信使用同一套接字,这点与 TCP 服务器不同,TCP 服务器则为每个成功返回的 accept()方法创建一个新的套接字
- 在 UDP 程序中,DatagramSocket 的每一次 receive()调用最多只能接收调用一次 send()方法所发送的数据,而且,不同的 receive()方法调用绝对不会返回同一个 send()方法所发送的额数据
- 在 UDP 套接字编程中,如果 receive()方法在一个缓冲区大小为 n 的 DatagramPscket 实例中调用,而接受队列中的第一个消息长度大于 n,则 receive()方法只返回这条消息的前 n 个字节,超出的其他字节部分将自动被丢弃,而且也没有任何消息丢失的提示。因此,接受者应该提供一个足够大的缓存空间的 DatagramPacket 实例,以完整地存放调用 receive() 方法时应用程序协议所允许的最大长度的消息。一个 DatagramPacket 实例中所运行传输的最大数据量为 65507 个字节,即 UDP 数据报文所能负载的最多数据,因此,使用一个有 65600 字节左右缓存数组的数据总是安全的
小栗子
客户端
public class Client {
public Client() {
}
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.setSoTimeout(3000);
//客户端建立与服务器的连接
socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), 2000), 3000);
System.out.println("已发起服务器连接,并进入后续流程~");
System.out.println("客户端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort());
System.out.println("服务器信息:" + socket.getInetAddress() + " P:" + socket.getPort());
try {
todo(socket);
} catch (Exception var3) {
System.out.println("异常关闭");
}
socket.close();
System.out.println("客户端已退出~");
}
private static void todo(Socket client) throws IOException {
//将输入流包装成缓冲字符流
InputStream in = System.in;
BufferedReader input = new BufferedReader(new InputStreamReader(in));
//获取客户端的输出流并包装成打印流
OutputStream outputStream = client.getOutputStream();
PrintStream socketPrintStream = new PrintStream(outputStream);
//获取客户端的输入流
InputStream inputStream = client.getInputStream();
BufferedReader socketBufferedReader = new BufferedReader(new InputStreamReader(inputStream));
boolean flag = true;
do {
//获取键盘输入流
String str = input.readLine();
//将输入打印给服务器端
socketPrintStream.println(str);
String echo = socketBufferedReader.readLine();
if("bye".equalsIgnoreCase(echo)) {
flag = false;
} else {
System.out.println(echo);
}
} while(flag);
socketPrintStream.close();
socketBufferedReader.close();
}
}
服务器端
public class Server {
public Server() {
}
public static void main(String[] args) throws IOException {
//服务器监听本机的2000断开
ServerSocket server = new ServerSocket(2000);
System.out.println("服务器准备就绪~");
System.out.println("服务器信息:" + server.getInetAddress() + " P:" + server.getLocalPort());
while(true) {
//获取客户端
Socket client = server.accept();
//启动异步线程,处理多客户端
Server.ClientHandler clientHandler = new Server.ClientHandler(client);
clientHandler.start();
}
}
private static class ClientHandler extends Thread {
private Socket socket;
private boolean flag = true;
ClientHandler(Socket socket) {
this.socket = socket;
}
public void run() {
super.run();
System.out.println("新客户端连接:" + this.socket.getInetAddress() + " P:" + this.socket.getPort());
try {
//获取客户端的输出流
PrintStream e = new PrintStream(this.socket.getOutputStream());
BufferedReader socketInput = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
do {
//获取从客户端键盘输入的流,这里是客户端输出到服务器端
String str = socketInput.readLine();
if("bye".equalsIgnoreCase(str)) {
this.flag = false;
e.println("bye");
} else {
System.out.println(str);
e.println("回送:" + str.length());
}
} while(this.flag);
socketInput.close();
e.close();
} catch (Exception var12) {
System.out.println("连接异常断开");
} finally {
try {
this.socket.close();
} catch (IOException var11) {
var11.printStackTrace();
}
}
System.out.println("客户端已退出:" + this.socket.getInetAddress() + " P:" + this.socket.getPort());
}
}
}
报文
- 报文段在TCP/IP协议网络传输过程,起着路由导航作用
- 用以查询各个网络路由网段,IP地址,交换协议等IP数据包
- 报文段充当整个TCP/IP协议数据包的导航路由功能
- 报文在传输过程中会不断地封装成分组,包,帧来传输
- 封装方式就是添加一些控制信息组成的首部,即报文头
Mac地址
Mac-Media Access Control-即媒体访问控制,或称为物理地址,硬件地址,用来定义网络设备的位置。