热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

socket测试工具_socket数据反序列化失败的问题

前言:问题出自今年6月份,当时做了一些整理,记录到了自己的有道云笔记中,最近打算笔记搬到公众号,顺带就重新再次记录下,就当是重温了吧.另外后续也会将笔记陆续搬过来,同

前言: 问题出自今年6月份, 当时做了一些整理, 记录到了自己的有道云笔记中,最近打算笔记搬到公众号, 顺带就重新再次记录下, 就当是重温了吧. 另外后续也会将笔记陆续搬过来, 同时也会重新拾起这个公众号, 谢谢大家

回归问题

场景:


使用netty作为socket服务器

使用机器人批量登录操作, 发送跑马灯

跑马灯类型: 公告

机器人数量: 500

测试工具是自己写的(socket客户端连接socket游服,
并对外提供websocket服务, 使用html页面连接websocket)

同时我这边还登录的个测试账号

问题说明

测试账户收到的数据如下

9e62b82682b9b4c7bad41940ea88f33b.png

自己测试客户端client解码输出正常

4d23051695842ddb85f510d05b1f15f5.png

服务器端发送出去的数据也正常, 如图

8e4d02190887637af657d8ac27a14c1c.png

也就是只有AIclient会报错

e6e9f39e92531973cdb39b9a7f66f972.png

上面报错的那段的内容如下

decode in: PooledUnsafeDirectByteBuf(ridx: 0, widx: 2460, cap: 4096)
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
byte in: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
decode in: PooledUnsafeDirectByteBuf(ridx: 0, widx: 360, cap: 512)
gdddd
decode in: PooledUnsafeDirectByteBuf(ridx: 0, widx: 30, cap: 64)
data: [8, 7, 16, 0, 26, 16, -26, -75, -117, -24, -81, -107, -27, -123, -84, -27, -111, -118, 45, 45, 45, 49]
decode in: PooledUnsafeDirectByteBuf(ridx: 0, widx: 30, cap: 64)
data: [8, 7, 16, 0, 26, 16, -26, -75, -117, -24, -81, -107, -27, -123, -84, -27, -111, -118, 45, 45, 45, 49]
decode in: PooledUnsafeDirectByteBuf(ridx: 0, widx: 810, cap: 1024)
gdddd
decode in: PooledUnsafeDirectByteBuf(ridx: 0, widx: 90, cap: 128)
gdddd
decode in: PooledUnsafeDirectByteBuf(ridx: 0, widx: 30, cap: 64)
[17:55:07:509] [nioEventLoopGroup-3-8] [ERROR] - (GameCodecer.java:54) - decode
java.lang.IllegalStateException: Reading from a byte array threw an IOException (should never happen).反序列化类发生异常
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:94) ~[classes/:?]
at com.miguantech.common.netty.GameCodecer.decodeRec(GameCodecer.java:52) [classes/:?]
at com.miguantech.test.client.net.tcp.ClientDecodeHandler.decode(ClientDecodeHandler.java:72) [test-classes/:?]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:503) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:442) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:281) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:287) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:700) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191]
Caused by: java.lang.RuntimeException: Reading from a byte array threw an IOException (should never happen).
at io.protostuff.IOUtil.mergeFrom(IOUtil.java:54) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ProtobufIOUtil.mergeFrom(ProtobufIOUtil.java:103) ~[protostuff-core-1.5.9.jar:1.5.9]
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:90) ~[classes/:?]
... 25 more
Caused by: io.protostuff.ProtobufException: Protocol message contained an invalid tag (zero).
at io.protostuff.ProtobufException.invalidTag(ProtobufException.java:106) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ByteArrayInput.readFieldNumber(ByteArrayInput.java:253) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.runtime.RuntimeSchema.mergeFrom(RuntimeSchema.java:457) ~[protostuff-runtime-1.5.9.jar:1.5.9]
at io.protostuff.IOUtil.mergeFrom(IOUtil.java:45) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ProtobufIOUtil.mergeFrom(ProtobufIOUtil.java:103) ~[protostuff-core-1.5.9.jar:1.5.9]
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:90) ~[classes/:?]
... 25 more

客户端的解码handler内容如

package com.miguantech.test.client.net.tcp;

import com.miguantech.common.netty.GameCodecer;
import com.miguantech.common.protocol.message.Message;
import com.miguantech.test.LocalLoggerUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.Arrays;
import java.util.List;

public class ClientDecodeHandler extends ByteToMessageDecoder {

// @Override
// protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List out) throws Exception {
// if (buffer.readableBytes() >= 8) {
//
// System.out.println("decode in: " + buffer.toString());
// // 记录包头开始的index
// int beginReader;
// // 获取包头开始的index
// beginReader = buffer.readerIndex();
// // 标记包头开始的index
// buffer.markReaderIndex();
//
//
// // 消息的长度
// int length = buffer.readInt();
// int code = buffer.readInt();
// // 判断请求数据包数据是否到齐
// if (buffer.readableBytes() // // 还原读指针
// buffer.readerIndex(beginReader);
// return;
// }
//
// Message decode = GameCodecer.decode(buffer, code, length);
// if (decode == null) {
LocalLoggerUtils.info("decode fail code:{}", code);
// buffer.readerIndex(beginReader);
// return;
// }
// out.add(decode);
buffer.resetReaderIndex();
//
// }
// }


@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {if(in.readableBytes() <8){return;
}
System.out.println("decode in: " &#43; in.toString());// System.out.println("decode in: " &#43; Arrays.toString(in.array()));int begin &#61; in.readerIndex();in.markReaderIndex();int length &#61; in.readInt();int code &#61; in.readInt();if(in.readableBytes() }
Message msg &#61; GameCodecer.decodeRec(in, code, length);if(msg &#61;&#61; null){
System.out.println("gdddd");in.resetReaderIndex();return;
}out.add(msg);
}
}

自定义的编码规则如下GameCoderc

package com.miguantech.common.netty;

import com.miguantech.MessageDispatcher;
import com.miguantech.common.protocol.message.Message;
import com.miguantech.common.protocol.message.SCMessage;
import com.miguantech.common.utils.LoggerUtils;
import com.miguantech.common.utils.SerializationUtil;
import com.miguantech.protocol.sys.CSLogin;

import io.netty.buffer.ByteBuf;

import java.util.Arrays;

public class GameCodecer {

// ---4---|---4---|---len---|
// ---l---|---code|---data--|
// l&#xff1a;消息总长&#xff0c;用于半包
// len消息体长度&#xff0c; 用于读data

public static void encode(SCMessage message, ByteBuf out) {
System.out.println("bytes: " &#43; Arrays.toString(message.getBytes()));
out.writeBytes(message.getBytes());
}

public static Message decode(ByteBuf in, int code, int len) {
try {
byte[] bytes &#61; new byte[len];
Class extends Message> clazz &#61; MessageDispatcher.getClazz(code);
if (clazz &#61;&#61; null) {
return null;
}
in.readBytes(bytes, 0, len);
Message deserialize &#61; SerializationUtil.deserialize(bytes, len, clazz);
return deserialize;
} catch (Exception e) {
LoggerUtils.errorLogger.error("decode", e);
}
return null;
}

public static Message decodeRec(ByteBuf buf, int code, int len){
// int dataLen &#61; buf.readableBytes();
Class extends Message> clazz &#61; MessageDispatcher.getClazz(code);
if(clazz &#61;&#61; null){
return null;
}
byte[] data &#61; new byte[len];
buf.readBytes(data, 0, len);
System.out.println("data: " &#43; Arrays.toString(data));
try{
return SerializationUtil.deserialize(data, clazz);
}catch (Exception e){
LoggerUtils.errorLogger.error("decode", e);
}
return null;
}

}

序列化工具用的是protoStuff, 代码如下SerializeUtil

package com.miguantech.common.utils;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.protostuff.ProtostuffIOUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.objenesis.Objenesis;
import org.springframework.objenesis.ObjenesisStd;

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtobufIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;

public class SerializationUtil {

private static Map, Schema>> cachedSchema &#61; new ConcurrentHashMap<>();private static Objenesis objenesis &#61; new ObjenesisStd(true);private static final ThreadLocal buffer &#61; new ThreadLocal();private SerializationUtil() {
}
public static void initSchema(Class> cls) {if (!ArrayUtils.isEmpty(cls.getFields())) {
RuntimeSchema> createFrom &#61; RuntimeSchema.createFrom(cls);
cachedSchema.put(cls, createFrom);
}
}
&#64;SuppressWarnings("unchecked")
public static Schema getSchema(Classcls) {
Schema schema &#61; (Schema) cachedSchema.get(cls);if (schema &#61;&#61; null) {
schema &#61; RuntimeSchema.createFrom(cls);if (schema !&#61; null) {
cachedSchema.put(cls, schema);
}
}
return schema;
}
&#64;SuppressWarnings("unchecked")
public static byte[] serialize(T obj) {
Class cls &#61; (Class) obj.getClass();
LinkedBuffer linkedBuffer &#61; buffer.get();if(linkedBuffer &#61;&#61; null){
linkedBuffer &#61; LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
buffer.set(linkedBuffer);
}try {
Schema schema &#61; getSchema(cls);
return ProtobufIOUtil.toByteArray(obj, schema, linkedBuffer);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
linkedBuffer.clear();
}
}
public static T deserialize(byte[] in, int len, Class cls) {try {
T obj &#61; null;if (!ArrayUtils.isEmpty(cls.getDeclaredFields())) {
obj &#61; (T) objenesis.newInstance(cls);if(len > 0){
Schema schema &#61; getSchema(cls);ProtobufIOUtil.mergeFrom(in, 0, len, obj, schema);
}
}else{
obj &#61; cls.newInstance();
}
return obj;
} catch (Exception e) {System.out.println("byte in: " &#43; Arrays.toString(in));
throw new IllegalStateException(e.getMessage() &#43; "&#xff0c;反序列化类发生异常&#xff1a;" &#43; cls.getSimpleName()
&#43; "&#xff0c;byte数组长度&#xff1a;" &#43; in.length &#43; "&#xff0c;len&#xff1a;" &#43; len, e);
}
}
public static T deserialize(byte[] bytes, Class cls){try {
T obj &#61; cls.newInstance();
Schema schema &#61; getSchema(cls);ProtobufIOUtil.mergeFrom(bytes, obj, schema);
return obj;
} catch (Exception e) {System.out.println("byte in: " &#43; Arrays.toString(bytes));
throw new IllegalStateException(e.getMessage() &#43; "反序列化类发生异常", e);
}
}
public static T deserialize(byte[] in, int offset, int len, Class cls) {try {
T obj &#61; null;if (!ArrayUtils.isEmpty(cls.getDeclaredFields())) {
obj &#61; (T) objenesis.newInstance(cls);if(len > 0){
Schema schema &#61; getSchema(cls);ProtobufIOUtil.mergeFrom(in, offset, len, obj, schema);
}
}else{
obj &#61; cls.newInstance();
}
return obj;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}

贼懵逼

到现在也还没找到方法解决

handle中注释的同名函数不是我写的, 当然也会报错

解决

接上一篇

目前的新进展 先看错误日志

以下是两次连续的错误

[15:32:39:383] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:54) - /127.0.0.1:61085 1 decode error data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[15:32:39:383] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:55) - /127.0.0.1:61085 1 decode error buf schema: PooledUnsafeDirectByteBuf(ridx: 30, widx: 30, cap: 512)
[15:32:39:383] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:56) - /127.0.0.1:61085 1 decode error code: 1532 len: 22
[15:32:39:384] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:57) - /127.0.0.1:61085 1 decode error
java.lang.IllegalStateException: Reading from a byte array threw an IOException (should never happen).反序列化类发生异常
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:93) ~[classes/:?]
at com.miguantech.common.netty.GameCodecer.decodeRec(GameCodecer.java:52) [classes/:?]
at com.miguantech.test.client.net.tcp.ClientDecodeHandler.decode(ClientDecodeHandler.java:88) [test-classes/:?]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:503) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:442) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:281) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:287) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:700) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191]
Caused by: java.lang.RuntimeException: Reading from a byte array threw an IOException (should never happen).
at io.protostuff.IOUtil.mergeFrom(IOUtil.java:54) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ProtobufIOUtil.mergeFrom(ProtobufIOUtil.java:103) ~[protostuff-core-1.5.9.jar:1.5.9]
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:90) ~[classes/:?]
... 25 more
Caused by: io.protostuff.ProtobufException: Protocol message contained an invalid tag (zero).
at io.protostuff.ProtobufException.invalidTag(ProtobufException.java:106) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ByteArrayInput.readFieldNumber(ByteArrayInput.java:253) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.runtime.RuntimeSchema.mergeFrom(RuntimeSchema.java:457) ~[protostuff-runtime-1.5.9.jar:1.5.9]
at io.protostuff.IOUtil.mergeFrom(IOUtil.java:45) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ProtobufIOUtil.mergeFrom(ProtobufIOUtil.java:103) ~[protostuff-core-1.5.9.jar:1.5.9]
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:90) ~[classes/:?]
... 25 more
[15:32:39:389] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:90) - /127.0.0.1:61085 decode error in schema : PooledUnsafeDirectByteBuf(ridx: 30, widx: 30, cap: 512)
[15:32:39:389] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:91) - /127.0.0.1:61085 decode error , code: 1532 length: 22
[15:32:39:389] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:93) - /127.0.0.1:61085 decode error resetIndex after now: 0
[15:32:39:389] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:94) - /127.0.0.1:61085 已经接收个数num: 37
[0, 0, 0, 22, 0, 0, 5, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[15:32:39:411] [work-1-78] [INFO] - (LocalLoggerUtils.java:23) - WeightRandomSelectorImpl choose ActionBulletin
[15:32:39:411] [work-1-78] [INFO] - (LocalLoggerUtils.java:21) - 992816132391960587&#61;&#61;&#61;》要不要执行:true
[15:32:39:417] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:54) - /127.0.0.1:61085 1 decode error data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[15:32:39:418] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:55) - /127.0.0.1:61085 1 decode error buf schema: PooledUnsafeDirectByteBuf(ridx: 30, widx: 60, cap: 512)
[15:32:39:418] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:56) - /127.0.0.1:61085 1 decode error code: 1532 len: 22
[15:32:39:418] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:57) - /127.0.0.1:61085 1 decode error
java.lang.IllegalStateException: Reading from a byte array threw an IOException (should never happen).反序列化类发生异常
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:93) ~[classes/:?]
at com.miguantech.common.netty.GameCodecer.decodeRec(GameCodecer.java:52) [classes/:?]
at com.miguantech.test.client.net.tcp.ClientDecodeHandler.decode(ClientDecodeHandler.java:88) [test-classes/:?]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:503) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:442) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:281) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:287) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:700) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-all-4.1.43.Final.jar:4.1.43.Final]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191]
Caused by: java.lang.RuntimeException: Reading from a byte array threw an IOException (should never happen).
at io.protostuff.IOUtil.mergeFrom(IOUtil.java:54) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ProtobufIOUtil.mergeFrom(ProtobufIOUtil.java:103) ~[protostuff-core-1.5.9.jar:1.5.9]
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:90) ~[classes/:?]
... 25 more
Caused by: io.protostuff.ProtobufException: Protocol message contained an invalid tag (zero).
at io.protostuff.ProtobufException.invalidTag(ProtobufException.java:106) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ByteArrayInput.readFieldNumber(ByteArrayInput.java:253) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.runtime.RuntimeSchema.mergeFrom(RuntimeSchema.java:457) ~[protostuff-runtime-1.5.9.jar:1.5.9]
at io.protostuff.IOUtil.mergeFrom(IOUtil.java:45) ~[protostuff-core-1.5.9.jar:1.5.9]
at io.protostuff.ProtobufIOUtil.mergeFrom(ProtobufIOUtil.java:103) ~[protostuff-core-1.5.9.jar:1.5.9]
at com.miguantech.common.utils.SerializationUtil.deserialize(SerializationUtil.java:90) ~[classes/:?]
... 25 more
[15:32:39:418] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:90) - /127.0.0.1:61085 decode error in schema : PooledUnsafeDirectByteBuf(ridx: 30, widx: 60, cap: 512)
[15:32:39:419] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:91) - /127.0.0.1:61085 decode error , code: 1532 length: 22
[15:32:39:419] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:93) - /127.0.0.1:61085 decode error resetIndex after now: 0
[15:32:39:419] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:94) - /127.0.0.1:61085 已经接收个数num: 37
[0, 0, 0, 22, 0, 0, 5, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 5, -4, 8, 7, 16, 0, 26, 16, -26, -75, -117, -24, -81, -107, -27, -123, -84, -27, -111, -118, 45, 45, 45, 49]
[15:32:39:417] [work-1-78] [INFO] - (LocalLoggerUtils.java:21) - 992816132391960587&#61;&#61;&#61;》ConditionIsLogin

ClientDecodeHandler

package com.miguantech.test.client.net.tcp;

import com.miguantech.common.netty.GameCodecer;
import com.miguantech.common.protocol.message.Message;
import com.miguantech.common.utils.LoggerUtils;
import com.miguantech.test.LocalLoggerUtils;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class ClientDecodeHandler extends ByteToMessageDecoder {

private static final Logger logger &#61; LoggerFactory.getLogger(ClientDecodeHandler.class);

// 测试,key: localAddress, value: 从gameServer socket中已经接收的个数
// private static final TObjectIntHashMap localAddressNumMap &#61; new TObjectIntHashMap<>();
private static final HashMap localAddressNumMap &#61; new HashMap<>();
&#64;Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {if(in.readableBytes() <8){
return;
}
String localAdd &#61; ctx.channel().localAddress().toString();in.markReaderIndex();int begin &#61; in.readerIndex();int length &#61; in.readInt();int code &#61; in.readInt();if((length &#61;&#61; 0 && code &#61;&#61; 0) || in.readableBytes() return;
}
Message msg &#61; GameCodecer.decodeRec(in, code, length, localAdd);if(msg &#61;&#61; null){LoggerUtils.errorLogger.error(localAdd &#43; " decode error in schema : " &#43; in.toString());LoggerUtils.errorLogger.error(localAdd &#43; " decode error , code: " &#43; code &#43; " length: " &#43; length);in.resetReaderIndex();LoggerUtils.errorLogger.error(localAdd &#43; " decode error resetIndex after now: " &#43; in.readerIndex());LoggerUtils.errorLogger.error(localAdd &#43; " 已经接收个数num: " &#43; getNumByAddress(localAdd));int wLen &#61; in.writerIndex();
byte[] testByte &#61; new byte[wLen];in.getBytes(begin, testByte);System.out.println(Arrays.toString(testByte));
return;
}
out.add(msg);
updateNumByAddress(localAdd);
}
public static int getNumByAddress(String address){if(localAddressNumMap.containsKey(address)){
return localAddressNumMap.get(address);
}
return 0;
}
public static void addNumByAddress(String address, int add){int oldNum &#61; getNumByAddress(address);
localAddressNumMap.put(address, oldNum &#43; add);
}
public static void updateNumByAddress(String address){
addNumByAddress(address, 1);
}
public static void removeAddress(String address){
localAddressNumMap.remove(address);
}
public static HashMap getChannelIdNumMap(){
return localAddressNumMap;
}
}

每次出现错误时, 我这边已经将readerIndex重置为原来的开始读的那个位置

并将当前已经写入的字节数据打印出来

int wLen &#61; in.writerIndex();
byte[] testByte &#61; new byte[wLen];
in.getBytes(begin, testByte);

System.out.println(Arrays.toString(testByte));

第一次打印如下

[0, 0, 0, 22, 0, 0, 5, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

第二次打印如下

[0, 0, 0, 22, 0, 0, 5, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 5, -4, 8, 7, 16, 0, 26, 16, -26, -75, -117, -24, -81, -107, -27, -123, -84, -27, -111, -118, 45, 45, 45, 49]

此前再说明下目前构造的包结构如下

---4---|---4---|---x---|
---a---|---b---|---c---|

首先
上面一行表示字节长度, 4表示4个字节长度

a: 数据c的长度数值, 占4个字节
b: 协议名字对应的int值, 占4个字节
c: 数据 字节数据

总的包字节个数 &#61; 4 &#43; 4 &#43; x

从上面的输出中

[15:32:39:418] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:55) - /127.0.0.1:61085 1 decode error buf schema: PooledUnsafeDirectByteBuf(ridx: 30, widx: 60, cap: 512)
[15:32:39:418] [nioEventLoopGroup-3-12] [ERROR] - (GameCodecer.java:56) - /127.0.0.1:61085 1 decode error code: 1532 len: 22

也就可以看出第一个错误的包结构是正常的, 结构是正常, 所以会被认为是一个完整的包

故放进入进行解码解析

放进来后

public static Message decodeRec(ByteBuf buf, int code, int len, String localAdd){
Class extends Message> clazz &#61; MessageDispatcher.getClazz(code);
if(clazz &#61;&#61; null){
LoggerUtils.errorLogger.error(localAdd &#43; " clazz空 code: " &#43; code);
return null;
}
byte[] data &#61; new byte[len];
buf.readBytes(data, 0, len);
try{
return SerializationUtil.deserialize(data, clazz);
}catch (Exception e){
LoggerUtils.errorLogger.error(localAdd &#43; " 1 decode error data: " &#43; Arrays.toString(data));
LoggerUtils.errorLogger.error(localAdd &#43; " 1 decode error buf schema: " &#43; buf.toString());
LoggerUtils.errorLogger.error(localAdd &#43; " 1 decode error code: " &#43; code &#43; " len: " &#43; len);
LoggerUtils.errorLogger.error(localAdd &#43; " 1 decode error", e);
}
return null;
}

发现读取到的data这个字节数组里面的字节都是0, 故在SerializationUtil中反序列化时就报错了

SerializationUtil

package com.miguantech.common.utils;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.protostuff.ProtostuffIOUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.objenesis.Objenesis;
import org.springframework.objenesis.ObjenesisStd;

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtobufIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;

public class SerializationUtil {

private static Map, Schema>> cachedSchema &#61; new ConcurrentHashMap<>();private static Objenesis objenesis &#61; new ObjenesisStd(true);private static final ThreadLocal buffer &#61; new ThreadLocal();private SerializationUtil() {
}
&#64;SuppressWarnings("unchecked")public static Schema getSchema(Class cls) {
Schema schema &#61; (Schema) cachedSchema.get(cls);if (schema &#61;&#61; null) {
schema &#61; RuntimeSchema.createFrom(cls);if (schema !&#61; null) {
cachedSchema.put(cls, schema);
}
}return schema;
}
&#64;SuppressWarnings("unchecked")public static byte[] serialize(T obj) {
Class cls &#61; (Class) obj.getClass();
LinkedBuffer linkedBuffer &#61; buffer.get();if(linkedBuffer &#61;&#61; null){
linkedBuffer &#61; LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
buffer.set(linkedBuffer);
}try {
Schema schema &#61; getSchema(cls);return ProtobufIOUtil.toByteArray(obj, schema, linkedBuffer);
} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);
} finally {
linkedBuffer.clear();
}
}public static T deserialize(byte[] bytes, Class cls){try {
T obj &#61; cls.newInstance();
Schema schema &#61; getSchema(cls);
ProtobufIOUtil.mergeFrom(bytes, obj, schema);return obj;
} catch (Exception e) {throw new IllegalStateException(e.getMessage() &#43; "反序列化类发生异常", e);
}
}
}

往后看, 可以看到第二个报错的位置, 打印的两个包中第二个包时正常的

这不免怀疑是服务器端发送的包有误, 序列化编码的时候出错..?

但是

我当前的测试场景是500个机器人, 加一个自己的测试账号

进行跑马灯功能的测试,会发现就只有一个账号报错(即所有的报错都是这个账号进程)

[15:32:39:389] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:93) - /127.0.0.1:61085 decode error resetIndex after now: 0
[15:32:39:389] [nioEventLoopGroup-3-12] [ERROR] - (ClientDecodeHandler.java:94) - /127.0.0.1:61085 已经接收个数num: 37

String localAdd &#61; ctx.channel().localAddress().toString();

LoggerUtils.errorLogger.error(localAdd &#43; " decode error resetIndex after now: " &#43; in.readerIndex());
LoggerUtils.errorLogger.error(localAdd &#43; " 已经接收个数num: " &#43; getNumByAddress(localAdd));

socket进程标识/127.0.0.1:61085的这个账户会出现问题

而别的账户进程都收发都正常

服务器发送的包

15:32:41.673 [work-1-2] INFO (SCMessage.java:28) - sc : SCPushMarquee :[8, 7, 16, 0, 26, 16, -26, -75, -117, -24, -81, -107, -27, -123, -84, -27, -111, -118, 45, 45, 45, 49]
bytes: [0, 0, 0, 22, 0, 0, 5, -4, 8, 7, 16, 0, 26, 16, -26, -75, -117, -24, -81, -107, -27, -123, -84, -27, -111, -118, 45, 45, 45, 49]

测试账号的接收信息如图

2d3f7d3e0dfc436e36f95a4f3047e4fc.png

出错账户信息

4eecb090de4a7dd36e745a433c1c2b4b.png

错误

ea71fe61863b1e308204a99d62ca1e89.png


update-1

进展

现在解决了

问题出在服务器端序列化操作, 一个对象进行重复性的序列化操作导致的

在高并发的情况下容易出现

如目前修改后的代码

351b55ab7530fd05bff48cb23a5d75fc.png

我在这里就增加了是否为null的判断

if (bytes !&#61; null){
    return;
}

如果进行这个判断, 就会出现字节数组全为0或者前后混乱的情况, 类似粘包的结构

3a31e0e2219207fad0e02a63d2ac82fe.png

28b368386d585d53334f3826e5cc1f9b.png这里再看下他的调用情况

33353b96c2bc4eba7dfae30b1ca613d4.png

发送SCMessage

public void send(SCMessage sc) {
sc.setSession(this); sc.serialize(); channel.writeAndFlush(sc);}

之后来到

056f338cc4d786ce0a357e8603fc4520.png

GameEncodeHandler是编码操作, 编码其实在serialize()函数中就完成了

GameEncodeHandler是继承MessageToByteEncoder, 它是一个向外发送消息时处理的一个类ChannelOutboundHandlerAdapter

这里就涉及到一个pipeline管道的东西了, 就先不记录了

之所以会走到GameEncodeHandler中来, 是在server中定义用到

f313b4fc8c544b348be84ae300523996.png

ok不扯了




推荐阅读
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
author-avatar
昙檀禅潺_162
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有