socket编程(二)——Socket模式

Socket编程注意点:

Socket connect(SocketAddress endpoint, int timeout)方法:连接到服务器,并指定一个超时值。超时值零被解释为无限超时。在建立连接或者发生错误之前,这个方法一直处于阻塞状态。【在编写程序时这个超时值最好设置上】

Socket setSoTimeout方法:Socket的read方法是一个阻塞方法,setSoTimeout设置阻塞的时间,如果超过阻塞时间会引发SocketTimeoutException;

通过Socket的输入流读取数据:是阻塞的;一般会在流的外面套一个BufferInputStream,通过read(byte[] b)read(b, 0, b.length)方法读取指定长度的字节,通过结合包头中指定的包体长度可以解决粘包问题。read(byte[] b)read(b, 0, b.length),通过重复地调用底层流的read方法,尝试读取尽可能多的字节。这种迭代的read会一直继续下去,直到满足一下条件之一:

  ·已经读取了指定的字节数

  ·底层流的read方法返回-1,指示文件末尾

  ·底层流的available方法返回0,指示将阻塞后续的输入请求

 

Socket isClosed()

Socket isConnected()

 

 

其他设置项

Socket setOOBInline(boolean on):启用/禁用OOBINLINE(TCP紧急数据的接收)

Socket setSendBufferSize(int size)

Socket setReceiveBufferSize(int size)

Socket setKeepAlive(boolean on):一种心跳机制,但是不见使用。

Socket setTrafficClass(int tc)

Socket setReuseAddress(boolean on)

Socket setTcpNoDelay(boolean on)

Socket setSoLinger(boolean on, int linger):设置在关闭套接字时,是否立即返回(不等待底层数据发送完毕)

 

例子:实现基本的通讯(发包解包)、请求、心跳机制

服务端代码

package com.tongxun.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 服务器端Socket
 * @author Administrator
 */
public class SocketServer {
    private Map<String, Socket> socketMap = new ConcurrentHashMap<String, Socket>();
    private ServerSocket server; // 服务器端Socket对象
    private int port;//服务端端口
    private int timeout;//连接维持超时
    
    /**
     * 初始化服务器
     * @param port
     * @param timeout
     */
    public SocketServer(int port, int timeout) {
        if(port<=0 || timeout<=0) throw new IllegalArgumentException();
        
        this.port = port;
        this.timeout = timeout;
    }
    
    /**
     * 启动服务器socket
     */
    public void start(){
        try {
            // 创建一个ServerSocket在端口2121监听客户请求
            server = new ServerSocket(this.port);
            System.out.println("Server starts...");
        } catch (Exception e) {
            System.out.println("Can not listen to. " + e);
        }
        
        //接收客户端连接
        while (true) {
            try {
                acceptConn();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 接收客户端连接
     * @throws IOException
     */
    private void acceptConn() throws IOException{
        Socket socket = server.accept();
        socket.setSoTimeout(1000);
        
        StringBuffer sb = new StringBuffer();
        sb.append(socket.getInetAddress().getHostAddress());
        sb.append(":");
        sb.append(socket.getPort());
        
        socketMap.put(sb.toString(), socket);
        
        new SRecvThread(socket, this).start();
    }

    public int getTimeout() {
        return timeout;
    }

    /**
     * 主方法
     * @param args
     */
    public static void main(String[] args) {
        SocketServer server = new SocketServer(2121, 10000);
        server.start();
    }
}

 

package com.tongxun.server;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;

import com.tongxun.util.Message;
import com.tongxun.util.PackageHead;

/**
 * 接收线程
 * @author zhangjc
 */
public class SRecvThread extends Thread {
    
    private Socket socket; // 保存与本线程相关的Socket对象
    private BufferedInputStream inputStream;
    private BufferedOutputStream outputStream;
    private byte[] head = new byte[10];
    private long lastRecv = System.currentTimeMillis();//最近一次接收时间
    private boolean running = true;
    private BusiHandler handler;
    private SocketServer context;
    
    public SRecvThread(Socket socket, SocketServer context) {
        this.socket = socket;
        try {
            this.context = context;
            inputStream = new BufferedInputStream(this.socket.getInputStream(), 1024);
            outputStream = new BufferedOutputStream(this.socket.getOutputStream(), 1024);
            handler = new BusiHandler(outputStream);
            System.err.println(MessageFormat.format("客户端{0}登录系统", this.getConnInfo()));
        } catch (IOException e) {
            e.printStackTrace();
            close();
        }
    }
    
    public void run() {
        try {        
            while (running) {
                recv();
            }
        } catch (Exception e) {
            e.printStackTrace();
            this.close();
        }
    }

    /**
     * 连接关闭和收尾动作
     */
    private void close(){
        try {
            running = false;
            if(inputStream!=null) inputStream.close();
            if(outputStream!=null) outputStream.close();
            if(!socket.isConnected()) socket.close();
            System.out.println("连接断开");
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
    
    private String getConnInfo(){
        return this.socket.getInetAddress().getHostAddress()+":"+this.socket.getPort();
    }
    
    /**
     * 接收处理
     * @return
     * @throws Exception
     */
    private Message recv() throws Exception{
        Message msg = new Message();
        try {
            read(head);
            PackageHead headPack = new PackageHead(head);
            byte[] body = new byte[headPack.getLen()];
            if(headPack.getLen()>0){
                inputStream.read(body);
            }
            msg.setHead(headPack);
            msg.setBody(body);
            
            this.lastRecv = System.currentTimeMillis();
            
            handler.exe(msg);
        } catch (SocketTimeoutException e) {
            if(System.currentTimeMillis()-lastRecv >= context.getTimeout()){
                System.out.println(System.currentTimeMillis()-lastRecv);
                System.out.println("超时");
                this.close();
            }
        } 
        
        return msg;
    }
    
    /**
     * 读取buff长度的数据,如果len为-1则表示连接断开了
     * @param buff
     * @return
     * @throws Exception 
     */
    private int read(byte[] buff) throws Exception{
        int len = inputStream.read(buff);
        if(len==-1) throw new Exception("客户端连接断开");
        return len;
    }
}

 

package com.tongxun.server;

import java.io.BufferedOutputStream;
import java.io.IOException;

import com.tongxun.util.Message;
import com.tongxun.util.PackageHead;

/**
 * 业务处理
 * @author zhangjc
 */
public class BusiHandler {
    private BufferedOutputStream outputStream;
    
    public BusiHandler(BufferedOutputStream outputStream){
        this.outputStream = outputStream;
    }
    
    public void exe(Message msg) throws IOException{
        //心跳包处理
        System.out.println(msg.getHead());
        if(msg.getHead().getType() == 2){
            System.out.println("接收心跳");
            PackageHead retHead = new PackageHead();
            Message retMsg = new Message();
            retHead.setType((short)2);
            retMsg.setHead(retHead);
            retMsg.setBody(new byte[0]);
            
            outputStream.write(retMsg.toBytes());
            outputStream.flush();
        }
        
        //时间请求处理
        if(msg.getHead().getType() == 1){
            System.out.println("接收时间请求");
            PackageHead retHead = new PackageHead();
            Message retMsg = new Message();
            byte[] retBody = String.valueOf(System.currentTimeMillis()).getBytes();
            retHead.setType((short)1);
            retHead.setLen((short)retBody.length);
            retMsg.setHead(retHead);
            retMsg.setBody(retBody);
            
            outputStream.write(retMsg.toBytes());
            outputStream.flush();
        }
    }
}

 

客户端代码

package com.tongxun.client;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

import com.tongxun.util.Message;
import com.tongxun.util.PackageHead;

/**
 * 客户端Socket
 * @author Administrator
 */
public class SocketClient {
    private int port;//服务器端口
    private String address;//服务器地址
    private int connTimeout;//建立连接时超时
    private int timeout;//连接维持超时
    private final int interval = 5000;//心跳包发送间隔
    private final int minTimeout = 9000;//最小超时时间
    private CRecvThread recvThread;//接收线程
    private HearbeatThread hearbeatThread;//心跳包发送线程
    private Socket socket;
    private BufferedInputStream inputStream;
    private BufferedOutputStream outputStream;
    
    /**
     * socket初始化
     * @param address 服务端地址
     * @param port 服务器端口
     * @param connTimeout 建立连接的超时时间
     */
    public SocketClient(String address, int port, int connTimeout, int timeout) {
        socket = new Socket();
        if(address == null || port<=0 || connTimeout<=0 || timeout<=0) throw new IllegalArgumentException();
        this.address = address;
        this.port = port;
        this.connTimeout = connTimeout;
        this.timeout = timeout;
    }
    
    /**
     * 建立连接并开启心跳发送线程和接收线程
     */
    public void start(){
        
        try{
            //在建立连接或发生错误之前,一直处于阻塞状态
            //所以最好设置一个超时时间
            socket.connect(new InetSocketAddress(address, port), connTimeout);
            //设置read方法最长阻塞时间,超过会触发SocketTimeoutException
            socket.setSoTimeout(1000);
            inputStream = new BufferedInputStream(this.socket.getInputStream(), 1024);
            outputStream = new BufferedOutputStream(this.socket.getOutputStream(), 1024);
        }catch(Exception e){
            //提示用户建立连接出错
            System.err.println(e);
            close(e);
        }
        System.err.println("Established a connection...");
        
        //接收线程
        recvThread = new CRecvThread(this);
        recvThread.start();
        
        //心跳发送线程
        hearbeatThread = new HearbeatThread(this);
        hearbeatThread.start();
    }
    
    /**
     * 关闭socket并做相应的收尾处理
     * @param e
     */
    public void close(Exception e){
        if(e!=null) e.printStackTrace();
        if(recvThread!=null) recvThread.close();
        if(hearbeatThread!=null) hearbeatThread.close();
        
        try {
            if(inputStream!=null) inputStream.close();
            if(outputStream!=null) outputStream.close();
            if(socket.isConnected()) socket.close();
            System.out.println("连接断开");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    /**
     * 发送时间请求
     */
    public void sendReq(){
        Message msg = new Message();
        PackageHead head = new PackageHead();
        head.setType((short)1);
        msg.setHead(head);
        msg.setBody(new byte[0]);
        try {
            outputStream.write(msg.toBytes());
            outputStream.flush();
            System.out.println("发送请求");
        } catch (Exception e) {
            close(e);
        }
    }
    
    public BufferedInputStream getInputStream() {
        return inputStream;
    }

    public BufferedOutputStream getOutputStream() {
        return outputStream;
    }

    public int getTimeout() {
        return timeout;
    }
    
    public int getInterval() {
        return interval;
    }

    public int getMinTimeout() {
        return minTimeout;
    }

    /**
     * 主方法
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        SocketClient client = new SocketClient("localhost", 2121, 5000, 10000);
        client.start();
        
        Thread.sleep(5000);
        client.sendReq();
    }
}

 

package com.tongxun.client;

import java.net.SocketTimeoutException;

import com.tongxun.util.Message;
import com.tongxun.util.PackageHead;

/**
 * 回包接收线程
 * @author zhangjc
 */
public class CRecvThread extends Thread {
    private byte[] head = new byte[10];//用来存放包头的缓存
    private long lastRecv = System.currentTimeMillis();//最后一次接收回包的时间
    private boolean running = true;
    private SocketClient context;
    
    public CRecvThread(SocketClient context) {
        this.context = context;
    }

    public void run() {
        try {
            while (running) {
                recv();
            }
        } catch (Exception e) {
            e.printStackTrace();
            context.close(e);
        }
    }

    public void close(){
        running = false;
    }
    
    private Message recv() throws Exception{
        Message msg = new Message();
        try {
            //读取包头
            read(head);
            PackageHead headPack = new PackageHead(head);
            byte[] body = new byte[headPack.getLen()];
            if(headPack.getLen()>0){
                read(body);    
            }
            msg.setHead(headPack);
            msg.setBody(body);
            
            this.lastRecv = System.currentTimeMillis();
            
            //心跳包处理
            if(msg.getHead().getType() == 2){
                System.out.println("心跳");
            }
            
            //时间请求回包
            if(msg.getHead().getType() == 1){
                if(msg.getBody()!=null){
                    System.out.println(new String(msg.getBody()));
                }
            }
            
        } catch (SocketTimeoutException e) {
            //定时触发SocketTimeoutException来判断连接是否超时
            if(System.currentTimeMillis()-lastRecv >= context.getTimeout()){
                context.close(e);
            }
        }
        
        return msg;
    }
    
    /**
     * 读取buff长度的数据,如果len为-1则表示连接断开了
     * @param buff
     * @return
     * @throws Exception 
     */
    private int read(byte[] buff) throws Exception{
        int len = context.getInputStream().read(buff);
        if(len==-1) throw new Exception("服务器连接断开");
        return len;
    }
}

 

package com.tongxun.client;

import com.tongxun.util.Message;
import com.tongxun.util.PackageHead;

/**
 * 心跳发送线程
 * @author zhangjc
 */
public class HearbeatThread extends Thread {
    private SocketClient context;
    private boolean running = true;
    
    public HearbeatThread(SocketClient context) {
        if(context==null){
            throw new IllegalArgumentException("context不能为null");
        }
        if(context.getTimeout() <= context.getMinTimeout()){
            throw new IllegalArgumentException("超时时间不能小于"+context.getMinTimeout());
        }
        
        this.context = context;
    }
    
    public void run() {
        try {
            sleep(context.getInterval());
            while (running) {
                //组包
                Message msg = new Message();
                PackageHead head = new PackageHead();
                head.setType((short)2);
                msg.setHead(head);
                msg.setBody(new byte[0]);
                //发送
                context.getOutputStream().write(msg.toBytes());
                context.getOutputStream().flush();
                
                sleep(context.getInterval());
            }
        } catch (Exception e) {
            context.close(e);
        }
    }
    
    public void close(){
        running = false;
    }
}

 

 

工具类

package com.tongxun.util;

/**
 * 基础数据与字节数组的转化
 * @author zhangjc
 */
public class BytesUtil {
    
    public static short getShort(byte[] b, int index) {  
        return (short) (((b[index + 1] << 8) | b[index + 0] & 0xff));  
    }  
    
    public static void putShort(byte b[], short s, int index) { 
        b[index + 1] = (byte) (s >> 8);  
        b[index + 0] = (byte) (s >> 0);  
    }  
}

 

package com.tongxun.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * 消息结构
 * @author zhangjc
 */
public class Message {
    private PackageHead head;
    private byte[] body;
    
    public PackageHead getHead() {
        return head;
    }
    public void setHead(PackageHead head) {
        this.head = head;
    }
    public byte[] getBody() {
        return body;
    }
    public void setBody(byte[] body) {
        this.body = body;
    }
    
    public byte[] toBytes() throws IOException{
        ByteArrayOutputStream array = new ByteArrayOutputStream();
        array.write(head.toBytes());
        array.write(getBody());
        return array.toByteArray();
    }
}

 

package com.tongxun.util;

/**
 * 消息头
 * 保留4个字节
 * @author zhangjc
 */
public class PackageHead{
    private byte[] bytes = new byte[10];
    
    /**长度*/
    private short len;
    /**数据类型*/
    private short type;
    /**目的地*/
    private short target;
    
    public PackageHead(byte[] bytes){
        this.bytes = bytes;
        
        this.len = BytesUtil.getShort(bytes, 0);
        this.type = BytesUtil.getShort(bytes, 2);
        this.target = BytesUtil.getShort(bytes, 4);
    }
    
    public PackageHead(){}
    
    public byte[] getBytes() {
        return bytes;
    }
    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }
    public short getLen() {
        return len;
    }
    public void setLen(short len) {
        this.len = len;
    }
    public short getType() {
        return type;
    }
    public void setType(short type) {
        this.type = type;
    }
    public short getTarget() {
        return target;
    }
    public void setTarget(short target) {
        this.target = target;
    }
    
    public byte[] toBytes(){
        byte[] ret = new byte[10];
        BytesUtil.putShort(ret, (short)this.len, 0);
        BytesUtil.putShort(ret, (short)this.type, 2);
        BytesUtil.putShort(ret, (short)this.target, 4);
        ret[9] = 124;
        return ret;
    }
}

 

转载于:https://www.cnblogs.com/barker/p/5435573.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值