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