Java | Socket 通信

版权声明:喜欢的点个赞吧!欢迎转载,请注明出处来源,博文地址: https://blog.csdn.net/u012294515/article/details/85259178

需求

项目上遇到一个需求,通过telnet 8000端口获取信息,并对获取到的信息做处理。

Socket 相关知识

先学习下Socket的通信原理

Socket 通信模型

在这里插入图片描述
由通信模型可以得出Socket通信步骤:

  1. 建立服务端ServerSocket和客户端Socket
  2. 打开连接到Socket的输出输入流
  3. 按照协议进行读写操作
  4. 关闭相对应的资源

Socket 与 ServerSocket 交互图

在这里插入图片描述

ServerSocket 类

ServerSocket 称为服务器套接字的角色是等待来自客户端的连接请求。一旦服务器套接字获得一个连接请求,它创建一个Socket实例来与客户端进行通信。

Socket 类

Socket 类代表一个客户端套接字,即任何时候你想连接到一个远程服务器应用的时候你构造的套接字,通过连接发送和接受字节流。

代码示例

服务端代码示例

package Socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;


public  class  SocketServer extends Thread{
    ServerSocket server = null;
    Socket socket = null;
    public SocketServer(int port) {
        try {
            server = new ServerSocket(port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run(){

        super.run();
        try{
            System.out.println("等待客户端连接...");
            socket = server.accept();
            new sendMessThread().start();//连接并返回socket后,再启用发送消息线程
            System.out.println(socket.getInetAddress().getHostAddress()+" 连接成功...");
            InputStream in = socket.getInputStream();
            int len = 0;
            byte[] buf = new byte[1024];
            while ((len=in.read(buf))!=-1){
                System.out.println("client saying: "+new String(buf,0,len));
            }

        }catch (IOException e){
            e.printStackTrace();
        }
    }


    class sendMessThread extends Thread{
        @Override
        public void run(){
            super.run();
            Scanner scanner=null;
            OutputStream out = null;
            try{
                if(socket != null){
                    scanner = new Scanner(System.in);
                    out = socket.getOutputStream();
                    String in = "";
                    do {
                        in = scanner.next();
                        out.write(("服务器 saying: "+in).getBytes());
                        out.flush();//清空缓存区的内容
                    }while (!in.equals("q"));
                    scanner.close();
                    try{
                    	 //每次写完关闭输出流,否则在client段read会一直等待
                        out.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //函数入口
    public static void main(String[] args) {
        SocketServer server = new SocketServer(6001);
        server.start();
    }
}

客户端代码

package com.broada.cc;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.broada.util.ReadConf;

/**
 * socket客户端
 * @author Sun
 * Create By 2018年12月21日 上午10:30:31
 */
public class SocketClient extends Thread {
	
	private static final Log logger = LogFactory.getLog(SocketClient.class);
	
	/*该标志用于停止线程*/
	private boolean running = false;
	/*该标志用于停止线程*/
	private static String socketIP = "127.0.0.1";
	/*该标志用于停止线程*/
	private static String port = "6001";
    //定义一个Socket对象
    Socket socket = null;
    
    public SocketClient(String host, int port) {
        try {
            //需要服务器的IP地址和端口号,才能获得正确的Socket对象
            socket = new Socket(host, port);
            //设置10s的读取超时时间,用来防止read锁死
            socket.setSoTimeout(10000);
        } catch (UnknownHostException e) {
            logger.error("初始化socket失败:"+e);
        } catch (IOException e) {
            logger.error("异常:"+e);
        }
    }

    public void run() {
        //客户端一连接就可以读取服务器了
    	running = true;
    	while (running) {
    		try {
                // 读Sock里面的数据
    			InputStream s = socket.getInputStream();
                byte[] buf = new byte[1024];
                int len = 0;
                while ((len = s.read(buf)) != -1) {
                	String alert = new String(buf, 0, len);
//                	String alert = new String(buf,"UTF-8");转码
                	String[] alertArr = alert.split("\n");
                	logger.info("内容:【"+ alert+"】");
                }
    		}catch (SocketTimeoutException ste){
    			logger.info("【Socket未发送内容,等待中...】");
        	}catch (SocketException se) {
        		logger.error("【Socket服务端连接断开:"+se+"】");
                try {
            		//关闭连接
        			socket.close();
        		} catch (IOException ie) {
        			 logger.error("【Socket连接关闭失败:"+ie+"】");
        		}
                //服务器断开后30s后重新建立连接
                try {
                	logger.info("【连接断开,30s后socket将进行重连】");
        			Thread.sleep(30000);
        			//客户端重连
                    try {
    					socket = new Socket(socketIP, Integer.parseInt(port));
    					//重新设置读取超时时间
    					socket.setSoTimeout(10000);
    					logger.info("【重连成功,继续接收信息】");
    				} catch (IOException e1) {
    					 logger.error("【socket服务端重连失败:"+e1+"】");
    					 try {
    						 //关闭连接
    						 socket.close();
    					 } catch (IOException ie) {
		        			 logger.error("【socket连接关闭失败:"+ie+"】");
    					 }
    				}
        		} catch (Exception _ex) {
        			logger.error("【线程休眠出错:" + _ex + "】");
        		}
            }catch (Exception ex){
            	logger.error("【接收失败:" + ex + "】");
            }
    	}	
    }
    
    //函数入口
    public static void main(String[] args) {
        //需要服务器的正确的IP地址和端口号
    	logger.info("IP地址为【" + socketIP+ "】发送了连接至程序监听【"+port+"】端口.");
        SocketClient socketClient=new SocketClient(socketIP, Integer.parseInt(port));
        Thread clientThread = new Thread(socketClient, "cc");
        clientThread.start();
    }
}

Socket read阻塞问题

现象

之前的需求对方已提供了服务端,只需要编写客户端的代码,负责接收服务端的信息即可。示例中的客户端代码就是在使用的代码。但是现场遇到一个问题。在客户端接收socket信息几个小时后,就无法再接收到内容,客户端无报错,未断开连接

原因

本地调试时未发现问题,经过几番测试后发现,示例中的服务端代码在out = socket.getOutputStream();使用完后会关闭out.close();,这时客户端的s.read(buf)会返回-1,但现场会在这里一直等待下次信息输入。怀疑是read时长时间等待阻塞引起的。
由于服务端的socket由厂家编写且不在提供维护,无法从服务端下手。

方案

需要使的 read 方法返回-1,有以下几种方案:

  1. 服务端 Socket 关闭(Socke.close
  2. 服务端 Socket 调用 Socket.shutdownOutput 关闭输出流;或者直接关闭OutputStream(out.close)
  3. 设置读取超时抛出异常(socket.setSoTimeout(10000))时,强制跳出read方法。

对于我的情况是无法修改服务端socket代码,采用了第3种方案。详见示例客户端代码

 //需要服务器的IP地址和端口号,才能获得正确的Socket对象
            socket = new Socket(host, port);
            //设置10s的读取超时时间
            socket.setSoTimeout(10000);
 while (running) {
    		try {
                // 读Sock里面的数据
    			InputStream s = socket.getInputStream();
                byte[] buf = new byte[1024];
                int len = 0;
                //如果服务端不关闭输出流,read会有可能阻塞
                while ((len = s.read(buf)) != -1) {
                	String alert = new String(buf, 0, len);
//                	String alert = new String(buf,"UTF-8");
                	String[] alertArr = alert.split("\n");
                	logger.info("原始内容:【"+ alert+"】");
                }
    		}catch (SocketTimeoutException ste){
    		logger.info("【 Socket 未发送告警,等待中...】");
    		}
}

猜你喜欢

转载自blog.csdn.net/u012294515/article/details/85259178