【Java高级】初探socket编程 ——JavaSocket连接与简单通信

article/2025/10/15 1:53:24

新学期生活开始一段时间了,要继续学习一些新的技术(这里指socket /doge),目标是尝试完成一个在线即时聊天的小程序(尽量不咕)。会更新一系列socket编程的技术文章,欢迎关注交流~

那么千里之行,始于足下,就从这socket编程开始说起吧。

socket概念

首先一个问题,什么是socket编程?他有个中文名称叫做“套接字编程”。这个词不直观,也比较晦涩,很容易让人产生误解。我们来看一下百度百科的定义:

简介:socket一般指套接字。所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制

大致可以明白其含义是在网络上,两台主机的进程实现通信的编程技术。
但是描述还是有些不够形象,那么其实从英文原意的角度来看,socket的翻译有“插座”的意思
在这里插入图片描述
这么看,将两个要相互通信的主机比作是插座和插头,发起的一方是插头,接受的一方是插座,二者的ip和端口对接上之后即可相互传输信息。这个socket可以说是上接相应的应用程序,下接通信协议栈,保证信息快速完整的传递。

这个比喻虽然不够恰当,但是也足够形象的体现socket编程的含义。

确定一个socket的标识有两个,分别是:IP端口
表示方法是点分十进制的IP地址和端口号,中间使用冒号隔开,例如:127.0.0.1:8888就是电脑本机的8888端口

Socket的工作流程

一次socket的连接与通信大致可分为以下的步骤:

  • 服务端:启动程序并开始监听连接
  • 客户端:启动程序并请求连接
  • 客户端:建立连接后像服务端发送信息
  • 服务端:建立连接并接受客户端发送的信息
  • 服务端:完成信息接受后向客户端回复信息
  • 客户端:完成信息传输后接收服务端的回复信息
  • 客户端:会话完毕,关闭socket
  • 服务端:会话完毕,关闭socket并选择关闭ServerSocket或者继续监听

当然,我们也可以选择在服务端采用多线程的形式来完成多个客户端请求的情况,从而避免服务端被一个客户端霸占,后序客户端排队的情况。这样的工作流程在刚刚的基础上可以被表示为:

  • 服务端:启动程序并开始监听连接
  • 客户端:启动程序并请求连接
  • 客户端:建立连接后像服务端发送信息
  • 服务端:确认连接并开启一个线程用来处理通信
  • 服务端:建立连接并接受客户端发送的信息
  • 服务端:完成信息接受后向客户端回复信息
  • 客户端:完成信息传输后接收服务端的回复信息
  • 客户端:会话完毕,关闭socket
  • 服务端:会话完毕,关闭socket并选择关闭ServerSocket或者继续监听

了解了大致的工作流程,下面就来了解一下socket的连接和通信

Socket连接

那么使用Java如何实现socket编程呢?

其实就是需要分别实现客户端(Client)和服务端(Server)的socket,即服务端开放“插座”等待匹配,客户端使用“插头”匹配服务端的插头。

最核心需要用到两个类,他们都在java.net包中:

  • Socket类,位于客户端(client)使用的“插头”,初始化时需要指定连接对象的IP和端口。
  • ServerSocket类,服务端(Server)的“插座”,初始化时需要制定开放的端口(服务器本机IP)

服务端需要使用ServerSocket中一个重要的方法来获取客户端的连接:accept()

这个方法可以获得客户端的socket对象,服务端使用这个对象中的io流与客户端进行通信,同样的,客户端使用socket中的io流与服务端进行通信。

上面这段描述需要注意的有两点:

  1. 客户端与服务端通过socket对象来通信,客户端创建socket,服务端使用ServerSocket对象接收并获取socket
  2. 客户端与服务端必须通过socket对象提供的io流进行通信,流中的内容会通过网络相互传输。

下面就来简单的实现一个客户端与服务端的对接:

//客户端
public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);}
}
//服务端
public class SocketServer {public static void main(String[] args) throws IOException {System.out.println("服务端程序~~~");System.out.println("我创建了一个ServerSocket");ServerSocket server = new ServerSocket(8888);System.out.println("开始接受socket匹配");Socket client = server.accept();System.out.println("接收到了一个socket");}
}

此时,运行服务端:
在这里插入图片描述
发现程序并没有执行后面的语句输出提示。这个原因在于:accept()方法在等待连接时会使程序产生阻塞,不在往下执行,直到接受到一个连接,并且返回一个客户端的Socket对象实例。

那么接着就运行客户端,让他们相互匹配:
在这里插入图片描述
此时,服务端就接收到了一个socket连接,执行了后续的语句。
可以看出,在客户端,连接是在创建socket对象时就发起的,并不需要调用任何方法。
在服务端,需要使用accept方法来监听连接 ,当没有socket连接时,程序就会阻塞。

Socket通信

在建立了连接之后,服务端和客户端就要开始通信了。

上文提到过,两方的通信是通过字节流来完成的,而且这个字节流必须是socket对象提供的io流

这个io流需要通过socket的getInputStreamgetOutputStream两个方法来获取。

在使用io流进行通信的过程中,有一个需要注意的地方,就是服务端的输出流对接的是客户端的输出流,而客户端的输入流对接的是服务端的输出流。

用一张图来表示:
在这里插入图片描述
其实也很符合直觉的,但是在编写程序过程中,尤其是同时编写客户端和服务端的程序时,这两个流的方向是容易搞混的,需要注意一下。

单向传递消息

现在就先从客户端向服务端传递一条消息:

/*客户端*/
public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);System.out.println("正在行服务端发送消息");/*向服务端发送消息*/client.getOutputStream().write("你好服务端,这里是客户端".getBytes());System.out.println("像服务端发送消息完毕");/*会话结束*/client.close();}
}
/*服务端*/
public class SocketServer {public static void main(String[] args) throws IOException {System.out.println("服务端程序~~~");System.out.println("我创建了一个ServerSocket");ServerSocket server = new ServerSocket(8888);	//创建一个服务端soscketSystem.out.println("开始接受socket匹配");Socket client = server.accept();System.out.println("接收到了一个socket");InputStream is = client.getInputStream();	//输入流byte[] buffer = new byte[1024];				//缓冲int len = 0;								//每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();		//构建读取的消息while((len = is.read(buffer)) != -1){		//接收客户端的消息sb.append(new String(buffer,0,len));}System.out.println("收到客户端消息:" + sb.toString());client.close();//关闭连接server.close();//关闭服务端}
}

结果如下:
在这里插入图片描述

双向传递消息

想让消息有来有回,那么服务端就不能在接收后直接关闭连接,而是回复一条消息,那么这条消息就应该是通过服务端的输出流发送,在客户端的输入流接收。

/*客户端*/
public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);System.out.println("正在行服务端发送消息");/*向服务端发送消息*/client.getOutputStream().write("你好服务端,这里是客户端".getBytes());System.out.println("像服务端发送消息完毕");System.out.println("正在接收服务端回复");InputStream is = client.getInputStream();	//输入流byte[] buffer = new byte[1024];				//缓冲int len = 0;								//每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();		//构建读取的消息while((len = is.read(buffer)) != -1) {		//接收服务端的消息		sb.append(new String(buffer,0,len));}System.out.println("接收到服务端消息:" + sb.toString());/*会话结束*/client.close();}
}
/*服务端*/
public class SocketServer {public static void main(String[] args) throws IOException {System.out.println("服务端程序~~~");System.out.println("我创建了一个ServerSocket");ServerSocket server = new ServerSocket(8888);System.out.println("开始接受socket匹配");Socket client = server.accept();System.out.println("接收到了一个socket");InputStream is = client.getInputStream();	//输入流byte[] buffer = new byte[1024];				//缓冲int len = 0;								//每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();		//构建读取的消息while((len = is.read(buffer)) != -1){		//接收客户端的消息sb.append(new String(buffer,0,len));}System.out.println("收到客户端消息:" + sb.toString());System.out.println("正在回复~~~");client.getOutputStream().write("这里是服务端,收到消息,谢谢".getBytes());	//回复客户端client.close();//关闭连接server.close();//关闭服务端}
}

运行程序发现:
在这里插入图片描述
客户端并没有接收到服务端的回复,而服务端停滞在了接收客户端信息的地方。

流的阻塞问题

实际上,问题就出现在客户端向服务端传输信息这一过程中。在服务端看来,虽然已经获得了所有客户端发来的字节,但是它并不能确定客户端是否要继续发送信息,因此输入流就卡在那里,形成了阻塞。

解决这个问题,一个粗暴地方式是在客户端直接关闭输出流,当然不是调用输出流的关闭方法,而是调用socket的shutdownOutput方法。这个方式有个缺点,就是在关闭输出流之后,将无法再次输出。

我们使用这个语句来完善刚刚客户端的程序

public class SocketClient {public static void main(String[] args) throws UnknownHostException, IOException {System.out.println("客户端程序~~~");System.out.println("我创建了一个socket");Socket client = new Socket("127.0.0.1",8888);System.out.println("正在行服务端发送消息");/*向服务端发送消息*/client.getOutputStream().write("你好服务端,这里是客户端".getBytes());System.out.println("像服务端发送消息完毕");System.out.println("正在接收服务端回复");client.shutdownOutput();//关闭输出!!!!!InputStream is = client.getInputStream();	//输入流byte[] buffer = new byte[1024];				//缓冲int len = 0;								//每次读取的长度(正常情况下是1024,最后一次可能不是1024,如果传输结束,返回-1)StringBuilder sb = new StringBuilder();		//构建读取的消息while((len = is.read(buffer)) != -1) {		//接收服务端的消息		sb.append(new String(buffer,0,len));}System.out.println("接收到服务端消息:" + sb.toString());/*会话结束*/client.close();}
}

这回两端的通信就可以顺利进行了
在这里插入图片描述

上传和下载文件

传输文件与传输消息没有本质区别,在计算机眼中他们都是一样的二进制字节流。

不同点在于,一个数据源格式是字符串而另一个也是输入流。

我们要做的,就是将这些流进行对接,还是画个图来表示:
在这里插入图片描述
由于上传和下载这两个过程很类似,所以这里就仅实现一个文件上传的操作,进行演示。

在刚刚的通信基础上稍作修改:

/*客户端*/
public class TCPFileClient {public static void main(String[] args) throws UnknownHostException, IOException {Scanner scan = new Scanner(System.in);System.out.println("请输入要传递的文件全路径:");String filename = scan.next();FileInputStream fin = new FileInputStream(filename);//文件输出流,指向待传输文件System.out.println("正在尝试连接服务器");Socket client = new Socket("10.151.140.39",8888);System.out.println("服务器连接成功");OutputStream os = client.getOutputStream();		byte[] bytes = new byte[1024];int len = 0;long cnt = 0;//统计发送的字节数kbSystem.out.println("文件开始传输");while((len = fin.read(bytes)) != -1) {cnt++;if(cnt % (1 << 10) == 0) {System.out.println("已传输" + cnt / 1024 + "m");}os.write(bytes,0,len);}System.out.println("文件传输完成");InputStream is = client.getInputStream();client.shutdownOutput();StringBuffer sb = new StringBuffer();System.out.println("正在接受回复");while((len = is.read(bytes)) != -1) {sb.append(new String(bytes,0,len));}System.out.println("接收到回复:'" + sb + "'");fin.close();client.close();}
}
/*服务端*/
public class TCPFileServer {public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(8888);System.out.println("正在检测目标文件路径是否存在");File file = new File("E:\\server");if(!file.exists()) {System.out.println("已创建不存在的文件夹");file.mkdirs();}else {System.out.println("检测成功,目标文件夹存在");}FileOutputStream fos;Socket client;int name = 0;while(true) {System.out.println("服务器正在等待接收文件~~");fos = new FileOutputStream(file + "\\" + name + ".rar");//文件输出流,指向硬盘存储区域client = server.accept();InputStream is = client.getInputStream();byte[] bytes = new byte[1024];int len = 0;long cnt = 0;	//统计接收字节数kbSystem.out.println("正在进行文件传输");while((len = is.read(bytes)) != -1) {cnt++;if(cnt % (1 << 10) == 0) {System.out.println("已接收" + cnt / 1024 + "m");}fos.write(bytes,0,len);}System.out.println("文件传输成功,正在回话");client.getOutputStream().write("收到文件,谢谢".getBytes());fos.close();client.close();if(name == 50)break;}server.close();}
}

运行一下:
在这里插入图片描述
文件传输成功!!
由于服务端使用了循环,所以我们可以使得服务端一直处在接受文件的状态。


参考资料

  • Java 网络编程 之 socket 的用法与实现
  • 【JAVA开发】Socket套接字网络编程

http://chatgpt.dhexx.cn/article/CO6NKd1R.shtml

相关文章

Java--Socket通信(双向,有界面)

服务端&#xff1a; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStr…

JAVA 网络编程 Socket

网络&#xff1a;相互连接的计算机&#xff0c;带来的好处有共享资源&#xff0c;交换数据 IP地址&#xff1a;定位计算机地址 通常由网络地址主机地址组合 查看本机IP地址&#xff1a;电脑左下角输入CMD&#xff0c;回车在CMD界面输入ipconfig 127.0.0.1 测试本机网络配置 …

初识Java Socket编程

Java最初是作为网络编程语言出现的&#xff0c;其对网络提供了高度的支持&#xff0c;使得客户端和服务器的沟通变成了现实&#xff0c;而在网络编程中&#xff0c;使用最多的就是Socket。像大家熟悉的QQ、MSN都使用了Socket相关的技术。下面就让我们一起揭开Socket的神秘面纱。…

Java socket详解,看这一篇就够了

刚给大家讲解Java socket通信后&#xff0c;好多童鞋私信我&#xff0c;有好多地方不理解&#xff0c;看不明白。特抽时间整理一下&#xff0c;详细讲述Java socket通信原理和实现案例。整个过程楼主都是通过先简单明了的示例让大家了解整个基本原理&#xff0c;后慢慢接近生产…

递归下降文法C语言实验报告,递归下降分析法的简单例子的c语言实现

我们举的简单例子是 G[s]: S → a | ∧ | (T) T→T, S | S 转化为LL1文法 S →a |∧| (T) T →ST T→,ST| ε c语言代码实现 #include char scaner(char*input,int* p); void S(char*input,int* p); void T(char*input,int* p); void T1(char*input,int* p); void error(); int…

C++:编译实验之递归下降分析器

一、实验目的 1.加深对递归下降分析法一种自顶向下的语法分析方法的理解。 2.根据文法的产生式规则消除左递归&#xff0c;提取公共左因子构造出相应的递归下降分析器。 二、实验内容 根据课堂讲授的形式化算法&#xff0c;编制程序实现递归下降分析器&#xff0c;能对常见…

递归下降分析程序的设计和实现

递归下降分析程序的设计和实现 一、实验的目的和要求 1、了解语法分析的主要任务。 2、实现基本的递归下降分析器&#xff0c;能够分析任意的符号串是否为该文法所定义的合法算术表达式。二、实验环境 Windows7 Dev-C三、实验准备 先将递归下降分析程序的生成认真的学习一…

利用递归下降分析方法完成语法分析

一、 程序设计题目与说明 利用递归下降分析方法完成语法分析。 递归下降分析法是一种自顶向下的分析方法&#xff0c;文法的每个非终结符对应一个递归过程&#xff08;函数&#xff09;。分析过程就是从文法开始符出发执行一组递归过程&#xff08;函数&#xff09;&#xff…

编译原理 --- 递归下降分析器

第一部分 --- 构造递归下降分析器 1.在上面这个例子中则是子程序序A先调用子程序B&#xff0c;本程序结束完调用之后再返回来继续调用下一个符号 如果下一个符号是终结符的话那就直接进行匹配&#xff0c;不进行调用&#xff0c;匹配完后继续调用下一个符号 如果不是的话则调…

递归下降分析法实现强化计算器

一. 实验概述 1.使用bison 和 flex 实现扩展版计算器 该计算器支持实型的两种表达,分别是小数和科学计数法. 该计算器支持 加, 减, 乘 除 四种运算 和括号()运算符. 该计算器支持整形,实型混合运算 2.通过递归下降分析法自行编写的语法分析和使用flex进行的词法分析的计算器.…

编译原理研究性学习专题 2——递归下降语法分析设计原理与实现

1 实验内容 完成以下描述赋值语句的 LL(1)文法的递归下降分析程序 G[S]: S→ VE E→ TE’ E’→ ATE’ | e T→ FT’ T’→ MFT’ | E F→ (E) | i A→ | - M→ * | / V→ i 设计说明&#xff1a;终结符号 i 为用户定义的简单变量&#xff0c;即标识符的定义。 2 实验要求 …

Python技法之简单递归下降Parser的实现方法

文章目录 一. 算术运算表达式求值二. 生成表达式树三、左递归和运算符优先级陷阱四. 相关包 一. 算术运算表达式求值 对于简单的算术运算表达式&#xff0c;假定我们已经用分词技术将其转化为输入的tokens流&#xff0c;如NUMNUM*NUM。 在此基础上&#xff0c;我们定义BNF规则…

编译实验 . 递归下降分析器

实验目的&#xff1a; 1.1掌握语法分析方法。 1.2掌握使用算符优先分析法。 1.3完成语法分析程序的设计和实现。 1.4程序能完成对指定语言的语法分析。 2. 递归下降分析器 在不含左递归和每个非终结符的所有候选终结首符集都两两不相交的条件下&#xff0c;我们就可能构造…

用c语言编译递归下降翻译器,Java实现C语言语义分析(递归下降)

说起这次的语义分析&#xff0c;不得不说的是我的重大的改变。上一次的语法分析是利用了预测分析法来实现的&#xff0c;经过多方考证&#xff0c;发现用预测分析法的语法分析器基础来实现语义分析的困难重重&#xff0c;例如在语法指导翻译的时候那个栈的变化和各种属性的传递…

递归下降分析法

介绍&#xff1a; 递归下降分析法是针对LL(1)文法的一种语法分析方法&#xff1b; 通过对文法的消除左递归&#xff0c;提取左公因子&#xff0c;对各个产生式和非终结符求first()和follow()集&#xff0c;通过first()和follow()集构造该文法的预测分析表&#xff0c;当这个预…

编译原理实验-递归下降语法分析

具体代码已放至Github&#xff08;仅供参考&#xff09;&#xff1a; qxpBlog/Compiler_UESTC: 电子科技大学编译原理实验 (github.com) 具体实验过程如下&#xff1a; 一、实验目的、原理、内容及步骤&#xff1a; &#xff08;1&#xff09;目的&#xff1a;通过本实验加深…

编译原理实验--实验二 递归下降法判断算术表达式的正确性--Python实现

目录 一、实验目的和要求 二、实验内容 三、实验环境 四、实验步骤 1、语法分析所依据的文法&#xff1b; 2、给出消除左递归及提取左公因子的文法&#xff1b; 五、测试要求 六、实验步骤 1、语法分析所依据的文法 2、给出消除左递归及提取左公因子的文法&#xff1…

递归下降语法分析

一、实验目的 递归下降语法分析 二、实验题目 三、分析与设计 四、源代码 #include <iostream> #include <fstream> #include <cstring> #include <string> #include <conio.h> #define digit 1 // 1数字 #define op 2 // -*/()# #define Hh …

Java递归下降分析器_递归下降语法分析器

用java语言编写的递归下降语法分析器&#xff0c;是一种适合手写语法编译器的方法&#xff0c;且非常简单。递归下降法对语言所用的文法有一些限制&#xff0c;但递归下降是现阶段主流的语法分析方法&#xff0c;因为它可以由开发人员高度控制&#xff0c;在提供错误信息方面也…

递归下降算法

递归下降算法 算法模型&#xff1a; Term Term Expr ExprExprFactor Factor 单个元素。最小单位。 实现原理&#xff1a; 一个程式进入算法及被看作是一个项&#xff0c;分解成项加表达式的形式&#xff0c;表达式被分解成 表达式加因子的形式&#xff0c;因子是这个算法…