跳到主要内容
Java 网络编程:TCP/UDP 协议与 Socket 实战 | 极客日志
Java java
Java 网络编程:TCP/UDP 协议与 Socket 实战 介绍 Java 网络编程基础,涵盖 TCP/UDP 协议原理、Socket 通信机制、Echo 程序实现、多客户端通信模型及 MINA 框架应用。内容包含服务端与客户端代码示例,解析三次握手、数据包结构及线程池处理逻辑,适合初学者理解网络 IO 流程。
CodeArtist 发布于 2026/3/29 更新于 2026/5/26 27 浏览Java 网络编程基础
一、基本概念
网络通信涉及协议与接口等基础概念。
二、网络编程 TCP 协议
2.1 简单介绍
TCP/IP 协议:传输控制协议/因特网互联协议。网络层的 IP 协议 + 传输层的 TCP 协议组成。
七层协议模型参考 OSI 标准。
程序开发结构:
C/S(客户端/服务器):需要安装的软件,比如 QQ、微信。
B/S(浏览器/服务器):Java 适合此类开发。
三次握手流程:
A -> B: A 告诉 B 具体的消息。
B -> A: B 告诉 A 确认收到消息。
A -> B: A 再次告诉 B,确认收到 B 发送回来的确认消息。
TCP 特点:发送确保对方收到。
UDP 特点:只管发送,收不收到它不管。比如收音机,我发了消息,对象不接收,消息过去了,我可不管。
发送任何一条消息都应该包含下面几个参数:协议类型、源 IP、目标 IP、源端口、目标端口、帧序号、帧数据。
2.2 TCP Socket
服务器套接字:ServerSocket
客户端套接字:Socket
Socket 是网络驱动层提供给应用程序编程的接口和一种机制。
为什么 Socket 是接口?
一端发送到另一端需要硬件设备(网卡),那如何把数据写进网卡?我们知道硬件对接驱动程序,那么程序中数据怎么对接到驱动程序中呢?
数据 --> 放进 Socket --> 对应到驱动层 --> 对应到网卡 --> 通过网线传递出去。
所以说 Socket 是一个接口和机制,由网络驱动层提供。
数据发送过程:
应用程序产生 Socket 对象。
应用程序调用 bind,把 Socket 通知给驱动。
应用程序将'内容'传送给 Socket。
驱动从 Socket 取'内容',利用网卡发出去。
数据接收过程:
应用程序产生 Socket。
应用程序利用 bind 把 Socket 告诉驱动。
驱动拿到网卡中给的 IP+Port 拿到数据,把数据存入 Socket。
应用程序从 Socket 取数据。
三、TCP 实现 ECHO 程序
一个客户端、一个服务端,服务端代码先启,客户端后启。
功能:
服务端接收客户端发送的消息。
服务端将接收的消息用"echo:"拼接后返回给客户端。
客户端接收服务端返回的消息。
注意:
先写服务器端代码,核心:Socket socket = server.accept(); 如果监听不到客户端连接,会一直阻塞在这里。
如果监听到,就会继续往下执行。
无论客户端、服务端读取数据 String info = br.readLine(); 一直读不到,那么也会阻塞住。
如果用的缓冲流,记得 flush() 一下。
3.1 服务端
package com.network.echo;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketDemo {
{
{
( );
System.out.println( );
server.accept();
(socket != ) {
System.out.println( );
( (socket.getInputStream()));
br.readLine();
System.out.println( + info);
( (socket.getOutputStream()));
ps.println( + info);
ps.flush();
ps.close();
br.close();
}
} (IOException e) {
(e);
}
}
}
public
static
void
main
(String[] args)
try
ServerSocket
server
=
new
ServerSocket
6666
"服务器已经启动成功,等待客户端连接......"
Socket
socket
=
if
null
"客户端连接成功,开始处理数据......"
BufferedReader
br
=
new
BufferedReader
new
InputStreamReader
String
info
=
"服务器读取到客户端数据是:"
PrintStream
ps
=
new
PrintStream
new
BufferedOutputStream
"echo: "
catch
throw
new
RuntimeException
3.2 客户端 package com.network.echo;
import java.io.*;
import java.net.Socket;
public class ClientSocketDemo {
public static void main (String[] args) {
try {
Socket socket = new Socket ("localhost" , 6666 );
PrintStream ps = new PrintStream (new BufferedOutputStream (socket.getOutputStream()));
ps.println("hello world, I am java" );
ps.flush();
BufferedReader br = new BufferedReader (new InputStreamReader (socket.getInputStream()));
String info = br.readLine();
System.out.println("我从服务器接收的数据是-->" + info);
br.close();
ps.close();
} catch (IOException e) {
throw new RuntimeException (e);
}
}
}
四、服务端与多客户端通信 一个服务器处理多个客户端,服务端启动一次,客户端可以启动多次。
4.1 服务器端代码 主程序用来监听客户端请求,来一个客户端请求开一个线程。
package com.network.multi;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiServerDemo {
public static void main (String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3 );
try {
ServerSocket server = new ServerSocket (6666 );
while (true ) {
Socket socket = server.accept();
System.out.println("客户端连接成功:" + socket.getInetAddress().getHostAddress());
es.execute(new ClientThread (socket));
}
} catch (IOException e) {
throw new RuntimeException (e);
}
}
}
class ClientThread implements Runnable {
private Socket socket;
public ClientThread (Socket socket) {
this .socket = socket;
}
@Override
public void run () {
try {
BufferedReader br = new BufferedReader (new InputStreamReader (socket.getInputStream()));
PrintStream ps = new PrintStream (new BufferedOutputStream (socket.getOutputStream()));
String info = br.readLine();
System.out.println(info);
ps.println("echo:" + info);
ps.flush();
ps.close();
br.close();
} catch (IOException e) {
throw new RuntimeException (e);
}
}
}
4.2 客户端代码 package com.network.multi;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class MultiClientDemo {
public static void main (String[] args) {
Scanner sc = new Scanner (System.in);
try {
Socket socket = new Socket ("localhost" , 6666 );
PrintStream ps = new PrintStream (new BufferedOutputStream (socket.getOutputStream()));
BufferedReader br = new BufferedReader (new InputStreamReader (socket.getInputStream()));
System.out.print("请输入内容:" );
String s = sc.nextLine();
ps.println(s);
ps.flush();
String info = br.readLine();
System.out.println(info);
ps.close();
br.close();
} catch (IOException e) {
throw new RuntimeException (e);
}
}
}
五、多客户端之间的通信
5.1 概念 客户端之间的通信,需要通过服务器中转。
A 客户端发送消息到 B 客户端,需要先向服务器发消息,再由服务器发消息到 B 客户端。
发送的细节,需要一个数据包包含:我是谁、他是谁、消息、消息类型。
5.2 数据包类 package com.network.commutation;
import java.io.Serializable;
public class Message implements Serializable {
private String from;
private String to;
private String info;
private int type;
public Message (String from, String to, String info, int type) {
this .from = from;
this .to = to;
this .info = info;
this .type = type;
}
public Message () {}
public String getFrom () { return from; }
public void setFrom (String from) { this .from = from; }
public String getTo () { return to; }
public void setTo (String to) { this .to = to; }
public int getType () { return type; }
public void setType (int type) { this .type = type; }
public String getInfo () { return info; }
public void setInfo (String info) { this .info = info; }
@Override
public String toString () {
return "Message{" + "from='" + from + '\'' + ", to='" + to + '\'' + ", info='" + info + '\'' + ", type=" + type + '}' ;
}
}
5.3 数据类型类 package com.network.commutation;
public final class MessageType {
public static final int LOGIN_TYPE = 0x1 ;
public static final int SEND_TYPE = 0x2 ;
}
5.4 服务器端及其内部的客户端处理线程 package com.network.commutation;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
public static void main (String[] args) {
Vector<ClientThread> vector = new Vector <>();
ExecutorService es = Executors.newFixedThreadPool(5 );
try {
ServerSocket server = new ServerSocket (8888 );
System.out.println("服务器已启动,等待客户端连接..." );
while (true ) {
Socket socket = server.accept();
es.execute(new ClientThread (socket, vector));
}
} catch (IOException e) {
throw new RuntimeException (e);
}
}
}
class ClientThread implements Runnable {
private String name;
private Socket socket;
private Vector<ClientThread> vector;
private ObjectInputStream ois;
private ObjectOutputStream oos;
private boolean flag = true ;
public ClientThread (Socket socket, Vector<ClientThread> vector) {
this .socket = socket;
this .vector = vector;
this .vector.add(this );
}
@Override
public void run () {
try {
ois = new ObjectInputStream (socket.getInputStream());
oos = new ObjectOutputStream (socket.getOutputStream());
System.out.println("客户端 " + socket.getInetAddress().getHostAddress() + " 连接成功..." );
while (flag) {
Message message = (Message) ois.readObject();
int type = message.getType();
switch (type) {
case MessageType.LOGIN_TYPE:
name = message.getFrom();
message.setInfo("欢迎您," );
oos.writeObject(message);
break ;
case MessageType.SEND_TYPE:
String to = message.getTo();
for (ClientThread ct : vector) {
if (to.equals(ct.name) && ct != this ) {
ct.oos.writeObject(message);
break ;
}
}
break ;
}
}
ois.close();
oos.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
5.5 客户端及其内部的读取消息的线程 package com.network.commutation;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Client {
public static void main (String[] args) {
Scanner sc = new Scanner (System.in);
ExecutorService es = Executors.newSingleThreadExecutor();
try {
Socket socket = new Socket ("localhost" , 8888 );
System.out.println("服务器连接成功..." );
ObjectOutputStream oos = new ObjectOutputStream (socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream (socket.getInputStream());
System.out.print("请输入用户名:" );
String name = sc.nextLine();
Message msg = new Message (name, null , null , MessageType.LOGIN_TYPE);
oos.writeObject(msg);
msg = (Message) ois.readObject();
System.out.println(msg.getInfo() + msg.getFrom());
es.execute(new ReadThread (ois));
boolean flag = true ;
while (flag) {
msg = new Message ();
msg.setFrom(name);
System.out.print("to: " );
msg.setTo(sc.nextLine());
System.out.print("info: " );
msg.setInfo(sc.nextLine());
msg.setType(MessageType.SEND_TYPE);
oos.writeObject(msg);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class ReadThread implements Runnable {
private ObjectInputStream ois;
private Boolean flag = true ;
public ReadThread (ObjectInputStream ois) {
this .ois = ois;
}
@Override
public void run () {
try {
while (flag) {
Message msg = (Message) ois.readObject();
System.out.println("[" + msg.getFrom() + "] 对我说:" + msg.getInfo());
}
if (ois != null ) {
ois.close();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException (e);
}
}
}
5.6 代码原理分析 客户端 A 、客户端 B 都要连接服务器 。一旦连接上服务器后,服务器中会产生各个客户端对应的客户端处理线程,比如客户端 A 处理线程 、客户端 B 处理线程 ,当然服务器中会用一个集合来存储这些客户端处理线程 。
我们做两件事:客户端连接服务器、客户端之间发消息。
5.6.1 客户端连接服务器 场景:客户端连接上服务器后,会在客户端上要求输入一个用户名,这个名字以后就代表客户端的唯一名字,然后客户端会将名字发送给服务器,服务器收到消息后会返回"欢迎你,xxx"给客户端,客户端拿到消息后会打印到控制台上。
5.6.2 客户端之间通信 场景:客户端都成功连上服务器后,客户端 A 给客户端 B 发送一个消息。客户端 A 会将消息先转发到服务器上,然后服务器再转发到客户端 B 上。
我们好奇这个转发的过程是如何实现的。
用到上面的原理,一个客户端连接上服务器后,服务器中会产生客户端对应的客户端处理线程,现在我们的客户端 A 和客户端 B 都连接上服务器了,那么在服务器中会创建客户端处理线程 A、客户端处理线程 B。
这个时候,我们可以理解,客户端处理线程 A 就代替了客户端 A 的工作,客户端处理线程 B 就代替了 B 的工作,虽然客户端 A 和客户端 B 无法直接通信,但是客户端处理线程 A 和客户端处理线程 B 都在服务器中,所以可以通信。
当然,我们的客户端中不能如此简单,人家服务器给我发消息,我也得读取。一般读取的操作,单独写一个读取线程,不停的读取;
当然,客户端也可以不停的给其他人发消息,可以直接在客户端的主线程中操作。
六、网络编程 UDP 协议 要求接收者先等着。
在网络上以任意可能的路径传到目的地,但是到达目的地的时间、内容的正确性无法保证,每个传输的数据报必须在 64kb 之间。
DatagramPacket: 数据报包类
DatagramSocket: 接收和发送数据报的套接字
客户端 package com.network.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPClient {
public static void main (String[] args) {
byte [] bytes = new byte [1024 ];
DatagramPacket dp = new DatagramPacket (bytes, bytes.length);
try {
DatagramSocket socket = new DatagramSocket (8000 );
System.out.println("客户端正在接收..." );
socket.receive(dp);
String info = new String (dp.getData(), 0 , dp.getLength());
System.out.println(info);
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException (e);
}
}
}
服务器 package com.network.udp;
import java.io.IOException;
import java.net.*;
public class UDPServer {
public static void main (String[] args) {
String s = "good good study, 天天 up" ;
byte [] bytes = s.getBytes();
try {
DatagramPacket dp = new DatagramPacket (bytes, 0 , bytes.length, InetAddress.getByName("localhost" ), 8000 );
DatagramSocket ds = new DatagramSocket (9000 );
ds.send(dp);
} catch (UnknownHostException | SocketException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException (e);
}
}
}
七、URL 网络上,一个 URL 就是一个资源。
抽象类 URLConnection 是所有类的超类,表示程序、URL 之间的通信连接。
package url;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class URLDemo {
public static void main (String[] args) {
try {
URL url = new URL ("https://p1.meituan.net/biztone/1730f0baefe95984e632a52590f0b446393917.jpg" );
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
BufferedInputStream bis = new BufferedInputStream (conn.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream ("/Users/mac/D/hello.png" ));
byte [] bytes = new byte [1024 ];
int len = -1 ;
while ((len = bis.read(bytes)) != -1 ) {
bos.write(bytes, 0 , len);
}
System.out.println("下载成功!" );
bis.close();
bos.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
八、MINA 框架
8.1 最小核心依赖包
<dependency >
<groupId > org.apache.mina</groupId >
<artifactId > mina-core</artifactId >
<version > 2.1.3</version >
</dependency >
<dependency >
<groupId > org.slf4j</groupId >
<artifactId > slf4j-simple</artifactId >
<version > 1.7.35</version >
</dependency >
8.2 服务器端 package com.mina;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Server {
public static void main (String[] args) {
NioSocketAcceptor acceptor = new NioSocketAcceptor ();
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
chain.addLast("myChain" , new ProtocolCodecFilter (new TextLineCodecFactory ()));
acceptor.setHandler(new MinaServerHandler ());
try {
int port = 9999 ;
acceptor.bind(new InetSocketAddress (port));
} catch (IOException e) {
throw new RuntimeException (e);
}
System.out.println("Mina Server is started..." );
}
}
package com.mina;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class MinaServerHandler extends IoHandlerAdapter {
@Override
public void sessionOpened (IoSession session) throws Exception {
super .sessionOpened(session);
System.out.println("欢迎你," + session.getRemoteAddress());
}
@Override
public void sessionClosed (IoSession session) throws Exception {
super .sessionClosed(session);
System.out.println("客户端退出了...." );
}
@Override
public void messageReceived (IoSession session, Object message) throws Exception {
super .messageReceived(session, message);
String msg = (String) message;
session.write("echo:" + msg);
}
}
8.3 客户端 package com.mina;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import java.net.InetSocketAddress;
import java.util.Scanner;
public class Client {
public static void main (String[] args) {
NioSocketConnector connector = new NioSocketConnector ();
DefaultIoFilterChainBuilder chain = connector.getFilterChain();
chain.addLast("myChain" , new ProtocolCodecFilter (new TextLineCodecFactory ()));
connector.setHandler(new MinaClientHandler ());
connector.setConnectTimeoutCheckInterval(10000 );
ConnectFuture cf = connector.connect(new InetSocketAddress ("localhost" , 9999 ));
cf.awaitUninterruptibly();
Scanner sc = new Scanner (System.in);
while (true ) {
System.out.print("请输入:" );
String info = sc.nextLine();
cf.getSession().write(info);
}
}
}
package com.mina;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class MinaClientHandler extends IoHandlerAdapter {
@Override
public void sessionOpened (IoSession session) throws Exception {
super .sessionOpened(session);
System.out.println("sessionOpened...." );
}
@Override
public void sessionClosed (IoSession session) throws Exception {
super .sessionClosed(session);
System.out.println("sessionClosed...." );
}
@Override
public void messageReceived (IoSession session, Object message) throws Exception {
super .messageReceived(session, message);
String info = (String) message;
System.out.println(info);
}
}
8.4 结果 (此处省略运行截图,实际运行时请参照上述代码配置)
8.5 传输对象类型 new TextLineCodecFactory() 改成 new 某对象类,就可以实现自定义对象传输。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online