Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.xingshuangs.iot.protocol.s7.service.S7PLCServer Maven / Gradle / Ivy
/*
* MIT License
*
* Copyright (c) 2021-2099 Oscura (xingshuang)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.xingshuangs.iot.protocol.s7.service;
import com.github.xingshuangs.iot.common.buff.ByteReadBuff;
import com.github.xingshuangs.iot.exceptions.SocketRuntimeException;
import com.github.xingshuangs.iot.net.SocketUtils;
import com.github.xingshuangs.iot.net.server.TcpServerBasic;
import com.github.xingshuangs.iot.protocol.s7.enums.*;
import com.github.xingshuangs.iot.protocol.s7.model.*;
import com.github.xingshuangs.iot.protocol.s7.utils.AddressUtil;
import com.github.xingshuangs.iot.utils.BooleanUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
/**
* S7 plc server class.
* S7的PLC服务端
*
* @author xingshuang
*/
@Slf4j
public class S7PLCServer extends TcpServerBasic {
/**
* Locker.
* (数据Map操作锁)
*/
private final Object objLock = new Object();
/**
* Write and read lock.
* (读写锁)
*/
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
/**
* All data for operating.
* (所有数据)
*/
protected final HashMap dataMap = new HashMap<>();
public S7PLCServer() {
this(102);
}
public S7PLCServer(int port) {
this.port = port;
this.dataMap.put("DB1", new byte[65536]);
this.dataMap.put("M", new byte[65536]);
this.dataMap.put("I", new byte[65536]);
this.dataMap.put("Q", new byte[65536]);
this.dataMap.put("T", new byte[65536]);
this.dataMap.put("C", new byte[65536]);
}
/**
* Gets the currently available area.
* (获取目前可用的区域)
*
* @return data areas
*/
public Set getAvailableAreas() {
synchronized (this.objLock) {
return this.dataMap.keySet();
}
}
/**
* Add DB area when needing.
* (添加所需的DB块)
*
* @param dbNumbers db number
*/
public void addDBArea(int... dbNumbers) {
log.debug("Add DB{} to server data area", dbNumbers);
synchronized (this.objLock) {
for (int x : dbNumbers) {
String name = String.format("DB%s", x);
this.dataMap.computeIfAbsent(name, key -> new byte[65536]);
}
}
}
@Override
protected boolean checkHandshake(Socket socket) {
// 校验connect request
S7Data s7Data = this.readS7DataFromClient(socket);
if (!(s7Data.getCotp() instanceof COTPConnection)
|| s7Data.getCotp().getPduType() != EPduType.CONNECT_REQUEST) {
// 客户端[{}]握手失败,不是连接请求
log.error("Client [{}] Handshake failed, not connection request", socket.getRemoteSocketAddress());
return false;
}
S7Data connectConfirm = S7Data.createConnectConfirm(s7Data);
this.write(socket, connectConfirm.toByteArray());
// 校验setup
s7Data = this.readS7DataFromClient(socket);
if (!(s7Data.getCotp() instanceof COTPData)
|| s7Data.getCotp().getPduType() != EPduType.DT_DATA) {
// 客户端[{}]握手失败,不是参数设置
log.error("Client [{}] handshake failed, not parameter setting", socket.getRemoteSocketAddress());
return false;
}
S7Data connectAckDtData = S7Data.createConnectAckDtData(s7Data);
this.write(socket, connectAckDtData.toByteArray());
log.debug("The client [{}] handshake succeeded", socket.getRemoteSocketAddress());
return true;
}
@Override
protected void doClientHandle(Socket socket) {
S7Data req = this.readS7DataFromClient(socket);
if (!(req.getCotp() instanceof COTPData)
|| req.getCotp().getPduType() != EPduType.DT_DATA
|| req.getHeader().getMessageType() != EMessageType.JOB) {
S7Data response = S7Data.createErrorResponse(req, EErrorClass.ERROR_ON_SUPPLIES, 0x8500);
this.write(socket, response.toByteArray());
}
try {
switch (req.getParameter().getFunctionCode()) {
case READ_VARIABLE:
this.readVariableHandle(socket, req);
return;
case WRITE_VARIABLE:
this.writeVariableHandle(socket, req);
return;
default:
S7Data response = S7Data.createErrorResponse(req, EErrorClass.ERROR_ON_SUPPLIES, 0x8500);
this.write(socket, response.toByteArray());
}
} catch (Exception e) {
S7Data response = S7Data.createErrorResponse(req, EErrorClass.ERROR_ON_SERVICE_PROCESSING, 0x8404);
this.write(socket, response.toByteArray());
}
}
/**
* Read data handler.
* (读数据处理)
*
* @param socket socket object
* @param req request data
*/
private void readVariableHandle(Socket socket, S7Data req) {
ReadWriteParameter parameter = (ReadWriteParameter) req.getParameter();
List returnItems = new ArrayList<>();
try {
this.rwLock.readLock().lock();
parameter.getRequestItems().forEach(p1 -> {
RequestItem p = (RequestItem) p1;
// 判定该区域的数据是否存在
String area = AddressUtil.parseArea(p);
if (!this.dataMap.containsKey(area)) {
// 客户端[{}]读取[{}]数据,区域[{}],字节索引[{}],位索引[{}],长度[{}],无该区域地址数据
log.error("Client[{}] read [{}] data, area[{}], byte index[{}], bit index[{}], length[{}], no the address data",
socket.getRemoteSocketAddress(), p.getVariableType(), area, p.getByteAddress(), p.getBitAddress(), p.getCount());
returnItems.add(ReturnItem.createDefault(EReturnCode.OBJECT_DOES_NOT_EXIST));
return;
}
// 提取指定地址的字节数据
byte[] bytes = this.dataMap.get(area);
ByteReadBuff buff = new ByteReadBuff(bytes);
byte[] data;
if (p.getVariableType() == EParamVariableType.BYTE) {
data = buff.getBytes(p.getByteAddress(), p.getCount());
} else {
byte oldData = buff.getByte(p.getByteAddress());
data = BooleanUtil.getValue(oldData, p.getBitAddress()) ? new byte[]{(byte) 0x01} : new byte[]{(byte) 0x00};
}
// 客户端[{}]读取[{}]数据,区域[{}],字节索引[{}],位索引[{}],长度[{}],区域地址数据{}
log.debug("Client[{}] read [{}] data, area[{}], byte index[{}], bit index[{}], length[{}], address data{}",
socket.getRemoteSocketAddress(), p.getVariableType(), area, p.getByteAddress(), p.getBitAddress(), p.getCount(), data);
DataItem dataItem = DataItem.createAckBy(data, p.getVariableType() == EParamVariableType.BYTE ? EDataVariableType.BYTE_WORD_DWORD : EDataVariableType.BIT);
returnItems.add(dataItem);
});
} finally {
this.rwLock.readLock().unlock();
}
S7Data ack = S7Data.createReadWriteResponse(req, returnItems);
this.write(socket, ack.toByteArray());
}
/**
* Write data handler.
* (写入数据处理)
*
* @param socket socket object
* @param req request data
*/
private void writeVariableHandle(Socket socket, S7Data req) {
ReadWriteParameter parameter = (ReadWriteParameter) req.getParameter();
ReadWriteDatum datum = (ReadWriteDatum) req.getDatum();
List dataItems = datum.getReturnItems().stream().map(DataItem.class::cast).collect(Collectors.toList());
List returnItems = new ArrayList<>();
try {
this.rwLock.writeLock().lock();
for (int i = 0; i < parameter.getItemCount(); i++) {
RequestItem p = (RequestItem) (parameter.getRequestItems().get(i));
DataItem d = dataItems.get(i);
// 判定该区域的数据是否存在
String area = AddressUtil.parseArea(p);
if (!this.dataMap.containsKey(area)) {
// 客户端[{}]写入[{}]数据,区域[{}],字节索引[{}],位索引[{}],长度[{}],无该区域地址
log.error("Client[{}] write [{}] data, area[{}], byte index[{}], bit index[{}], length[{}], no the address data",
socket.getRemoteSocketAddress(), p.getVariableType(), area, p.getByteAddress(), p.getBitAddress(), p.getCount());
returnItems.add(ReturnItem.createDefault(EReturnCode.OBJECT_DOES_NOT_EXIST));
continue;
}
// 写入指定地址的数据
byte[] bytes = this.dataMap.get(area);
if (p.getVariableType() == EParamVariableType.BYTE) {
System.arraycopy(d.getData(), 0, bytes, p.getByteAddress(), d.getData().length);
} else {
byte newData = BooleanUtil.setBit(bytes[p.getByteAddress()], p.getBitAddress(), d.getData()[0] == 1);
System.arraycopy(new byte[]{newData}, 0, bytes, p.getByteAddress(), 1);
}
// 客户端[{}]写入[{}]数据,区域[{}],字节索引[{}],位索引[{}],长度[{}],区域地址数据{}
log.debug("Client[{}] write [{}] data, area[{}], byte index[{}], bit index[{}], length[{}], address data{}",
socket.getRemoteSocketAddress(), p.getVariableType(), area, p.getByteAddress(), p.getBitAddress(), p.getCount(), d.getData());
returnItems.add(ReturnItem.createDefault(EReturnCode.SUCCESS));
}
} finally {
this.rwLock.writeLock().unlock();
}
S7Data ack = S7Data.createReadWriteResponse(req, returnItems);
this.write(socket, ack.toByteArray());
}
/**
* Read S7 data from client.
* (读取S7协议的数据)
*
* @param socket socket object
* @return S7Data
*/
private S7Data readS7DataFromClient(Socket socket) {
byte[] data = this.readClientData(socket);
return S7Data.fromBytes(data);
}
/**
* Rewrite read client data, sticky packet data processing
* (重写读取客户端数据,针对粘包的数据处理)
*
* @param socket client socket object
* @return byte array
*/
@Override
protected byte[] readClientData(Socket socket) {
try {
InputStream in = socket.getInputStream();
int firstByte = in.read();
if (firstByte == -1) {
SocketUtils.close(socket);
throw new SocketRuntimeException("The client is disconnected.");
}
// 先获取TPKT
byte[] tpktData = new byte[4];
tpktData[0] = (byte) firstByte;
this.read(socket, tpktData, 1, 3, 1024, 0, true);
TPKT tpkt = TPKT.fromBytes(tpktData);
// 根据内部的长度获取剩余部分内容
byte[] data = new byte[tpkt.getLength()];
System.arraycopy(tpktData, 0, data, 0, tpktData.length);
this.read(socket, data, 4, data.length - 4, 1024, 0, true);
return data;
} catch (IOException e) {
throw new SocketRuntimeException(e);
}
}
}