2554 字
13 分钟
Java TCP通信简易实现

Java TCP通信简易实现#

客户端#

一,建立与服务端的连接#

现在我们假设服务端已经在它的主机上的6666端口启动了服务,我们为了建立与它的连接,需要new一个socket对象,向服务端发起TCP连接请求

// 假设服务端在本地6666端口启动,
// new Socket(IPAddress ,port);
Socket socket = new Socket("127.0.0.1",6666);

好的,现在请求成功了,已经与服务端建立好了连接,接下来我们需要发送我们打算发送的数据

于是我们需要一个输出流(OutputStream)

OutputStream os = socket.getOutputStream();

你可能会问,

Q:为什么不是自己new一个os呢?

A:因为socket对象内部已经与服务器建立了底层的连接通道,它封装了网路的输入输出流,只有通过socket.getOutputStream() 获得的流对象,才真正连接到对方的接收端口。

Q:那我不能把自己new的os对象手动建立连接吗?

A:不能,os对象本身是一个抽象的数据流接口/抽象类,它只负责数据的“写出”功能,但它不具备“建立网络连接”这个能力。

接下来我们又遇到一个问题,OutputStream对象只能写入原始字节,不能直接写 int、float、String 等数据类型,也就是byte类型,所以我们要进行包装,方便数据的写入

DataOutputStream dos = new DataOutputStream(os);

太好了,这下我们可以进行数据的写入了!

Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
dos.writeUTF(msg);

整合代码:

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class TCPClientNote {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",6666);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
dos.writeUTF(msg);
}
}

但是现在有一个问题,我们发完一次数据好像我的客户端就结束了,所以为了可以不断地发送信息,我们可以加一个循环嵌套:

while(true){
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
}

再加一个退出条件:

while(true){
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
if(msg == "exit"){
// 先关闭 dos流,再关闭 socket连接
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
// 强制把缓冲区中的消息发送出去,避免数据停留在内存里
// DataOutputStream 内部有缓冲机制,调用 writeUTF() 后,数据可能暂时存放在内存缓冲区,还没真正
// 通过网络发送出去。
dos.flush();
}

完整版:

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class TCPClientNote {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",6666);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
while(true){
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
if(msg == "exit"){
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}

服务端:#

一,连接客户端#

new 一个Serversocket对象,用于与客户端进行连接,需要指定端口,用于监听。

int port = 6666;
Serversocket ss = new Serversocket(port);
// Serversocket ss = new Serversocket(6666);

为了与来进行连接的客户端进行连接,有以下代码:

Socket socket = ss.accept();

这行代码会在此处阻塞等待,直到有客户端连接。

ss.accept() 在与客户端连接之后会返回一个socket对象,通过创建socket变量并接收这个连接的对象,用于与客户端通信,读取客户端发送的数据。

二,读取客户端发来的消息#

现在我们已经获取了客户端的socket,现在我们可以进行读取操作了

首先将socket中的输入流取出来,也就是客户端发过来的数据流:

InputStream is = socket.getInputStream();

但是这样获取过来的都是原始的字节信息,即 byte数组,如果读取这样的信息,我们还要进行各种各样的转换操作才能将他变成可读的信息,为了更方便的读取数据,我们可以再次将输入流进行包装:

// 传入字节流对象
DataInputStream dis = new DataInputStream(is);

这下我们就可以方便的读取数据了:

String msg = dis.readUTF();
System.out.println(msg);

到这里,一个简易的服务端的代码编写基本就完成了

整合代码:

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServerNote{
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(6666);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
String msg = dis.readUTF();
System.out.println(msg);
}
}

但是这段代码有个非常明显的问题,就是只接收了一次消息,然后程序就结束了

同理,那我们加一层循环嵌套,可以一直接收消息:

while(true){
String msg = dis.readUTF();
System.out.println(msg);
}

完整版:

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServerNote{
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(6666);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while(true){
String msg = dis.readUTF();
System.out.println(msg);
}
}
}

这下,客户端可以与服务端通信了

多用户连接与群发消息:#

一、多线程与实现#

前面我们所实现的仅仅是一对一的连接,即一个服务端连接一个客户端,接下来我们来实现多个客户端连接一个服务端。

有人会说,那我直接while true 不断让他接收新的客户端连接不就行了嘛

while(true){
Socket socket = serverSocket.accept();
// ...
}

事实是:不行

每次接收到新socket连接时,serversocket会将之前的客户端连接所遗弃,只处理本次连接的客户端

换句话说,现在的服务端只有一个main线程在运行,这样简单的循环实现没有告诉他我要并行处理多个客户端连接

所以我们需要使用多线程:

while(true){
Socket socket = serverSocket.accept();
// ...
new ServerReaderThread(socket).start();
}

我们先不看线程类如何实现,我们先来关注如何存储我们的用户池

其实非常简单,直接new一个ArrayLsit即可:

// static设置为类级别,只有一个线程池
public static List<Socket> ClientSockets = new ArrayList<>();

完整代码:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class TCPServer2 {
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
Socket socket = serverSocket.accept();
onLineSockets.add(socket);
// 启动线程
// new ServerReaderThread(socket).start();
}
}
}

接下来我们来实现线程类

二、线程类#

现在我们的主文件不再需要实现接收消息的功能,只需要在线程类中实现即可:

// 继承自线程类
public class ServerReaderThread extends Thread{
private Socket socket;
// 构造方法注入socket对象
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run(){
// ...
}
}

接下来我们来看看启动线程后的代码如何写

与之前服务类相同,先获取输入流,之后进行包装:

InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);

循环接收发来的信息:

while(true){
String msg = dis.readUTF();
System.out.println(msg);
}

当客户端断开连接时,会抛出异常,所以我们还要进行异常的捕获:

while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
SendMsgAll(msg);
}catch (Exception e) {
//从线程池中移除断开连接的用户
TCPServer2.onLineSockets.remove(socket);
dis.close();
socket.close();
break;
}
}

完整代码:

public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
// 在 run() 方法中,获取输入流的过程可能抛出受检异常,如 IOException,但 run() 方法无法在
// 签名中使用 throws 抛出,因此必须用最外层 try-catch 捕获并处理。常见做法是将其转化为
// RuntimeException,以便后续调试和统一异常管理。
try {
InputStream inputStream = socket.getInputStream();
DataInputStream dis = new DataInputStream(inputStream);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch(Exception e) {
TCPServer2.onLineSockets.remove(socket);
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
三、群发消息#

下面我们来实现如何将用户的消息转发给别的用户

实际上在我们的模型中,必须有一个人做服务端接收消息,而用户端都是发送消息的一方,如何实现也能接收消息呢?其实就是服务端进行转发

为此,我们还要实现一个ClientReaderThread,用于客户端接收消息,这是因为如果我们直接在Client.java里实现接收的话,由于只有一个主线程,在接收消息时如果没人给他发送消息,主线程就会被阻塞,无法执行其他任务,以下是代码实现,与ServerReaderthread几乎一样:

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ClienReaderThread extends Thread{
public Socket socket;
public ClienReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream inputStream = socket.getInputStream();
DataInputStream dis = new DataInputStream(inputStream);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch(Exception e) {
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

之后将这个线程在客户端代码中启动即可:

// ...其他代码...
Socket socket = new Socket("127.0.0.1",6666);
new ClienReaderThread(socket).start();
OutputStream os = socket.getOutputStream();
// ...其他代码...

群发消息也很简单,就是遍历用户池中的用户,进行发消息即可,以下是方法实现:

private void SendMsgAll(String msg) throws IOException {
for (Socket onLineSocket : TCPServer2.onLineSockets) {
if(onLineSocket.getRemoteSocketAddress().equals(socket.getRemoteSocketAddress())){
continue;
}
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}

至此,我们实现了所有功能

全部完整代码:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class TCPServer2 {
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
Socket socket = serverSocket.accept();
onLineSockets.add(socket);
System.out.println( socket.getRemoteSocketAddress() + "上线了");
new ServerReaderThread(socket).start();
}
}
}
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
public static void main(String[] args) throws IOException {
//服务器主机ip和端口
Socket socket = new Socket("127.0.0.1",6666);
new ClienReaderThread(socket).start();
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
System.out.println("已连接");
while (true) {
String msg = sc.nextLine();
if (msg.equals("exit")){
System.out.println("end");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream inputStream = socket.getInputStream();
DataInputStream dis = new DataInputStream(inputStream);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
SendMsgAll(msg);
} catch(Exception e) {
System.out.println("有人下线了" + socket.getRemoteSocketAddress());
TCPServer2.onLineSockets.remove(socket);
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void SendMsgAll(String msg) throws IOException {
for (Socket onLineSocket : TCPServer2.onLineSockets) {
if (onLineSocket.getRemoteSocketAddress().equals(socket.getRemoteSocketAddress())){
continue;
}
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ClienReaderThread extends Thread{
public Socket socket;
public ClienReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream inputStream = socket.getInputStream();
DataInputStream dis = new DataInputStream(inputStream);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch(Exception e) {
System.out.println("自己下线了");
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

完成于2025.6.5 16:50

Java TCP通信简易实现
https://lennyface24.github.io/posts/java-2025-06-05/
作者
Itzlennyface
发布于
2025-06-05
许可协议
CC BY-NC-SA 4.0