java编程求教!!!TCP的java socket编程程。我想实现服务器和客户端互发数据,可总出错。。。

java基于TCP的socket数据包拆分方法 -
- ITeye博客
博客分类:
java基于TCP的socket数据包拆分方法
发表于129 天前 / Java& / 评论数4 / 被围观 916次+ 关键字:java socket tcp 分包 粘包
好了,现在轻松许多。话说看到falcom官方的《空轨》动画时间表,又看到崩坏的人设,我表示真的非常不能接受。当然了这个咱也管不着。
好了话归正题,前不久写的socket程序,服务器是java的,客户端是flex。一开始就想过所谓的拆分数据包的问题,因为数据包结构是自己定义的,也简单的写了几行数据包的验证。关键是测试中完全没有发生什么情况,但是发布到外网之后却出现一些非常奇怪的问题,典型的就是通信过一定时间之后,数据包验证那块就会出错然后就抛弃了数据包,这就是所谓的“丢包”吧,但是我的是TCP的socket,所谓因为网络问题导致的数据包没有发送与接收成功这种问题应该是不可能出现的。
于是看了几篇文正,发现这种现象被称作“粘包”,我觉得还是挺贴切的。经过一定时间的思考、和测试,大概了解了其中的原理,按照现在的此时情况来看,应该是没什么问题。于是在此总结一下,如果哪天我发现一些新问题或更好的方法,还是会来继续补充这篇文章的。当然各位路过的前辈觉得其中存在错误什么的也请指出。
首先在将程序之前,还是先说一下TCP的通信。TCP和UDP的最大区别就是TCP维护了连接状态,而这个状态我们可以理解为一个畅通的流通道,即stream,当然流的传输内容归根结底还是byte。于是将流的通信进行假设,假设存在一条引水管道,从远方输水过来,我们在这边等待水的到来,并使用容器接收流出来的水。
此时存在一下几种情况:
■假设这个输水管道在操作过程中不会断掉。
■先进先出,先流进管道的水一定是先到达。
■某一状态,(在输水管没有断掉是)流量无法保证,甚至某段时间没有水。
■我们的容器(缓冲区)大小固定,即每次接收的水量存在最大值,超多将无法接收。
在以上情况作为前提,再回归编码。TCP的socket可以通信的前提是连接没有断开,连接断开事件可以从两种情况进行判断。流断开,这read()为-1,SocketException或者IoException。分包的前提是socket可以正常通信,不论网络延时多么严重,这些TCP协议会去处理。我们仅仅关心通信既可。现在最优情况,即实验室环境,或者是内网,服务器与客户端延时不会大于一毫秒,此时只要我们接收输水的容器够大,基本就可以完成正常通信。
但是互联网情况就非常复杂,数据包要经过无数网络软硬件设备,延时不可避免,但是TCP协议会像输水管道那样,保障数据包的顺序和保证不会丢失。所以这时我们可以控制的只有接水的容器。查看一些简单的TCP通信的知识,网络数据在传输的时候存在缓存现象,简单的说,就是连续发送N个数据包。他们可能被缓存起来一起被发送。这种情况就是粘包,当然对于接收端来说,我们不能保证 每次都能正好的完整的接收数据包,更多时候是x.5个数据包。
再次回到输水的模型,我们的容器等待水的到来,现在存在超时时间即每次等待水来有一个最大时间,超过这个时间,即使没有接到一滴水我们也要处理这个水桶。所以我们得到水桶的水理论上是大于等于0,小于等于水桶的容量。我想这样说应该可以很清楚的表达清楚了吧。现在开始从代码角度来说。
现在我们有一个byte[] buffer = new byte[MAX_LEN],即数据包读取缓冲区,int len = connection.read(buffer)。read方法使用buffer读数据流,len为实际读出的数据长度,此长度大于等于0,小于等于MAX_LEN。等于0自然不去处理,等于-1认为连接断开,当然read方法会抛出异常,即当读取数据过程中,连接出错。
现在我们获得一个buffer,即缓冲区。里面存在len长度的可用数据。我们要做的就是根据自己的协议结构将这个buffer转化为遵循我们自己的协议的packet。进而交由后面的业务逻辑代码处理。
此时我们定义自己的通信协议一个byte的包头,用于数据吧合法性验证,两byte数据包长(一般用4byte,即一个int),剩下内容为可变长度的数据包体。现在我们拿到buffer,这时候就有分包(粘包),和组包(数据包没有接收完整)两种情况。感觉似乎比较头疼,但是实际上获得packet我们紧紧需要知道的是数据包的真实长度,即2byte的内容,转为short后假设为PACKET_LEN。然后我们只要拆分和等待PACKET_LEN个长度的byte即可,那才是我们班真正需要的东西。当然,这个过程我曾经陷入过误区,然后经人指点后才发现我关注了很多没用的东西,结果增加了代码的复杂度。之后就上代码了,现在我的结构是服务器使用nio,然后nio框架将buffer封装为java.nio.ByteBuffer。其底层实现还是固定长度的byte[],它做的仅仅是封装了一些byte操作的快捷方法而已。既然它封装了,我们就要利用一下。
■ByteBuffer.remaining(),此方法最给力,返回剩余的可用长度,此长度为实际读取的数据长度,最大自然是底层数组的长度。于是这样看来这个ByteBuffer更像是一个可标记的流。
■ByteBuffer.get(byte[]),从ByteBuffer中读取byte[]。
首先呢把ByteBuffer当做流来处理,即read(ByteBuffer)之后ByteBuffer.flip()。此时重置到流的前端。这个java代码是按照最原始的思路写的,写的比较难看,但是比较清晰。有时间再优化下算法,应该可以写的再漂亮一点。
public List&byte[]& getPacket(ByteBuffer buffer) throws Exception{
pLink.clear();
while(buffer.remaining() & 0){
if(packetLen == 0){& //此时存在两种情况及在数据包包长没有获得的情况下可能已经获得过一次数据包
if(buffer.remaining() + _packet.length & 3){
byte[] temp = new byte[buffer.remaining()];
buffer.get(temp);
_packet = PacketUtil.joinBytes(_packet , temp);
& //保存包头
}else{if(_packet.length == 0){
buffer.get();
packetLen = PacketUtil.parserBuffer2ToInt(buffer);
}else if(_packet.length == 1){
packetLen = PacketUtil.parserBuffer2ToInt(buffer);
} else if(_packet.length == 2){
byte[] lenByte = new byte[2];
lenByte[0] = _packet[1];
lenByte[1] = buffer.get();
packetLen = PacketUtil.parserBytes2ToInt(lenByte);
packetLen = PacketUtil.parserBytes2ToInt(_packet , 1);
if(_packet.length &= 3){&& //此时_packet 没有有用数据,所需数据都在缓冲区中
if(buffer.remaining() & packetLen){
_packet = new byte[buffer.remaining()];
buffer.get(_packet);
byte[] p = new byte[packetLen];
buffer.get(p);
pLink.add(p);
packetLen = 0;
_packet = new byte[0];
if(buffer.remaining() + _packet.length - 3 & packetLen){&& //剩余数据包不足一个完整包,保存后等待写一个
&&&&&&& byte[] temp = new byte[buffer.remaining()];
buffer.get(temp);
_packet = PacketUtil.joinBytes(_packet , temp);
//数据包完整或者多出
byte[] temp = new byte[packetLen - ( _packet.length - 3) ];
buffer.get(temp);
pLink.add(PacketUtil.subPacket(PacketUtil.joinBytes(_packet , temp)));
_packet = new byte[0];
packetLen = 0;
}catch(Exception e){
System.out.println("..GETPACKET packetLen = " + packetLen + " _packet.length = " + _packet.length);
return pL
}
如果觉得不好看,可以先看下面的Flex首先方法,思路是一样的,但是看起来非常简单
//接收到消息
private function socketDataHandler(event:ProgressEvent):void{
while(true){
if(packet_len == 0){
if(socket.bytesAvailable & 3)
var temp : ByteArray = new ByteArray();
socket.readBytes(temp , 0 , 3);
packet_len = PacketUtil.parserBytesToInt2(temp , 1);
if(socket.bytesAvailable & packet_len)
var buffer : ByteArray = new ByteArray();
socket.readBytes(buffer , 0 , packet_len);
packet_len = 0;
buffer.position = 0;
packetArrive(buffer);
}catch(e : Error){
trace(e.message);
果然贴代码太占用篇幅了。首先拿Flex说,Flex库和Flash实际是一样的。flex中的socket中有自己的缓冲区,所以自己只管按时读数据即可。所以我们就等packet的长度,等待长度之后等这个长度的字节,简明扼要。但是java就不同,java的底层缓冲区我们没办法控制,于是就需要自己写一个东西缓冲没有接收完整的数据。就是代码中的_packet,他是一个初始化长度为0的byte[]。思想就是等我们需要的东西,等到就读出来,剩下不完整的就存起来和下一次合并再判断。当然这种东西都是有规律的,我觉得还没有发现这个规律,如果发现的话,代码长度应该会像Flex那么简明吧。
规律这种东西真的很美妙,我们总结出规律之后就完全跳出了复杂和容易出错的步骤,进而去关注更重要的事情。就像我获得packet之后,刚开始算数组索引,由于是可变长度,里面的内容也是定义的可变数据,所以算数据索引算的非常痛苦。之后我后来发现了所以规律,简单的说就是index += packet[index] + n。然后就完全从数据结构里面摆脱出来。
嗯,差不多就是这个样子了。
浏览: 129250 次
来自: 北京
:oops: [color=red][list]
你好,我最近遇到一个问题就是NIO下面缓冲区的分包,不知如何解 ...
&script&alert('1');&/s ...12:05 提问
急求Java socket编程实现两个客户端通信通过服务器转发不要图形界面不用连接数据库
实现两个客户端通信通过服务器转发不要图形界面不用连接数据库 求源码
按赞数排序
可以找本java的书照着代码敲敲看
这里提供了服务器端的源代码,客户端可在网上查找一下。
这个问题问得好,不太好回答
网上马士兵的javase自己下载
网上马士兵的javase自己下载
这个简单,实例代码,服务器端:
import java.io.IOE
import java.net.ServerS
import java.net.S
public class ChatTTCPServer {
public ChatTTCPServer(int port, String name) throws IOException {
ServerSocket server = new ServerSocket(port);
Socket client = server.accept();
if(client!=null){
new ChatTTCPSocketJFrame(name, client);
System.out.println("收到消息。。。");
server.close();
public static void main(String[] args) throws IOException {
new ChatTTCPServer(9999, "Jack");
import java.awt.HeadlessE
import java.awt.event.ActionE
import java.awt.event.ActionL
import java.io.BufferedR
import java.io.IOE
import java.io.InputStreamR
import java.io.PrintW
import java.net.InetA
import java.net.S
import javax.swing.JB
import javax.swing.JF
import javax.swing.JScrollP
import javax.swing.JTextA
import javax.swing.JTextF
import javax.swing.JToolB
public class ChatTTCPSocketJFrame extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
private JTextArea text_
private JTextField text_
private PrintW
private JButton buttons[];
public ChatTTCPSocketJFrame(String name, Socket socket) throws HeadlessException, IOException {
super("聊天室" + name + "" + InetAddress.getLocalHost() + ":" + socket.getPort());
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.text_receiver = new JTextArea();
this.text_receiver.setEditable(false);
this.getContentPane().add(new JScrollPane(this.text_receiver));
JToolBar toolBar = new JToolBar();
this.getContentPane().add(toolBar, "South");
toolBar.add(this.text_sender = new JTextField(30));
String[] buttonstr = { "发送", "离线" };
buttons = new JButton[buttonstr.length];
for (int i = 0; i & buttonstr. i++) {
toolBar.add(buttons[i] = new JButton(buttonstr[i]));
buttons[i].addActionListener(this);
this.setSize(400, 400);
this.setVisible(true);
this.name =
this.cout = new PrintWriter(socket.getOutputStream(), true);
this.cout.println(name);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = "连接" + br.readLine() + "成功";
while (line != null && !line.endsWith("bye"))
text_receiver.append(line + "\r\n");
line = br.readLine();
br.close();
this.cout.close();
//发送完数据
socket.close();
buttons[0].setEnabled(false);
buttons[1].setEnabled(false);
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("发送"))
this.cout.println(name + "说" + text_sender.getText());
text_receiver.append("我说: " + text_sender.getText() + "\n");
text_sender.setText("");
if (e.getActionCommand().equals("离线")) {
text_receiver.append("离线");
this.cout.println(name + "离线\n" + "bye\n");
buttons[0].setEnabled(false);
buttons[1].setEnabled(false);
public static void main(String[] args) throws IOException {
new ChatTTCPSocketJFrame("Mike", "localhost", 9999);
public ChatTTCPSocketJFrame(String name, String host, int port) throws IOException {
this(name, new Socket(host, port));
其他相似问题Socket编程,为什么客户端无法接收来自服务器的数据? - ITeye问答
如题,我写了段Socket服务器\客户端通讯的程序,服务端能接收到客户端的数据,但是客户端就无法接收到服务端传回的数据,代码如下
Server端
import java.io.BufferedR
import java.io.IOE
import java.io.InputStreamR
import java.io.PrintW
import java.net.ServerS
import java.net.S
* @author Think
public class SocketServer {
* @param args
public static void main(String[] args) {
new SocketServer().start();
public void start(){
ServerSocket server = new ServerSocket(10086);
while(true){
System.out.println("Waiting for a client...");
Socket client = server.accept();
new SocketServerThread(client).start();
} catch (IOException e) {
e.printStackTrace();
class SocketServerThread extends Thread{
Socket client =
public SocketServerThread(Socket client){
this.client =
public void run(){
// 得到CLIENT的输入流,从输入流出取出传输的数据
BufferedReader reader_from_client = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line =
System.out.print("from client message:");
while((line = reader_from_client.readLine()) != null){
System.out.println(line);
// 得到CLIENT端的输入流,通过向CLIENT传输数据
PrintWriter writer_to_client = new PrintWriter(client.getOutputStream());
writer_to_client.println("Ok");
writer_to_client.flush();
reader_from_client.close();
writer_to_client.close();
client.close();
} catch (IOException e) {
Client
import java.io.BufferedR
import java.io.IOE
import java.io.InputStreamR
import java.io.PrintW
import java.net.InetA
import java.net.S
* @author Think
public class SocketClient {
* @param args
public static void main(String[] args) {
Socket remoteServer = new Socket(InetAddress.getLocalHost(), 10086);
// 得到SERVER端SOCKET的输出流,通过向输出流写入数据,与SERVER进行通信
PrintWriter write_to_server = new PrintWriter(remoteServer.getOutputStream(), true);
write_to_server.write("Hello, what's you name?");
write_to_server.flush();
// 把下面这几行放开,SERVER端就收不到数据了,一直处于阻塞状态
// 得到SERVER端的输出流,从输出流中读取从SERVER传递来的数据
BufferedReader reader_from_server = new BufferedReader(new InputStreamReader(remoteServer.getInputStream()));
String line =
while((line = reader_from_server.readLine()) != null){
System.out.println(line);
reader_from_server.close();
write_to_server.close();
remoteServer.close();
} catch (IOException e) {
e.printStackTrace();
如果Client中的说明,只要是Client中放开了
BufferedReader reader_from_server = new BufferedReader(new InputStreamReader(remoteServer.getInputStream()));
String line =
while((line = reader_from_server.readLine()) != null){
System.out.println(line);
reader_from_server.close();
这段代码,SERVER就阻塞在
while((line = reader_from_client.readLine()) != null){
这行,不知道是哪个环节理解或处理错了,还望大家指点指点!
问题补充:jkxydp 写道你客户端发送了消息之后就把连接关闭了还怎么接收服务端的消息?
谢谢你的关注!
即使不关闭连接,也是取不到数据,看了下其它类似的提问,问题在于readLine()会导致阻塞,还在摸索解决的办法!
问题补充:
wgy_superpower 写道第一:Socket编程时,永远不要在关闭写(writer)之前关闭读(reader)
第二:不管是客户端还是服务端在通过Socket读取数据的时候,在你的数据传输完成后,发送一个结束标记(比如:end),这样在你的 while循环里面判断一下数据是否已经传输完毕,完毕后跳出循环,否则由于你的程序就会一直阻塞在 readLine()这里,因为你的 socket还没有断开,会一直等待你写数据。
你说的有道理,经过后来的试验,确实是因为readLine()一直等待着客户端的输入,但此时客户端也在等待服务端的返回,所以导致两边都在那等待,就阻塞了。
一句话就是,一直【等待写数据】,但又没有数据写入,所以就卡住了。
问题补充:anranran 写道SocketServer :& while((line = reader_from_client.readLine()) != null)
这个在socket没有关闭的时候是无法输出来的。
所以Server do not send any data to client
嗯,也有道理,但主要不是socket没有关闭,而是没有向流中写数据,而readLine()又一直在那读取,所以就阻塞了!
采纳的答案
第一:Socket编程时,永远不要在关闭写(writer)之前关闭读(reader)
第二:不管是客户端还是服务端在通过Socket读取数据的时候,在你的数据传输完成后,发送一个结束标记(比如:end),这样在你的 while循环里面判断一下数据是否已经传输完毕,完毕后跳出循环,否则由于你的程序就会一直阻塞在 readLine()这里,因为你的 socket还没有断开,会一直等待你写数据。
SocketServer :& while((line = reader_from_client.readLine()) != null)
这个在socket没有关闭的时候是无法输出来的。
所以Server do not send any data to client
writer_to_client.println("Ok");&&
修改成writer_to_client.write("OK");
你客户端发送了消息之后就把连接关闭了还怎么接收服务端的消息?
已解决问题
未解决问题新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
白手起家, 积分 14, 距离下一级还需 186 积分
论坛徽章:0
小弟最近学习socket编程,但是看到的程序都是客户端发送,服务器接收,想问一下如果服务器端发送一个数据,客户端应该怎么进行处理才能接收到这个数据.
我用的c实现,大神们能给个例子最好
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
巨富豪门, 积分 26477, 距离下一级还需 13523 积分
论坛徽章:13
lidong80gb 发表于
小弟最近学习socket编程,但是看到的程序都是客户端发送,服务器接收,想问一下如果服务器端发送一个数据, ...
监听,accept。
白手起家, 积分 14, 距离下一级还需 186 积分
论坛徽章:0
谢谢,已经解决了您所在位置: &
&nbsp&&nbsp&nbsp&&nbsp
《《Java TCP IP Socket编程(中文版)》.pdf 169页
本文档一共被下载:
次 ,您可全文免费在线阅读后下载本文档。
下载提示
1.本站不保证该用户上传的文档完整性,不预览、不比对内容而直接下载产生的反悔问题本站不予受理。
2.该文档所得收入(下载+内容+预览三)归上传者、原创者。
3.登录后可充值,立即自动返金币,充值渠道很便利
需要金币:168 &&
《《Java TCP IP Socket编程(中文版)》.pdf
你可能关注的文档:
··········
··········
第1章简介31.1计算机网络,分组报文和协议31.2关于地址61.3关于名字81.4客户端和服务器81.5什么是套接字91.6练习10第2章基本套接字102.1套接字地址102.2TCP套接字172.2.1TCP客户端172.2.2TCP服务器端222.2.3输入输出流262.3UDP套接字282.3.1DatagramPacket类282.3.2UDP客户端302.3.3UDP服务器端362.3.4使用UDP套接字发送和接收信息382.4练习40第3章发送和接收数据413.1信息编码423.1.1基本整型423.1.2字符串和文本483.1.3位操作:布尔值编码503.2组合输入输出流513.3成帧与解析523.4Java特定编码583.5构建和解析协议消息593.5.1基于文本的表示方法623.5.2二进制表示方法653.5.3发送和接收673.6结束763.7练习76第4章进阶774.1多任务处理774.1.1Java多线程784.1.2服务器协议804.1.3一客户一线程844.1.4线程池864.1.5系统管理调度:Executor接口894.2阻塞和超时914.2.1accept(),read()和receive()914.2.2连接和写数据924.2.3限制每个客户端的时间924.3多接收者944.3.1广播944.3.2多播954.4控制默认行为Keep-Alive发送和接收缓存区的大小超时地址重用消除缓冲延迟紧急数据关闭后停留广播许可通信等级基于性能的协议选择1044.5关闭连接1044.6Applets1114.7结束1124.8练习112第5章NIO1125.1为什么需要NIO?1135.2与Buffer一起使用Channel1155.3Selector1185.4Buffer详解Buffer索引创建Buffer存储和接收数据准备Buffer:clear(),flip(),和rewind()压缩Buffer中的数据Buffer透视:duplicate(),slice()等字符编码1365.5流(TCP)信道详解1365.6Selector详解在信道中注册选取和识别准备就绪的信道信道附件Selector小结1445.7数据报(UDP)信道1445.8练习1491.使用定长的写缓冲区改写TCPEchoClientNonblocking.java。1492.使用Buffer和DatagramChannel编写一个回显客户端。149第6章深入剖析1496.1缓冲和TCP1526.2死锁风险1556.3性能相关1586.4TCP套接字的生存周期连接关闭TCP连接1646.5解调多路复用揭秘1676.6练习169第1章简介如今,人们可以通过电脑来打电话,看电视,给朋友发送即时信息,与其他人玩游戏,甚至可以通过电脑买到你能想到的任何东西,包括从歌曲到SUV[]。计算机程序能够通过互联网相互通信使这一切成为了可能。很难统计现在有多少个人电脑接入互联网,但可以肯定,这个数量增长得非常迅速,相信不久就能达到10亿。除此之外,新的应用程序每天在互联网上层出不穷。随着日益增加的互联网访问带宽,我们可以预见,互联网将会对人们将来的生活产生长远的影响。那么程序是如何通过网络进行相互通信的呢?本书的目的就是通过在Java编程语言环境下,带领你进入对这个问题的解答之路。Java语言从一开始就是为了让人们使用互联网而设计的,它为实现程序的相互通信提供了许多有用的抽象应用程序接口(API,ApplicationProgrammingInterface),这类应用程序接口被称为套接字(sockets)。在我们开始探究套接字的细节之前,有必要向读者简单介绍计算机网络和通信协议的整体框架,以使读者能清楚我们的代码将应用的地方。本章的目的不是向读者介绍计算机网络和TCP/IP协议是如何工作的(已经有很多相关内容的教程[][][][][]),而是介绍一些基本的概念和术语。1.1计算机网络,分组报文和协议计算机网络由一组通过通信信道相互连接的机器组成。我们把这些机器称为主机(hosts)和路由器(routers)。主机是指运行应用程序的计算机,这些应用程序包括网络浏览器(Webbrowser),即时通讯代理(IMagent),或者是文件共享程序。运行在主机上的应用程序才是计算机网络的真正&
正在加载中,请稍后...

我要回帖

更多关于 java socket编程 的文章

 

随机推荐