网络编程套接字(socket)
2026/9/8 16:37:57 网站建设 项目流程

套接字

什么是socket:

所谓的套接字也就是socket,这个socket和我们编写网络程序又有什么关系呢:

socket是传输层给应用层提供的API的集合,socket就相当于是程序使用网络的接口,而网卡是把数据发出去的硬件,socket就相当于是网卡的一个遥控器

我们网络编程主要涉及的就是传输层以及应用层的交互,我们先决定传输层的哪一个协议,是UDP还是TCP,也就是上文所介绍的两种:

后续的各种层是操作系统根据我们代码中的任务来选择的

发送数据的过程:

Java 程序

Java Socket 对象

文件描述符 fd

进程的文件描述符表

操作系统内核中的 Socket 对象

TCP / IP 协议栈

网卡

网络

为什么会有文件描述符表:明明我们是讲socket,为什么会有一个文件描述符表呢,原因就是在Linux中很多东西都被抽象成了“可以像文件一样读写的资源”,所以他们统一通过文件描述符表(fd)来管理,但是我们学习的并不是linux操作系统啊,为什么会有什么个说法,因为在基本上所有的操作系统中,都有这么一个概念,都会让程序拿到一个编号,然后通过这个编号来找到底层系统中的操作以及方法,只不过在linux里面叫做文件描述符表,所以我们可以统一叫做这个来理解,还有就是为什么我们不能直接通过Java代码来操作一切,因为网络、文件、进程这些资源都属于操作系统的重要资源,不能让普通程序随便直接改,所以需要通过一个像文件描述符表的东西进行调度,调度到底层的操作系统的socket

socket的组成:

Socket(网络通信接口,大概念)

├── TCP
│ ├── Socket
│ └── ServerSocket

└── UDP
├── DatagramSocket
└── DatagramPacket

UDP

DatagramSocket = 通信工具
DatagramPacket = 要传输的数据包

DatagramSocket

构造方法作用常见场景
DatagramSocket()创建一个 UDP Socket,端口由系统自动分配客户端常用
DatagramSocket(int port)创建 UDP Socket,并绑定指定端口服务器常用
DatagramSocket(int port, InetAddress laddr)绑定指定端口和本机 IP一台机器有多个网卡/IP 时使用
方法作用
send(DatagramPacket p)发送 UDP 数据包
receive(DatagramPacket p)接收 UDP 数据,并保存到p
close()关闭 Socket,释放网络资源
getLocalPort()获取当前 Socket 绑定的本地端口
getLocalAddress()获取当前 Socket 绑定的本地 IP
isClosed()判断 Socket 是否已经关闭

DatagramPacket

1,接收数据:

new DatagramPacket(byte[] buf, int length)

参数作用
buf用来存放收到的数据
length最多允许接收多少字节

2,发送数据:

new DatagramPacket(
byte[] buf,
int length,
InetAddress address,
int port
)

参数作用
buf要发送的数据
length要发送多少字节
address对方的 IP 地址
port对方的端口号


回显服务器例子

注意一

为什么服务器要指定端口,而客户端不需要,因为一台服务器是用来服务很多的客户端的,指定端口是让客户端知道并且只传入到那一个端口里面去,但是客户端有很多,就得系统随机,并且有些同学很容易将一个概念混淆,就是觉得客户端的端口和服务器的端口得一致才能对接上,实际是不是的:这里也会涉及前文讲到的五原组

我们只要明确了这五个就行,不需要一致

不是说需要五元组吗,那为什么服务器里面好像少了一个自己的ip:因为系统会根据客户端的数据进行匹配

比如客户端发送:
目标 IP = 192.168.1.10
目标端口 = 9090
协议 = UDP

到我们电脑之后,系统看到的信息就是

目的 IP:192.168.1.10
目的端口:9090
协议:UDP

假设你服务器中写到:

new DatagramSocket(9090);

也就相当于是:

监听:所有本机 IP 的 UDP 9090

然后进行匹配,最终就可以得出服务器自己的ip

所以说五元组往往是客户端和服务器两边的信息共同组成的!!!!!!!!

注意二

局域网 ≈ 一个小区
IP ≈ 楼号 / 房屋地址
端口 ≈ 房间号

所以为什么会出现端口号冲突,也就是我们现在写的程序是在自己的电脑上面,所以ip都是127.0.0.1,所以如果服务器和客户端的端口号也一致的情况下,就会产生端口号冲突,也就是走到了一个小区的一个楼层中的一个房间,那我们电脑上有那么多的应用程序,为什么不会造成冲突呢,因为系统会给客户端的端口自动分配,所以我们程序里面的客户端的代码也没有指定端口号,简单介绍一下电脑里的ip

不同 IP 的来源不一样:

127.0.0.1:操作系统按照网络协议标准预留的,本机固定使用。

192.168.x.x这种局域网 IP:一般是你家路由器通过 DHCP 自动分配给电脑。

公网 IP:通常由运营商分配给你的路由器/网络出口。

VPN 的 IP:由 VPN 软件或 VPN 服务器分配。

所以我们的应用程序的ip都是通过操作系统自己分配的,比如你点开来一个网站,系统会识别这个请求是从WiFi网卡发出去的,所以会分配192开头的那个

具体代码

package network; //服务器 import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.nio.charset.StandardCharsets; public class UdpEchoServer { //首先创建一个socket对象 DatagramSocket serverSocket=null; //通过构造方法来完整 //创建一个服务器对象,需要指定端口 public UdpEchoServer(int port) throws Exception{ serverSocket=new DatagramSocket(port); } //开始方法 public void start() throws IOException { System.out.println("服务器启动了"); while (true) { //先得创建一个datagrampacket对象,这个对象是传输数据的基本单位,就相当于你赶海一人拿一个桶是一样的 DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024); //接收请求 serverSocket.receive(requestPacket); //将请求转为字符串 String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); //根据请求计算响应 String response = process(request); //将响应封装到datagrampacket对象中,并且返回 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); //requestPacket.getSocketAddress()不仅拿到了ip也拿到了端口号 serverSocket.send(responsePacket); //打印日志 System.out.printf("客户端:%s 请求:%s 响应:%s\n", requestPacket.getSocketAddress(), request, response); } } private String process(String request) { //因为回显,所以就原路返回就行 return request; } public static void main(String[] args) throws Exception { UdpEchoServer udpEchoServer=new UdpEchoServer(9090); udpEchoServer.start(); } }
package network; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.util.Scanner; //客户端 public class UdpEchoClient { //也是先创建一个socket对象 DatagramSocket clientSocket=null; //需要给定服务器的ip以及端口 private String serverIp; private int serverPort; public UdpEchoClient(String serverIp,int serverPort) throws SocketException { clientSocket=new DatagramSocket(); this.serverIp=serverIp; this.serverPort=serverPort; } public void start() throws Exception{ while(true){ Scanner scanner=new Scanner(System.in); System.out.println("请输入要发送的内容:"); String request=scanner.nextLine(); if (request.equals("exit")){ break; } DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort); clientSocket.send(requestPacket); DatagramPacket responsePacket=new DatagramPacket(new byte[1024],1024); clientSocket.receive(responsePacket); //将服务器返回的数据打印出来 String response = new String(responsePacket.getData(), 0, responsePacket.getLength()); // 4. 显示响应. System.out.println(response); } } public static void main(String[] args) throws Exception { UdpEchoClient udpEchoClient=new UdpEchoClient("127.0.0.1",9090); udpEchoClient.start(); } }

注意三

别人要访问我的服务器:

本机测试用 127.0.0.1,想让互联网中的别人访问,通常需要“公网 IP + 开放端口”,云服务器就是最常见的实现方式。

127.0.0.1表示当前这台电脑自己。

查字典案例

我们刚刚通过一个回显的案例来初步了解了一下UDP的构造以及相关socket的api,那我们现在再用一个查字典的案例来进一步了解一下

注意一

服务器以及客户端的主要流程

服务器大体

在while循环里面

第一步:读取请求并解析

(因为我们这里是需要返回一个字符串,所以转为字符串格式)

第二步:根据请求计算相应

(这里以回显为例子,所以只需要原路返回就行)

第三步:把相应返回给客户端

客户端大体

第一步:在控制台读取输入

第二步:构造请求发送给服务器

第三步:接收服务器发来的响应

第四步:根据返回来的响应进行显示结果

所以我们可以总结一下:我们写服务器以及客户端,只需要跟着上面的步骤,在发送和接收的时候,来书写对的DatagramPacket对象,使用正确的构造方法

注意二

为什么可以这么写:因为其实现在市面的大部分背单词软件都是这么写的,我们也可以上网复制中英文对应关系来使我们的回答更加全面

具体代码

package network; import java.io.IOException; import java.net.DatagramSocket; import java.util.HashMap; import java.util.Map; public class UdpDictServer extends UdpEchoServer{ //因为我们是一个查字典,所以我们用到map来根据key来查找values Map<String,String> map=new HashMap<String, String>(); //重写方法 public UdpDictServer(int port) throws Exception { super(port); //在这里传入对应的中英文关系 map.put("apple","苹果"); map.put("banana","香蕉"); map.put("cat","猫"); map.put("dog","狗"); } @Override public String process(String request) { //request 是客户端传来的查询内容,是 key;getOrDefault() 返回的是这个 key 对应的 value。 return map.getOrDefault(request,"查无此词"); } public static void main(String[] args) throws Exception { UdpDictServer server = new UdpDictServer(9090); server.start(); } }

(因为这里的客户端和前面的回显服务器一样,都是发送,包装成String,然后发送回去,所以我们还是用的之前的客户端)

TCP

ServerSocketSocket

客户端 服务器

Socket

│ 请求连接
├──────────────────────────→ ServerSocket
│ │
│ accept()
│ │
│ ↓
│ Socket
│ │
└──────────── TCP连接 ────────────┘

整体的关系差不多就是这样,serversocket负责将将请求引进家门(处理客户端的连接,所以只会在服务器中写到),而socket像是专门负责业务的专员(在客户端以及服务器里都会用到)

回显服务器例子

TCP的短长连接

短连接:就是连接一次,通信完就断,需要多次进行连接和断开,会影响效率,但是不会一直占着服务器资源

长连接:连接一次,可以通信很多次之后关闭,就像我们程序中写到的while循环,不用老是创建和关闭,所以效率会高,但是会长时间占着资源,可能一直占着一个线程

注意一

accept建立连接:

因为在我们没有写多线程的时候,就只有一个socket服务员,也就是只能一个线程在进行服务,后面的就得等着,所以我们通过多线程的形式,可以实现多客户端

注意二

flush:

在我们用printwriter(这个内置了缓冲区)进行写入的时候,会先将数据写进缓冲区里,数据满了之后才会将数据传输出去,所以我们得写flush,写入一些数据就将他们传输过去

TCP和UDP的有无连接

TCP的Socket socket = serverSocket.accept()这个方法,会进行连接,也就是会拿到这个socket的信息并且记录下来,所以后续就会根据这个信息给特定的socket进行交互,但是udp不会记录对方socket信息

注意三

具体代码

package network; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; public class TcpEchoClient { Socket socket=null; TcpEchoClient(String serverName,int serverPort) throws IOException { socket=new Socket(serverName,serverPort); } public void start() throws IOException { System.out.println("client start!"); //依旧是先创建字节流的输入输出来处理数据 try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) { Scanner scanner = new Scanner(System.in); Scanner scannerNetwork = new Scanner(inputStream); PrintWriter printWriter = new PrintWriter(outputStream); while (true) { //先读取数据 System.out.println("client:请输入数据"); String request = scanner.next(); //再将读取的数据传给服务器 printWriter.println(request); printWriter.flush(); //最后读取服务器的相应 if (!scannerNetwork.hasNext()) { break; } String response = scannerNetwork.next(); //最后打印出来 System.out.println("server: " + response); } }catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090); client.start(); } }
package network; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TcpEchoServer { //先创建一个ServerSocket对象 ServerSocket serverSocket = null; public TcpEchoServer(int port) throws Exception { serverSocket = new ServerSocket(port); } //开始方法 public void start() throws IOException { //先建立连接,然后由Socket进行处理 //注意一 ExecutorService executorService = Executors.newCachedThreadPool(); while (true) { Socket socket = serverSocket.accept(); executorService.submit(() -> { try { processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); } } private void processConnection(Socket socket) throws IOException { System.out.printf("[%s:%d] 客户端上线!\n", socket.getInetAddress(), socket.getPort()); //tcp是面向字节流的 try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) { //因为tcp是面向字节流的,所以我们定义了InputStream和OutputStream,但是我们输入是一个字符串,所以我们会 //继续用到scanner来读和PrintWriter来写,并且PrintWriter可以将我们输入的字符串转为字节传输出去 Scanner scanner = new Scanner(inputStream); PrintWriter printWriter = new PrintWriter(outputStream); while (true) { if (!scanner.hasNext()) { // 客户端关闭了连接 System.out.printf("[%s:%d] 客户端下线!\n", socket.getInetAddress(), socket.getPort()); // 假设这里有一系列的逻辑呢?? break; } //先接收数据 String request = scanner.next(); //再根据请求计算相应 String response = process(request); //最后发送相应给客户端 printWriter.println(response); //注意二 printWriter.flush(); System.out.printf("[%s:%d] 客户端请求: %s\n", socket.getInetAddress(), socket.getPort(), request); } } catch (Exception e) { e.printStackTrace(); } finally { socket.close(); } } public String process(String request) { return request; } public static void main(String[] args) throws Exception { TcpEchoServer tcpEchoServer = new TcpEchoServer(9090); tcpEchoServer.start(); } }

查字典案例

package network; import java.util.HashMap; import java.util.Map; public class TcpDictServer extends TcpEchoServer{ Map<String,String> map=new HashMap<String, String>(); public TcpDictServer(int port) throws Exception { super(port); map.put("apple","苹果"); map.put("banana","香蕉"); map.put("cat","猫"); map.put("dog","狗"); } @Override public String process(String request) { //request 是客户端传来的查询内容,是 key;getOrDefault() 返回的是这个 key 对应的 value。 return map.getOrDefault(request,"查无此词"); } public static void main(String[] args) throws Exception { TcpDictServer server = new TcpDictServer(9090); server.start(); } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询