精华内容
下载资源
问答
  • javaSocket无法完全接收返回内容

    千次阅读 2017-05-31 10:20:24
    最近在使用Socket通讯时,遇到了接收内容不全的问题:客户端发送请求数据,服务器明明返回了73个字节内容,但客户端有时能全部接收,但有时却只能返回4个字节。 一开始是怀疑服务器返回有问题,但使用调试工具连续...

         最近在使用Socket通讯时,遇到了接收内容不全(返回内容 =  4字节报文长度 + 内容主体)的问题:客户端发送请求数据,服务器明明返回了73个字节内容,但客户端有时能全部接收,但有时却只能返回4个字节。

    一开始是怀疑服务器返回有问题,但使用调试工具连续测试了很多次,结果显示:服务器的确每次都返回了73个字节内容。那很明显了,问题出现在客户端代码上。


    错误现象:


    再来看看调试工具结果:




    让我们来看看客户端代码,调用方法如下:(该方法适用于返回报文前两个字节表示长度的情况:2字节报文长度 + 内容主体)

    public static void test() {
    		SocketClient client = new SocketClient();
    		// 建立socket对象
    		int iret = client.connect("192.168.1.105", 1234);
    		if (iret == 0) {
    			// 发送数据
    			client.write("helloworld".getBytes());
    			// 接收数据
    			byte data[] = client.read();
    			if ((data != null) && (data.length != 0)) {
    				// 处理接收结果
    				Utils.print("响应报文字节数组---->" + Arrays.toString(data));
    			}
    		}
    		
    	}

    SocketClient.java源码:

    public class SocketClient {
    
    	// 存储接收数据
    	private byte m_buffer[] = new byte[0x10000];
    	private Socket m_socket;
    	private InputStream m_inputstream;
    	private OutputStream m_outputstream;
    	private BufferedInputStream m_bufferedinputstream;
    	private BufferedOutputStream m_bufferedoutputstream;
    	private boolean connected;
    
    	public int connect(String host, int port) {
    		try {
    			SocketAddress socketAddress = new InetSocketAddress(host, port);
    			m_socket = new Socket();
    			m_socket.connect(socketAddress, 5000);
    			m_socket.setSoTimeout(60000);
    
    			m_inputstream = m_socket.getInputStream();
    			m_bufferedinputstream = new BufferedInputStream(m_inputstream);
    			m_outputstream = m_socket.getOutputStream();
    			m_bufferedoutputstream = new BufferedOutputStream(m_outputstream);
    		} catch (Exception e) {
    			return -1;
    		}
    		connected = true;
    		return 0;
    	}
    
    	/**
    	 * 发送请求数据
    	 * 
    	 * @param data
    	 * @param start
    	 * @param end
    	 * @return
    	 */
    	public int write(byte data[]) {
    		if (data == null || data.length == 0 || !connected) {
    			return 0;
    		}
    		try {
    			m_bufferedoutputstream.write(data, 0, data.length);
    			m_bufferedoutputstream.flush();
    		} catch (Exception e) {
    			return -1;
    		}
    		return 0;
    	}
    	
    	/**
    	 * 读取返回数据
    	 * 
    	 * @return
    	 */
    	public byte[] read() {
    		if (!connected) {
    			return null;
    		}
    		int len = -1;
    		try {
    			// 长度不正确,有时返回4,有时返回73
    			len = m_bufferedinputstream.read(m_buffer, 0, 0x10000);
    		} catch (Exception e) {
    			len = 0;
    		}
    		if (len != -1) {
    			return null;
    		} else {
    			byte ret[] = new byte[len];
    			for (int i = 0; i < len; i++) {
    				ret[i] = m_buffer[i];
    			}
    			return ret;
    		}
    	}
    }

    通过代码调试,发现问题出现在inputsream.read方法上,java API对其描述如下:

    int  java. io. BufferedInputStream.read( byte[] buffer,  int offset,  int byteCount) throws  IOException

    Reads at most byteCount bytes from this stream and stores them in byte array buffer starting at offset offset. Returns the number of bytes actually read or -1 if no bytes were read and the end of the stream was encountered. If all the buffered bytes have been used, a mark has not been set and the requested number of bytes is larger than the receiver's buffer size, this implementation bypasses the buffer and simply places the results directly into buffer.

    Overrides:  read(...) in  FilterInputStream
    Parameters:
    buffer the byte array in which to store the bytes read.
    offset the initial position in  buffer to store the bytes read from this stream.
    byteCount the maximum number of bytes to store in  buffer.
    Returns:
    the number of bytes actually read or -1 if end of stream.
    Throws:
    IndexOutOfBoundsException - if  offset < 0 or  byteCount < 0, or if  offset + byteCount is greater than the size of  buffer.
    IOException - if the stream is already closed or another IOException occurs.

    引起错误原因在于:客户端在发送数据后,过快地执行read操作,而这时服务端尚未完全返回全部内容,因此只能读到部分字节。于是换了个思路:

    public class SocketClient {
    
    	private Socket m_socket;
    	private InputStream m_inputstream;
    	private OutputStream m_outputstream;
    	private BufferedInputStream m_bufferedinputstream;
    	private BufferedOutputStream m_bufferedoutputstream;
    	private boolean connected;
    
    	public int connect(String host, int port) {
    		try {
    			SocketAddress socketAddress = new InetSocketAddress(host, port);
    			m_socket = new Socket();
    			m_socket.connect(socketAddress, 5000);
    			m_socket.setSoTimeout(60000);
    
    			m_inputstream = m_socket.getInputStream();
    			m_bufferedinputstream = new BufferedInputStream(m_inputstream);
    			m_outputstream = m_socket.getOutputStream();
    			m_bufferedoutputstream = new BufferedOutputStream(m_outputstream);
    		} catch (Exception e) {
    			return -1;
    		}
    		connected = true;
    		return 0;
    	}
    
    	/**
    	 * 发送请求数据
    	 * 
    	 * @param data
    	 * @param start
    	 * @param end
    	 * @return
    	 */
    	public int write(byte data[]) {
    		if (data == null || data.length == 0 || !connected) {
    			return 0;
    		}
    		try {
    			m_bufferedoutputstream.write(data, 0, data.length);
    			m_bufferedoutputstream.flush();
    		} catch (Exception e) {
    			return -1;
    		}
    		return 0;
    	}
    	
    	/**
    	 * 读取返回数据
    	 * 
    	 * @return
    	 */
    	public byte[] read() {
    		if (!connected) {
    			return null;
    		}
    		try {
    			return readStream(m_bufferedinputstream);
    		} catch (Exception e) {
    			return null;
    		}
    	}
    	
    	/**
    	 * @功能 读取流
    	 * @param inStream
    	 * @return 字节数组
    	 * @throws Exception
    	 */
    	public static byte[] readStream(InputStream inStream) throws Exception {
    		ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
    		byte[] buffer = new byte[1024];
    		int len = -1;
    		while ((len = inStream.read(buffer)) != -1) {
    			outSteam.write(buffer, 0, len);
    		}
    		outSteam.close();
    		inStream.close();
    		return outSteam.toByteArray();
    	}
    	
    	public static void test() {
    		SocketClient client = new SocketClient();
    		// 建立socket对象
    		int iret = client.connect("192.168.1.105", 1234);
    		if (iret == 0) {
    			// 发送数据
    			client.write("helloworld".getBytes());
    			// 接收数据
    			byte data[] = client.read();
    			if ((data != null) && (data.length != 0)) {
    				// 处理接收结果
    				Utils.print("响应报文字节数组---->" + Arrays.toString(data));
    			}
    		}
    		
    	}
    }

    测试通过.....


    另外,可参考以下解决思路:

    protected byte[] readMessage(BufferedInputStream is) throws IOException {
    		//        MyLog.d(TAG,"=======>readMessage--inputStream=" );
    		        int offset = 0;
    		        int messageStartOffset = -1;
    		        int wait = 0;
    		        int messageEndOffset = -1;
    		        int findStartOffset = -1;
    
    		        while(messageEndOffset==-1||(messageEndOffset+2)>offset){
    		            if(is.available()==0){
    		                try {
    		                    Thread.sleep(MESSAGE_WAIT_INTERVAL);
    		                    wait += MESSAGE_WAIT_INTERVAL;
    		                } catch (InterruptedException ex) {
    		                }
    		                if(wait>=MESSAGE_OVERTIME){
    		                //超时错误
    		                    throw new RuntimeException(EXCEPTION_TIMEOUT);
    		                }
    		                continue;
    		            }
    		            
    		            offset += is.read(messageBuffer, offset, is.available());//读出数据
    		            TestMessage.showBytes(messageBuffer, 0, offset, "MESSAGE");
    		            if(messageStartOffset==-1){ //未找到报文头
    		                if(findStartOffset<0)
    		                    findStartOffset = 0;
    		                messageStartOffset = findStartOffset(messageBuffer, findStartOffset, offset);//查找报文头
    		                MyLog.e(TAG, "messageStartOffset="+messageStartOffset);
    		                if(messageStartOffset>=0){//找到报文头
    		                    if(messageStartOffset<2){
    		                        //报文错误
    		                        throw new RuntimeException(EXCEPTION_MSG_PARSE_ERROR);
    		                    }else{
    		                        int iMessageLength = ((messageBuffer[messageStartOffset-2]&0xff)<<8)+
    		                         (messageBuffer[messageStartOffset-1]&0xff);
    		//                        MyLog.e(TAG, "iMessageLength="+iMessageLength);
    		                        int ignoreInvalidLength = messageStartOffset-4;
    		                        messageEndOffset = iMessageLength + ignoreInvalidLength;
    		//                        MyLog.e(TAG, "messageStartOffset="+messageStartOffset);
    		                        MyLog.e(TAG, "messageEndOffset="+messageEndOffset);

    如果想要让程序保证读取到count个字节,最好用以下代码:
    int count = 100;  
    byte[] b = new byte[count];  
    int readCount = 0; // 已经成功读取的字节的个数  
    while (readCount < count) {  
        readCount += inStream.read(b, readCount, count - readCount);  
    }  
    这样就能保证读取100个字节,除非中途遇到IO异常或者到了数据流的结尾情况!



    展开全文
  • 我本地是client端做了一个java socket连接。 但是有大部分服务器给我返回的数据是2段重复的数据是什么意思。 例如 正常返回ABCD 但是服务器返回的是ABCDABCD 服务器端开发说是我本地没有是长连接不是短连接,但是我...
  • java 原生 Socket 接收报文

    千次阅读 2018-09-23 10:37:13
    再按照长度为读取剩余的数据,所以这里的8请根据 实际的返回报文 数据 的约定(比如前6位,那么这里为 new byte[6])  byte[] bytes = new byte[8];  if (8 != is.read(bytes))  throw new Exception("报文头...

    /**

    String ip  服务器ip

    int port, 服务器端口

    byte[] data  发送的数据

    */

    public static byte[] bySocketGetMassage(String ip, int port, byte[] data) throws Exception {
            //客户端
    //1、创建客户端Socket,指定服务器地址和端口
            Socket socket = new Socket(ip, port);
    //        socket.setSoTimeout(30 * 1000);
    //2、获取输出流,向服务器端发送信息
            OutputStream os = socket.getOutputStream();//字节输出流
            os.write(data);
            os.flush();
    //3、获取输入流,并读取服务器端的响应信息
            InputStream is = socket.getInputStream();

           //注意new byte[8] 指的是 整个报文的最前面8位  为长度位   最先读取长度位;再按照长度为读取剩余的数据,所以这里的8请根据 实际的返回报文 数据  的约定(比如前6位,那么这里为 new byte[6])
            byte[] bytes = new byte[8];
            if (8 != is.read(bytes))
                throw new Exception("报文头读取异常");
            int len = Integer.parseInt(new String(bytes));
            System.out.println("读取报文长度:" + len);
            byte[] body = new byte[len];
            if (len != is.read(body))
                throw new Exception("报文长度读取不足");
    //4、关闭资源
            is.close();
            os.close();
            socket.close();
            return  groupBytes(bytes, body);
        }

     public static byte[] groupBytes(byte[]... chars) {
            int size = 0;
            for (byte[] cs : chars)
                size += cs.length;
            byte[] result = new byte[size];
            int index = 0;
            for (byte[] cs : chars) {
                System.arraycopy(cs, 0, result, index, cs.length);
                index += cs.length;
            }
            return result;
        }
        

     

    展开全文
  • 今天学习了java 的网络编程中的 Socket , 就自已写了一个客户端 Socket 和一个服务端 ServerSocket, 测试时发现, 服务端接收到了客户端发送过来的数据, 而客户端没有接收到服务端返回的消息, 下面给出问题代码 ...

    今天学习了java 的网络编程中的 Socket , 就自已写了一个客户端 Socket 和一个服务端 ServerSocket, 测试时发现, 服务端接收到了客户端发送过来的数据, 而客户端没有接收到服务端返回的消息, 下面给出问题代码


    客户端代码

     		Socket socket = new Socket("127.0.0.1", 8888);
            //获取socket中的输出流
            OutputStream os = socket.getOutputStream();
            //在输出流中写入要发送给服务端的数据
            os.write("我是一个努工作,还不粘人的老码农".getBytes());
            //获取socket中的输入流,用来接收服务器返回的数据
            InputStream is = socket.getInputStream();
            int len = 0;
            byte[] b = new byte[1024];
            while((len = is.read(b))!=-1){
                System.out.println(new String(b,0,len));
            }
            socket.close();
        }
    

    服务端代码

           //创建一个服务器socket
            ServerSocket serverSocket = new ServerSocket(8888);
            //获取客户端的socket对象
            Socket accept = serverSocket.accept();
            //获取一个输入流对象(服务器端不会去创建输入输出流对象,它使用的是客户端自已的流对象,给客户端发送消息)
            InputStream is = accept.getInputStream();
            int len = 0;
            byte[] b = new byte[1024];
            System.out.println("接收到客户端发来的消息:");
            while((len = is.read(b))!=-1){
                System.out.println(new String(b,0,len));
            }
            //显示出客户端发来的消息后,给客户端返回一个消息
            //获取输入流对象
            OutputStream os = accept.getOutputStream();
            os.write("服务器大王收到了你的消息,让你去寻山,快去吧".getBytes());
            accept.close();
            serverSocket.close();
    

    以上代码,分别开启服务端和客户端,发现服务器端可以正常显示收到的数据,而客户端不行
    刚开始以为是客户端出了问题, 因为它的结果与预想不符, 后来才发现根本不是人家客户端的事儿,问题出在 服务端的 while((len = is.read(b))!=-1)上面,也就是说 is.read(b) 一直没有返回 -1的值,所以程序就一直在这里循环,卡在这里, 当然也就不会给客户端发送数据了
    那么,现在的问题就是 is.read(b) 为什么 接收完数据还不返回-1呢???
    这是因为,read方法要读到流的结束标记才会返回 -1 (也就是说要关闭流的时候会返回-1),而我们的 inputStream是客户端的, 我们不可能在客户端 就把 InputStram给关闭了, 因为这个流对象, 服务端也要使用,所以 我们的客户端 socket还有一个方法 shutdownOutput(), 禁止此套接字的输出流,通俗点说,就是给输出流加了一个结束标记, 可以使 is.read()在读的时候可以返回 -1;
    所以,客户端的代码中在 os.write()后面要加上一个,结束标记的语句, socket.shutdownOutput(), 它可以添加一个结束标记, 又不至于把 流给关闭

    展开全文
  • Java Socket 发送/接收数据

    万次阅读 2018-11-28 13:52:53
    在上一篇了解过 Java Socket 的两种形式(TCP、UDP)后,本文将继续介绍如何利用 Java Socket 发送和接收数据及其内部原理的实现。 Java Socket 的目的在于实现与其他程序的信息交互,包括发送和接收信息两种主要...
    这是一篇鸽了许久的博客,一直没有补充源码和设计方案。最近决定重新开始写博客,从还清历史债开始!
    

    在上一篇了解过 Java Socket 的两种形式(TCP、UDP)后,本文将继续介绍如何利用 Java Socket 发送和接收数据及其内部原理的实现。
    Java Socket 的目的在于实现与其他程序的信息交互,包括发送和接收信息两种主要操作。进行信息交互的前提在于相互之间具有共同的协议,协议规定了程序之间交互信息的规范和标准。比如 IPv4 和 IPv6 就是定义了如何传输数据的标准,头部的基本结构等信息。

    下面将从一个 Demo 入手讲解如何根据需求设计传输协议,实现程序交互。由于关键目的是 socket 传输,所以方案设计重点放在如何设计协议,实现协议等关键问题上。

    1. 协议设计与实现

    背景信息:为一个学生管理系统设计传输协议。该系统包括客户端和服务端两部分,客户端根据需求发送增、删、改、查相关命令,而服务端根据命令完成相应操作,并反馈信息。学生存储基本信息如下表所示。

    项目类型
    idint
    姓名string
    性别“man” or “woman”
    年龄int

    学生最大范围为 0 ~ 65535,列表每页个数为 5 ~ 10 个。

    1.1 需求分析

    1.1.1 功能需求分析

    • :客户端传入学生姓名、性别和年龄,服务端解析信息,并执行新增操作。
    • :客户端传入学生 id,服务端解析信息,根据 id 执行删除操作。
    • :客户端传入学生 id 和待修改信息,服务端解析信息,根据 id 和修改项执行修改操作。
    • 查询列表:客户端传入 pageNo 和 pageSize ,服务端解析信息,根据 pageNo 和 pageSize 执行分页查找操作。
    • 查询信息:客户端传入 学生 id,服务端解析信息,根据 id 执行查询操作。

    1.1.2 性能需求分析

    在这个问题中,暂不考虑性能需求,但实际业务中需考虑到频繁请求的业务并给出可能边界,具体问题具体分析。

    1.2 协议设计

    对于客户端程序来说,可能会存在不同版本存在不同功能的情况,因此在协议设计的时候,最好预留版本号字段来控制不同版本。这样在业务处理代码里面,可以对不同版本号的程序做处理。在此为了简化问题,发送时按照最少字节数发送,考虑版本号采用 4 位。
    在该问题中可能的操作有 5 种,需要的二进制位至少需要 3 位(23 = 8 > 5)。但在此考虑可能的功能扩展,若使用 3 位存储所需功能,那只剩下 3 个不同功能扩展,后期可能会出现问题,因此考虑设置 4 位,这个放大根据实际情况考虑即可,原则是后期扩展不会出现问题。

    1.2.1 新增

    新增功能时,需要传输学生的姓名、性别、年龄。
    姓名:考虑采用 GBK 编码,每个中文占 2 个字节,名字的最大长度为 4 个汉字(不考虑少数民族,以汉族为例),因此一共占用 8 个字节,不足补 0。
    性别:类别数据,仅含有两类 man 和 woman,故可采用 0 / 1 来分别表示男 / 女,只需要 1 位。
    年龄:年龄的范围 1 ~ 120,因此考虑 7 位二进制数即可。
    由此,一个新增学生的传输格式可表示如下:

    版本号操作类型姓名性别年龄
    4 bit4 bit64 bit1 bit7 bit

    1.2.2 删除

    删除功能时,需要传输学生的 id,系统用户数的预估为 65535 个,因此,占用 16 位,最多可存储 65536 个用户。
    由此,一个删除学生的传输格式可表示如下:

    版本号操作类型id
    4 bit4 bit16 bit

    1.2.3 修改

    修改功能时,需传输学生的 id,待修改的项和内容。id 的传输方式同 1.2.2 删除一节中描述的传输格式。
    对于待修改项和内容的设置可以考虑采用标记位的形式进行。以修改的项目位性别和年龄两个字段为例,在这里只考虑每次只修改一个的情况。
    而对于学生信息的操作,则套用新增命令中的格式,即可实现对单个学生修改的传输。
    此时,修改学生信息的传输格式可表示如下:

    版本号操作类型id标记位年龄 / 性别
    4 bit4 bit16 bit8 bit8 bit

    在此为了简化问题,对年龄和性别均凑到单个字节来考虑,若是为了传输和性能最优考虑,需要根据标记位情况,对后面不同位数的数据做进一步处理。例如,年龄采用后面 7 位存储,性别采用后面 1 位存储。

    1.2.4 查询列表

    查询列表功能时,需传输 pageNo 和 pageSize 参数,其中 pageNo 表示需要查询的页码,pageSize 表示需要查询每页的个数。由于列表每页个数为 5 ~ 10 个,因此 pageSize 的值为 5 ~ 10,即需要4位存储,pageNo 的值为 6554 ~ 13107,即需要 14 位(16384)存储。操作中为了简化问题,将页码和每页个数向上取整到整数字节。
    分页查询传输格式可表示如下:

    版本号操作类型页码每页个数
    4 bit4 bit16 bit8 bit

    1.2.5 查询信息

    查询信息功能时,需传输 id 信息,传输格式如 1.2.2 删除节所示,在此不再赘述。

    2. 代码实现

    2.1 架构设计

    client 与 server 端建立 socket 连接,并将增删改查对应的指令编码(encode)发送到 server 端,server 端在接收到数据后,将指令解码(decode)处理并返回处理结果。
    code structure

    2.2 关键代码讲解

    2.2.1 字节操作类 WBit

    该类在 byte[] 的基础上做封装,主要功能用于合并和解析字节数组。该类的对象中会维护一个 pos 值,用于控制当前读写位置。
    类的方法及功能包括:

    // 构造函数:
    public WBit(int length);
    public WBit(byte[] bytes, int pos);
    
    // 以 length 长度合并字节数组,若长度不够,则前面补 0,pos 的值会 + length
    public void put(byte[] array, int length);
    // 添加 1 个字节到数组中,pos 的值会 + 8
    public void putByte(byte data);
    
    // 获取 length 长度的字节数组,若为前置 0  则自动舍弃,pos 的值会 - length
    public byte[] get(int length);
    // 获取 1 个字节,pos 的值会 - 8
    public byte getByte();
    
    // 根据 length 对 bytes 数组做分割
    public static byte[] cutArrayByLength(byte[] bytes, int length); 
    
    // 获取 byte 数组
    public byte[] getBytes();
    

    其中关键的方法有两个:

    get 方法

        public byte[] get(int length) throws Exception {
            if (0 != length % ByteSize) {
                throw new Exception("Not support non-integer length.");
            }
    
            this.pos -= length;
            int byteIndex = this.pos / ByteSize;
    
            byte[] operateBytes = new byte[length / ByteSize];
            int i = Math.max(0, byteIndex - 1), work = 0;
            for (; i < length / ByteSize; i++) {
                if (bytes[i] != 0) {
                    operateBytes[work++] = bytes[i];
                    bytes[i] = 0;
                }
            }
            System.arraycopy(bytes, i, bytes, 0, this.pos / ByteSize);
    
            return WBit.cutArrayByLength(operateBytes, work);
        }
    

    核心思想:当需要获取字节时,需要从当前位置 pos 往回数 length 个位,然后再往后按照需要读出的字节数复制到字节数组中,并借助 work 指针去掉连续的前置 0,最后再对数据做裁剪,返回结果。

    put 方法

        public void put(byte[] array, int length) throws Exception {
            if (0 != length % ByteSize) {
                throw new Exception("Not support non-integer length.");
            }
    
            int byteIndex = this.pos / ByteSize;
    
            if (array.length < length / ByteSize) {
                int supplyZeroCount = length / ByteSize - array.length;
                for (int i = 0; i < supplyZeroCount; i++) {
                    bytes[i + byteIndex] = (byte)0;
                }
    
                byteIndex += supplyZeroCount;
            }
    
            System.arraycopy(array, 0, bytes, byteIndex, array.length);
            pos += length;
        }
    

    核心思想:判断待添加数组和 length 的关系,若 length 较长,说明需要补充前置 0,否则则直接将数组内容从 pos 处拷贝并修改 pos 即可。

    注:由于 java 对位的操作不是很方便,因此在此简化问题,尽可能把编码的位数凑到整数字节或相邻的编码位数凑到整数字节。

    2.2.2 学生信息编解码 Student

    由于在该系统中,可能会涉及对学生信息的编解码,因此直接在 Student 类上面添加 toBytes和可以直接加载 byte[] 的构造函数,处理逻辑如下所示。

    构造函数

        public Student(byte[] data, boolean containsId) throws Exception {
            WBit wbit = new WBit(data, containsId ? Constant.TotalLength + Constant.IdLength : Constant.TotalLength);
    
            if (containsId) {
                byte[] idRes = wbit.get(Constant.IdLength);
                this.id = ByteBuffer.wrap(idRes).getInt();
            }
    
            this.name = new String(wbit.get(Constant.NameLength), DefaultCharset);
    
            byte sexAndAgeByte = wbit.get(ByteSize)[0];
            this.sex = sexAndAgeByte >> 7 == 0 ? Sex.Male : Sex.Female;
            this.age = sexAndAgeByte & 0x7F;
        }
    

    核心思想:借助 WBit 类对 byte[] 进行读取和解析,这里加了一个对编码时是否包含 id 的特殊处理。考虑到在新增的时候,客户端无法确认 id,因此传的信息应该是不包含 id 信息的,但是在查询列表时,信息需要包含 id 的以区分同样姓名、性别和年龄的学生。这里加了对汉字的处理,采用 gbk 编码以控制名字的字节个数。

    toBytes 方法

        public byte[] toBytes(boolean containsId) throws Exception {
            WBit wbit = new WBit(containsId ? Constant.TotalLength + Constant.IdLength : Constant.TotalLength);
    
            if (containsId) {
                byte[] idBytes = { id.byteValue()};
                wbit.put(idBytes, Constant.IdLength);
            }
    
            wbit.put(name.getBytes(DefaultCharset), Constant.NameLength);
    
            byte sexByte = (byte)(Sex.Male == sex ? 0 : 1);
            byte ageByte = age.byteValue();
            byte sexAndAgeByte = (byte)(sexByte << 7 | ageByte);
    
            wbit.putByte(sexAndAgeByte);
            return wbit.getBytes();
        }
    

    核心思想:借助 WBit 来组合 byte [],对于 sexByte 和 ageByte 的组合处理,采用了位运算,即使用 sexByte 的地位作为 ageByte 的最高位,拼成一个 Byte。

    2.2.3 Instruction 设计

    在指令方面的设计,则考虑采用一个简单的指令工厂,借助 interface 实现。
    首先定义一个 Instruction 的 interface:

    public interface Instruction {
    
        // encode instruction to byte array
        byte[] encode() throws Exception;
    
        // get instruction type
        Integer getInstructionType();
    
        // execute command, not implement yet
        byte[] execute();
    }
    

    然后增、删、改、查等操作均实现该接口,完成编码和执行等相关功能。
    以增加操作为例:

    public class AddInstruction implements Instruction {
        private Student student;
    
        public AddInstruction(Student stu) {
            this.student = stu;
        }
    
        public AddInstruction(byte[] data) throws Exception {
            this.student = new Student(data, false);
        }
    
        @Override
        public byte[] encode() throws Exception {
            return this.student.toBytes(false);
        }
    
        @Override
        public Integer getInstructionType() {
            return 1;
        }
    
        @Override
        public byte[] execute() {
            System.out.println("Execute add student operations for stu: " + student.toString());
            return Response.getResponseBytes(Constant.Success);
        }
    
        public Student getStudent() {
            return student;
        }
    }
    

    上层的工厂类设计如下:

    public class InstructionFactory {
        private Instruction instruction;
        private byte[] bytes;
    
        public InstructionFactory(Instruction instruction) {
            this.instruction = instruction;
        }
    
        public InstructionFactory(byte[] data) throws Exception {
            if (0 == data.length) {
                throw new Exception("Data is empty, can't decode");
            }
    
            bytes = data;
        }
    
        public byte[] getBytes() throws Exception {
            if (null == bytes) {
                byte[] instrBytes = instruction.encode();
    
                byte header = instruction.getInstructionType().byteValue();
                header |= Constant.ProtoctolNumber.byteValue() << 4;
    
                bytes = new byte[instrBytes.length + 1];
                bytes[0] = header;
    
                System.arraycopy(instrBytes, 0, bytes, 1, instrBytes.length);
            }
    
            return bytes;
        }
    
        public Instruction getInstruction() throws Exception {
            if (null == instruction) {
    
                byte header = bytes[0];
                byte[] instrBytes = new byte[bytes.length - 1];
                System.arraycopy(bytes, 1, instrBytes, 0, bytes.length - 1);
    
                int protoctolNumber = header >> 4;
                if (protoctolNumber != Constant.ProtoctolNumber) {
                    System.out.printf("%d %d", protoctolNumber, Constant.ProtoctolNumber);
                    throw new Exception("Protoctol don't match");
                }
    
                int type = header & 0x07;
                switch (type) {
                    case 1:
                        instruction = new AddInstruction(instrBytes);
                        break;
                    case 2:
                        instruction = new DeleteInstruction(instrBytes);
                        break;
                    case 3:
                        instruction = new UpdateInstruction(instrBytes);
                        break;
                    case 4:
                        instruction = new SelectInstruction(instrBytes);
                        break;
                }
            }
    
            return instruction;
        }
    
    }
    

    该类主要用于发送指令时统一添加 header 和接收指令时自动校验版本并将数据传递给不同指令解码,最后再调用执行对应操作(当前版本仅打印)。

    2.2.4 数据传输设计

    主要包括 server 和 client 端,client 端主要功能为将指令转换为 byte[],启动 socket 连接发送到 server 端,等待 server 端处理完成后,再获取相应信息。server 端

    Client 端 - TCPClient

    public class TCPClient {
        private InputStream input;
        private OutputStream output;
        private Socket socket;
    
        TCPClient() throws IOException {
            socket = new Socket(Constant.ServerUrl, Constant.ListenPort);
            input = socket.getInputStream();
            output = socket.getOutputStream();
        }
    
    
        public byte[] sendDataToServer(byte[] data) throws IOException {
            byte[] result;
    
            try {
                output.write(data);
                output.flush();
                socket.shutdownOutput();
    
                byte[] receiveData = new byte[Constant.MaxTranslateSize];
                int receiveCount = input.read(receiveData);
                result = WBit.cutArrayByLength(receiveData, receiveCount);
    
                socket.shutdownInput();
                input.close();
                output.close();
                socket.close();
    
            } catch (IOException e) {
                if (null != input) {
                    try {
                        input.close();
                    } catch (IOException e1) {
                        System.out.println(e1);
                    }
                }
    
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e1) {
                        System.out.println(e1);
                    }
                }
    
                if (null != socket) {
                    try {
                        socket.close();
                    } catch (IOException e1) {
                        System.out.println(e1);
                    }
                }
    
                throw e;
            }
    
            return result;
        }
    }
    

    注意事项:

    • 对于 client 端来说,输出流为发送数据到 server 端,输入流为从远端接收数据。
    • 在正常场景下关闭流的顺序,倒序。
    • 异常场景下的处理,需将原始异常抛出给上层,避免异常丢失的场景。

    **Server 端 **

    public class TCPServer {
    
        private ServerSocket serverSocket;
    
        TCPServer() throws IOException {
            this.serverSocket = new ServerSocket(Constant.ListenPort);
        }
    
        public void run() {
            System.out.println("Listening on port: " + Constant.ListenPort);
            int recvMsgSize;
            Socket clntSock = null;
            InputStream in = null;
            OutputStream out = null;
    
            while (true) {
                try {
                    clntSock = this.serverSocket.accept();
                    SocketAddress clientAddress = clntSock.getRemoteSocketAddress();
                    System.out.println("Handling client from " + clientAddress);
    
                    in = clntSock.getInputStream();
                    byte[] buffer = new byte[Constant.BufferSize];
                    byte[] receiveBytes = new byte[Constant.MaxTranslateSize];
                    int work = 0;
                    while (-1 != (recvMsgSize = in.read(buffer))) {
                        System.arraycopy(buffer, 0, receiveBytes, work, recvMsgSize);
                        work += recvMsgSize;
                    }
    
                    receiveBytes = WBit.cutArrayByLength(receiveBytes, work);
                    clntSock.shutdownInput();
    
                    out = clntSock.getOutputStream();
                    InstructionFactory factory = new InstructionFactory(receiveBytes);
                    Instruction instruction = factory.getInstruction();
    
                    byte[] result = instruction.execute();
    
                    out.write(result);
                    out.flush();
    
                    clntSock.shutdownOutput();
                } catch (Exception e) {
                    System.out.println(e);
                    if (null != out) {
                        try {
                            out.close();
                        } catch (IOException e1) {
                            System.out.println(e1);
                        }
                    }
    
                    if (null != in) {
                        try {
                            in.close();
                        } catch (IOException e1) {
                            System.out.println(e1);
                        }
                    }
    
                    if (null != clntSock) {
                        try {
                            clntSock.close();
                        } catch (IOException e1) {
                            System.out.println(e1);
                        }
                    }
    
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            TCPServer tcpServer = new TCPServer();
            tcpServer.run();
        }
    }
    

    注意事项:

    • server 端的异常处理需要避免因为单个请求处理失败,导致整个 server 挂掉的情况。
    • 对每次的 input 和 output 做处理,包括正常和异常场景。
    • 当前实现为阻塞式,循环处理,若是大量请求发到 server 端时可能会有问题,优化方式可考虑异步队列。

    3. 总结与反思

    在初始设计阶段,没想清楚到底做到什么程度,导致什么东西都想搞,想往上加,结果导致一直拖着拖着,差点拖没了。感谢评论区里小伙伴的无情催促,也算是我的动力之一吧。做工程还是完美主义不可取,这一版的程序虽然不完美,没有 UT,没有精细的异常处理,错误码,没有把传输协议细化到 bit,甚至功能也省掉了一大部分,但好歹是核心部分完成了。姑且算作结束吧,算是对之前未结束的事项一个结果。如果大家对源码感兴趣,想提各种意见或者完善优化这个程序,可以来这里 GitHub,可以互关一波,以后一起写写项目,搞搞技术。

    对 java socket 的简单理解大概就到这里了,后续如果有时间的话,会继续研究一下这块的东西,写点更有意思的东西出来。近期在用的语言是 golang,计划下一个项目用 golang 写个分布式监控的小程序,框架搭了一部分,还在构思中~

    展开全文
  • Java Socket发送和接收的例子 能正确运行 代码有注释
  • java实现socket信息传输后,在进一步实现文件传输的时候经常会发生文件传了出去,但接收方收不到,即生成的文件大小为0字节。这是因为接收的时候没有做好判断导致的。 主要判断模块如下所示: while ((length = s...
  • Java实现Socket发送和接收文件

    千次阅读 2016-12-27 20:49:22
    这是一个简单的包含发送端和接收端的例子。发送端向接收端发送文件名和文件内容,接收端将收到的文件保存在磁盘上。接收端可以同时接收多个发送端传来的文件,但没有处理文件同名的情况。  这个例子中设计了一个...
  • 移动开发 Android Socket Socket发送并接收服务器返回的数据 个人整理 。
  • Java socket接收出现中文乱码

    千次阅读 2014-05-18 14:45:03
    今天写Tcp,发现中文乱码的问题,百度了一下,发现这篇文章,开头两句话就解决了我的问题,读完受益匪浅啊,呵呵,收藏先。 尊重版权:原文地址:... in = new BufferedReader(new InputStreamReader(socket.get
  • java socket多线程 接收xml

    千次阅读 2009-12-30 09:37:00
    java socket多线程 接收xml2009-08-21 17:25这段时间做了个项目,不过觉得一直是我在写,我们总监再改。。看样子自己的代码能力太差了。而且乱七八糟的啦。。现将代码分享一下吧 :是从客户端接收xml进行一定的解析...
  • 既然是长连接就免不了心跳检测,这里使用了一种比较简单的做法:服务端对当前线程计时,如果超过某个时长没有收到任何数据就关闭该线程对应的Socket。代码复制粘贴即可运行。 发送时:将16进制的String转byte[] ...
  • Socket s = new Socket("192.168.218.2", 58100); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String info = null; while(!((info=br.readLine())==null)){
  • 服务端socket往往会使用socket.getInputStream.read()或socket.getInputStream.read(buffer)去读取客户端的请求内容,而且仅当read方法返回-1时,服务端socket才认定已经没有更多的东西可读了,这个在java doc可以...
  • 这是一个简单的包含发送端和接收端的例子。发送端向接收端发送文件名和文件内容,接收端将收到的文件保存在磁盘上。接收端可以同时接收多个发送端传来的文件,但没有处理文件同名的情况。  这个例子中设计了一个...
  • 图解Java服务端Socket建立原理

    千次阅读 2019-11-08 07:11:11
    市面上有关于javasocket教程,但几乎都是把javasocket代码罗列一下,并没有跟读者详细说明清楚为什么要这么写,这么写,每一步背的原理是什么.所以本文目标是想通过一份java Server Socket的实现,一步步解析拆解,...
  • Java Socket探究

    千次阅读 2016-04-16 20:40:38
    普通Socket的用法Java中的网络通信是通过Socket实现的,Socket分为ServerSocket和Socket两大类,ServerSocket用于服务端,可以通过accept方法监听请求,监听到请求后返回SocketSocket用于具体完成数据传输,客户端...
  • Java socket模拟发送和接收HTTP消息

    千次阅读 2014-01-09 17:11:08
    理解:模拟登陆指定网站,登陆成功后,获取返回串中的cookie值用于发起下次请求。 ... import java.io.IOException; import java.io.InputStream; import java.io....import java.net.Socket; impor
  • java实现socket:Sender.java与Receiver.java

    千次阅读 2011-06-29 15:26:00
    1:Sender.javapackage com.capinfotech.network;import java.io.IOException;import java.io.OutputStream;import java.io.PrintWriter;import java.net.Socket;import java.net.UnknownHostException;public
  • java socket 编程,实现客户端和服务端接收信息 #学习笔记/编程/socket tcp/IP四层模型 链路层:数据物理层,光纤,网线之类 网络层:用于将传输的数据进行分组,分配到相应的计算机或端口 传输层:网络之间进行通信...
  • Java Socket发送与接收HTTP消息简单实现
  • 来自java文档 Socket

    千次阅读 2012-05-08 20:36:33
    java.net.Socket 直接已知子类: SSLSocket public class Socketextends Object 此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。 套接字的实际工作由 SocketImpl 类...
  • Java Socket编程

    千次阅读 2017-06-21 14:00:32
    对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket。服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来...
  • import java.net.Socket; import javax.swing.JOptionPane; public class ServerListener extends Thread { public void run (){ try { ServerSocket serverSocket = new ServerSocket(55555); while ...
  • javaSocket应用

    千次阅读 2012-09-17 20:13:28
    Java中,有专门的Socket类来处理用户的请求和响应。利用Socket类的方法,就可以实现两台计算机之间的通讯。这里就介绍一下在Java中如何利用Socket进行网络编程。  在JavaSocket可以理解为客户端或者服务器端...
  • JavaSocket的用法

    千次阅读 2017-10-22 16:29:21
    普通Socket的用法Java中的网络通信是通过Socket实现的Socket分为两类: ServerSocket:用于服务端,可以通过accept方法监听请求,监听到请求后返回SocketSocket:用于具体完成数据传输,客户端直接使用Socket发起...
  • JAVA socket 接收硬件字节数据并解析

    千次阅读 2018-06-13 13:21:50
    一开始我们想到的是按照字节流来接收,但是,C语言中,byte类型没有符号位,最大值位255,java中byte类型带有符号位,最大值为127,问题就出现了,当接收到的字节数据超过127时,会取第一位为符号位,后几位补码,...
  • 【硬件通信】Java Socket怎么发送和接收16进制数据

    万次阅读 多人点赞 2018-11-14 19:52:19
    关键点 ... 接收时:获得数据,然后将byte[]的东西转化成16进制字符串 1 服务端socket public class Server { private static class ClientHandler implements Runnable { private Socket ...
  • Java TCP Socket编程

    2014-04-29 13:58:26
    一、TCP Socket 参考:《Java

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 83,710
精华内容 33,484
关键字:

java接收socket返回

java 订阅