版权声明:喜欢的点个赞吧!欢迎转载,请注明出处来源,博文地址: https://blog.csdn.net/u012294515/article/details/85259178
需求
项目上遇到一个需求,通过telnet 8000端口获取信息,并对获取到的信息做处理。
Socket 相关知识
先学习下Socket的通信原理
Socket 通信模型
由通信模型可以得出Socket通信步骤:
- 建立服务端ServerSocket和客户端Socket
- 打开连接到Socket的输出输入流
- 按照协议进行读写操作
- 关闭相对应的资源
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,有以下几种方案:
- 服务端
Socket
关闭(Socke.close
) - 服务端
Socket
调用Socket.shutdownOutput
关闭输出流;或者直接关闭OutputStream(out.close)
- 设置读取超时抛出异常(
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 未发送告警,等待中...】");
}
}