Java网络编程:Socket基础

本文介绍了Java网络编程的基础,讲解了计算机网络模型、TCP与UDP协议的区别、TCP的三次握手与四次挥手,以及IP地址和端口的概念。通过Socket类,详细阐述了如何在Java中进行TCP和UDP通信,提供了示例代码帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
Java拥有强大的网络编程能力,JDK中对网络编程提供的API也大大地降低了开发者进行网络编程的难度。

计算机网络模型

在讲Java网络编程之前,我们需要复习一下计算机网络的一些基础知识。

网络模型一般是指OSI七层参考模型和TCP/IP五层参考模型。这两个模型在网络中应用最为广泛。

OSI参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系,一般称为OSI参考模型或七层模型。

TCP/IP是传输控制协议/网络协议模型(Transmission Control Protocol/Internet Protocol),相比于OSI参考模型,它少了表示层和会话层。

在这里插入图片描述

应用层:应用层是最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务,其功能是直接向用户提供服务,完成用户希望在网络上完成的各种工作。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,TELNET等。

表示层:是OSI模型的第六层,它对来自应用层的命令和数据进行解释,对各种语法赋予相应的含义,并按照一定的格式传送给会话层。其主要功能是“处理用户信息的表示问题,如编码、数据格式转换和加密解密”等。

会话层:是OSI模型的第五层,是用户应用程序和网络之间的接口,主要任务是:向两个实体的表示层提供建立和使用连接的方法。将不同实体之间的表示层的连接称为会话。因此会话层的任务就是组织和协调两个会话进程之间的通信,并对数据交换进行管理。

传输层:OSI下3层的主要任务是数据通信,上3层的任务是数据处理。而传输层(Transport Layer)是OSI模型的第四层。因此该层是通信子网和资源子网的接口和桥梁,起到承上启下的作用。

该层的主要任务是:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。传输层的作用是向高层屏蔽下层数据通信的细节,即向用户透明地传送报文。该层常见的协议:TCP/IP中的TCP协议、Novell网络中的SPX协议和微软的NetBIOS/NetBEUI协议。

网络层:是OSI模型的第三层,它是OSI参考模型中最复杂的一层,也是通信子网的最高一层。它在下两层的基础上向资源子网提供服务。其主要任务是:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。该层控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接。具体地说,数据链路层的数据在这一层被转换为数据包,然后通过路径选择、分段组合、顺序、进/出路由等控制,将信息从一个网络设备传送到另一个网络设备。

数据链路层:是OSI模型的第二层,负责建立和管理节点间的链路。该层的主要功能是:通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。
在计算机网络中由于各种干扰的存在,物理链路是不可靠的。因此,这一层的主要功能是在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路,即提供可靠的通过物理介质传输数据的方法。

物理层:在OSI参考模型中,物理层(Physical Layer)是参考模型的最低层,也是OSI模型的第一层。物理层的主要功能是:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。

物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉传输介质和物理设备的差异。使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的“比特流”没有发生变化,对传送的比特流来说,这个电路好像是看不见的。

在这里插入图片描述

TCP与UDP协议

传输层是网络模型中承上启下的一层,向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。传输层的协议主要是TCP协议和UDP协议。

TCP协议

TCP是Transmission Control Protocol的简称,是一种面向连接的保证可靠的基于字节流传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。

TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:
(1)基于流的方式;
(2)面向连接;
(3)可靠通信方式;
(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;
(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。

为满足TCP协议的这些特点,TCP协议做了如下的规定:

①数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
②到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
③超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
④滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只会允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
⑤失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
⑥重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
⑦数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段,导致对端超时并重发。

UDP协议

UDP是User Datagram Protocol的简称,是一种无连接、不可靠的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。

在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
主要特点如下:

(1)由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。

(2)UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包而言UDP的额外开销很小。

(3)吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。

(4)UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。

TCP与UDP协议对比

(1)TCP 是面向连接的传输控制协议,而UDP 提供了无连接的数据报服务;

(2)TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;

(3)UDP 在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;

(4)UDP 具有较好的实时性,工作效率较 TCP 协议高;

(5)UDP 段结构比 TCP 的段结构简单,因此网络开销也小;

(6)TCP 协议可以保证接收端毫无差错地接收到发送端发出的字节流,为应用程序提供可靠的通信服务。对可靠性要求高的通信系统往往使用 TCP 传输数据。

TCP协议中的三次握手、四次挥手

建立起一个TCP连接需要先经过“三次握手”:

第一次握手,客户端发送syn包(syn = j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手,服务器收到syn包,必须确认客户的syn(ack=j+1),同时自己也发生一个syn包(syn=k),即发送syn+ack包,此时服务器进入SYN_RECV状态;
第三次握手,客户端收到服务器的syn+ack包,向服务器发送确认包ack(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

三次握手过程中不包含数据,只是为了确认客户端和服务器的发送和接收功能正常,三次握手完毕后,客户端和服务器才正式开始传送数据,理想情况下,TCP连接一旦建立,在通信双方任意一方主动关闭前,TCP连接都将一直保持下去。

注意:SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。

关闭连接需要经过“四次挥手”,刚开始双方都处于 establised 状态,假如是客户端先发起关闭请求,则:

1、第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。

2、第二次挥手:服务端在收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。

3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端将处于 LAST_ACK 的状态。

4、第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号+ 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端已收到自己的 ACK 报文之后才会进入 CLOSED 状态。

5、服务端在收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。

IP地址与端口

IP地址是用来标志网络中的一个通信实体的地址,通信实体可以是计算机、路由器等。

计算机网络中的每一个通信实体的IP地址都是独一无二的,在进行通信是,通信实体需要知道对方的地址才能准确地将消息发送过去。

在Java中有一个IP地址地抽象类InetAddress,它继承Object类,实现了Serializable ,直接子类有Inet4Address和Inet6Address,分别对应IPv4地址和IPv6地址。

IP 地址是 IP 使用的 32 位(IPv4)或 128 位(IPv6)无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。InetAddress 的实例包含 IP 地址,还可能包含相应的主机名(取决于它是否用主机名构造或者是否已执行反向主机名解析)。

特殊的IP地址:127.0.0.1是一个特殊的IP地址,它表示本机IP地址,也叫回环地址;192.168.0.0~192.168.255.255是私有地址又称局域网地址,属于非注册地址,专门为组织机构内部使用,不能用于Internet。

例如:

package com.test;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class IPTest {

	public static void main(String[] args) {
		//使用getLocalHost方法创建InetAddress对象
		InetAddress addr = null;
		try {
			addr = InetAddress.getLocalHost();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(addr.getHostAddress()); //返回:192.168.1.110
		System.out.println(addr.getHostName()); //输出计算机名
		
		//根据域名得到InetAddress对象
		try {
			addr = InetAddress.getByName("www.163.com");
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(addr.getHostAddress()); //返回 163服务器的ip:61.135.253.15
		System.out.println(addr.getHostName()); //输出:www.163.com
	}

}

运行结果:
在这里插入图片描述

注意InetAddress.getLocalHost()方法返回的地址不是127.0.0.1,而是本计算机在私有网段中的地址。

IP地址可以用来标志一台计算机,但是每台计算机将会运行多个应用程序,我们可以使用端口号来标志这些应用程序,不同应用程序同一个协议下(TCP或UDP)的端口不能冲突。

一个IP地址的端口通过16bit进行编号,最多可以有65536个端口(TCP协议有65536个,UDP协议也有65536个)。端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535。

端口号也是有限的,需要进行分配,一般公认端口范围是 0—1023,比如80端口分配给WWW,21端口分配给FTP;注册端口范围是 1024—49151 分配给用户进程或应用程序;动态/私有端口范围是 49152—65535。

在Windows下可以通过命令行来看到本机运行程序的IP及端口号:

在这里插入图片描述

在Java中封装了一个IP套接字地址类:InetSocketAddress类,实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名。如果解析失败,则该地址将被视为未解析 地址,但是其在某些情形下仍然可以使用,比如通过代理连接。

它提供不可变对象,供套接字用于绑定、连接或用作返回值。

例如:

package com.test;

import java.net.InetSocketAddress;

public class IPTest {

	public static void main(String[] args) {
		InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",8080);
		InetSocketAddress socketAddress2 = new InetSocketAddress("localhost",9000);
		System.out.println(socketAddress.getHostName());
		System.out.println(socketAddress2.getAddress());
		System.out.println(socketAddress.getPort());
	}

}

运行结果:

在这里插入图片描述

Socket

Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信的五种必须信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远程主机的IP地址、远程进程的协议端口。

套接字(socket)是一个抽象层,传输层连接的端点叫做套接字(socket)。应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

JDK中也封装了Socket,TCP协议的实现是Socket和ServerSocket,UDP协议的实现是DatagramSocket。

Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
例如:

服务端socket示例:

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSocketTest {
	public static void main(String[] args) throws Exception {
		// 监听指定的端口
		int port = 55533;
		ServerSocket server = new ServerSocket(port);
		// server将一直等待连接的到来
		System.out.println("server将一直等待连接的到来");
		Socket socket = server.accept();
		// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
		InputStream inputStream = socket.getInputStream();
		byte[] bytes = new byte[1024];
		int len;
		StringBuilder sb = new StringBuilder();
		while ((len = inputStream.read(bytes)) != -1) {
			// 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
			sb.append(new String(bytes, 0, len, "UTF-8"));
		}
		System.out.println("get message from client: " + sb);
		inputStream.close();
		socket.close();
		server.close();
	}
}

客户端socket示例:

import java.io.OutputStream;
import java.net.Socket;

public class ClientSocketTest {
	public static void main(String args[]) throws Exception {
		// 要连接的服务端IP地址和端口
		String host = "127.0.0.1";
		int port = 55533;
		// 与服务端建立连接
		Socket socket = new Socket(host, port);
		// 建立连接后获得输出流
		OutputStream outputStream = socket.getOutputStream();
		String message = "你好 yiwangzhibujian";
		socket.getOutputStream().write(message.getBytes("UTF-8"));
		outputStream.close();
		socket.close();
	}
}

运行结果:

在这里插入图片描述

这是一个客户端单向发送消息到服务器端的demo,且服务器端是只等待接收一次连接请求。从示例代码中可以看到创建socket需要指的IP地址和端口,发送和接收消息是需要通过输入输出流的。

Java中UDP协议的socket实现是DatagramSocket,并且不区分客户端和服务端。

例如:

服务端socket示例:

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class DatagramSocketServerTest {
	public static void main(String[] args) {
		try {
			// 要接收的报文
			byte[] bytes = new byte[1024];
			DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
			// 创建socket并指定端口
			DatagramSocket socket = new DatagramSocket(8088);
			System.out.println("服务器端正在等待客户端连接...");
			// 接收socket客户端发送的数据。如果未收到会一致阻塞
			socket.receive(packet);
			String receiveMsg = new String(packet.getData(), 0, packet.getLength());
			System.out.println(packet.getLength());
			System.out.println(receiveMsg);
			// 关闭socket
			socket.close();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

}

客户端socket示例:

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class DatagramSocketClientTest {
	public static void main(String[] args) {
		try {
			// 要发送的消息
			String sendMsg = "客户端发送的消息";
			// 获取服务器的地址
			InetAddress addr = InetAddress.getByName("localhost");
			// 创建packet包对象,封装要发送的包数据和服务器地址和端口号
			DatagramPacket packet = new DatagramPacket(sendMsg.getBytes(), sendMsg.getBytes().length, addr, 8088);
			// 创建Socket对象
			DatagramSocket socket = new DatagramSocket();
			// 发送消息到服务器
			socket.send(packet);
			// 关闭socket
			socket.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

运行结果:

在这里插入图片描述

这也是一个客户端单向发送消息到服务器端的demo,且服务器端是只等待接收一次连接请求。从示例代码中可以看到使用DatagramSocket进行通信需要将消息、消息长度和对方地址、端口号封装在DatagramPacket中,DatagramSocket发送出的每一个DatagramPacket都包含了IP地址和端口号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值