localNAE = new NetworkApplicationEntity();
localNAE.setNetworkConnection(localConn);
localNAE.setAssociationInitiator(true);
device.setNetworkApplicationEntity(localNAE);
device.setNetworkConnection(localConn);
在open方法中创建远程AE病调用 connect方法
public Association open(AE ae) throws IOException, GeneralSecurityException {
NetworkApplicationEntity remoteAE = new NetworkApplicationEntity();
NetworkConnection remoteConn = new NetworkConnection();
remoteConn.setHostname(ae.getHostName());
remoteConn.setPort(ae.getPort());
remoteAE.setAETitle(ae.getTitle());
remoteAE.setInstalled(true);
remoteAE.setAssociationAcceptor(true);
remoteAE.setNetworkConnection(remoteConn);
List<String> ciphers = ae.getCipherSuites();
LOG.info("Open association to {} url:{} Ciphers:{}", new Object[]{ae.getTitle(), ae, ciphers});
if (ciphers.size() > 0) {
String[] ciphers1 = (String[]) ciphers.toArray(new String[ciphers.size()]);
try {
server.invoke(tlsCfgServiceName, "initTLS",
new Object[]{remoteConn, device, ciphers1},
new String[]{NetworkConnection.class.getName(),
Device.class.getName(), ciphers1.getClass().getName()});
} catch (Exception e) {
log.error("Failed to initialize TLS! AE:"+ae+" ciphers:"+ciphers, e);
throw new IOException("Failed to initialize TLS aet:"+ae.getTitle());
}
} else {
remoteConn.setTlsCipherSuite(new String[0]);
localConn.setTlsCipherSuite(new String[0]);
}
if (bindToCallingAET) {
try {
AE callingAE = this.lookupAEHome().findByTitle(localNAE.getAETitle());
log.info("Try to bind socket to callingAE:"+callingAE+" hostname:"+callingAE.getHostName());
localConn.setHostname(callingAE.getHostName());
log.info("Socket bound to "+callingAE.getHostName());
} catch (Exception x) {
log.warn("Socket can not be bound to IP of calling AET!", x);
}
}
try {
return localNAE.connect(remoteAE, executor, true);
} catch (AAssociateRJ t) {
throw t;
} catch (Throwable t) {
log.error("localNAE.connect failed!",t);
throw new IOException("Failed to establish Association aet:"+ae.getTitle());
}
}
在connect方法中进行了最重要的创建makeAAssociateRQ和协商操作a.negotiate(rq);
NetworkConnection[] remoteConns = remoteAE.getNetworkConnection();
for (int i = 0; i < networkConnection.length; i++) {
NetworkConnection c = networkConnection[i];
if (!networkConnection[i].isInstalled())
continue;
for (int j = 0; j < remoteConns.length; j++) {
NetworkConnection nc = remoteConns[j];
if (nc.isInstalled() && nc.isListening()
&& c.isTLS() == nc.isTLS()) {
AAssociateRQ rq = makeAAssociateRQ(userIdentity, remoteAE);
Socket s = c.connect(nc);
Association a = Association.request(s, c, this,
userIdentity);
executor.execute(a);
a.negotiate(rq);
addToPool(a);
associationAccepted(a);
return a;
}
}
}
在协商处发出request请求sendAssociateRQ(rq);
public AAssociateAC negotiate(AAssociateRQ rq) throws IOException,
InterruptedException {
sendAssociateRQ(rq);
synchronized (this) {
while (state == State.STA5)
wait();
}
checkException();
if (state != State.STA6) {
throw new RuntimeException("unexpected state: " + state);
}
return associateAC;
}
void sendAssociateRQ(AAssociateRQ rq) throws IOException {
try {
state.sendAssociateRQ(this, rq);
} catch (IOException e) {
closeSocket();
throw e;
}
}
void sendAssociateRQ(Association as, AAssociateRQ rq)
throws IOException {
as.writeAssociationRQ(rq);
}
void writeAssociationRQ(AAssociateRQ rq) throws IOException {
associateRQ = rq;
name = rq.getCalledAET() + '(' + serialNo + ")";
setState(State.STA5);
encoder.write(rq);
}
调用encoder的write方法把(AAssociateRQ PDU写进去
public synchronized void write(AAssociateRQ rq)
throws IOException
{
log.info("{}: A-ASSOCIATE-RQ {} << {}", new Object[] { as, rq.getCalledAET(),
rq.getCallingAET() });
log.debug("{}", rq);
write(rq, PDUType.A_ASSOCIATE_RQ, ItemType.RQ_PRES_CONTEXT);
}
此处进行了最重要的头的填充
private void write(AAssociateRQAC rqac, int pdutype, int pcItemType)
throws IOException
{
int pdulen = rqac.length();
if (buf.length < 6 + pdulen)
buf = new byte[6 + pdulen];
pos = 0;
put(pdutype);
put(0);
putInt(pdulen);
putShort(rqac.getProtocolVersion());
put(0);
put(0);
encodeAET(rqac.getCalledAET());
encodeAET(rqac.getCallingAET());
put(rqac.getReservedBytes(), 0, 32);
encodeStringItem(ItemType.APP_CONTEXT, rqac.getApplicationContext());
encodePCs(pcItemType, rqac.getPresentationContexts());
encodeUserInfo(rqac);
writePDU(pdulen);
}
private void writePDU(int pdulen) throws IOException
{
out.write(buf, 0, 6 + pdulen);
out.flush();
pdvpos = 6;
pos = 12;
}
在发送完request请求后就是进行cecho操作
DimseRSP rsp = assoc.cecho();
以下是cecho相关调用
public DimseRSP cecho() throws IOException, InterruptedException {
return cecho(UID.VerificationSOPClass);
}
public DimseRSP cecho(String cuid) throws IOException, InterruptedException {
FutureDimseRSP rsp = new FutureDimseRSP();
PresentationContext pc = pcFor(cuid, null);
DicomObject cechorq = CommandUtils.mkCEchoRQ(ae.nextMessageID(), cuid);
invoke(pc.getPCID(), cechorq, null, rsp, ae.getDimseRspTimeout());
return rsp;
}
private PresentationContext pcFor(String cuid, String tsuid)
throws NoPresentationContextException {
Map<String,PresentationContext> ts2pc = acceptedPCs.get(cuid);
if (ts2pc == null)
throw new NoPresentationContextException("Abstract Syntax "
+ UIDDictionary.getDictionary().prompt(cuid)
+ " not supported");
if (tsuid == null)
return ts2pc.values().iterator().next();
PresentationContext pc = ts2pc.get(tsuid);
if (pc == null)
throw new NoPresentationContextException("Abstract Syntax "
+ UIDDictionary.getDictionary().prompt(cuid)
+ " with Transfer Syntax "
+ UIDDictionary.getDictionary().prompt(tsuid)
+ " not supported");
return pc;
}
public static DicomObject mkCEchoRQ(int msgId, String cuid)
{
DicomObject rq = mkRQ(msgId, C_ECHO_RQ, NO_DATASET);
rq.putString(Tag.AffectedSOPClassUID, VR.UI, cuid);
return rq;
}
private static DicomObject mkRQ(int msgId, int cmdfield, int datasetType)
{
DicomObject rsp = new BasicDicomObject();
rsp.putInt(Tag.MessageID, VR.US, msgId);
rsp.putInt(Tag.CommandField, VR.US, cmdfield);
rsp.putInt(Tag.CommandDataSetType, VR.US, datasetType);
return rsp;
}
void invoke(int pcid, DicomObject cmd, DataWriter data,
DimseRSPHandler rspHandler, int rspTimeout) throws IOException,
InterruptedException {
if (CommandUtils.isResponse(cmd))
throw new IllegalArgumentException("cmd:\n" + cmd);
checkException();
if (!isReadyForDataTransfer())
throw new IllegalStateException(state.toString());
PresentationContext pc = associateAC.getPresentationContext(pcid);
if (pc == null)
throw new IllegalStateException("No Presentation Context with id - "
+ pcid);
if (!pc.isAccepted())
throw new IllegalStateException(
"Presentation Context not accepted - " + pc);
rspHandler.setPcid(pcid);
rspHandler.setMsgId(cmd.getInt(Tag.MessageID));
addDimseRSPHandler(cmd.getInt(Tag.MessageID), rspHandler);
encoder.writeDIMSE(pcid, cmd, data, pc.getTransferSyntax());
rspHandler.setTimeout(System.currentTimeMillis() + rspTimeout);
}
private void addDimseRSPHandler(int msgId, DimseRSPHandler rspHandler)
throws InterruptedException {
synchronized (rspHandlerForMsgId) {
while (maxOpsInvoked > 0
&& rspHandlerForMsgId.size() >= maxOpsInvoked)
rspHandlerForMsgId.wait();
if (isReadyForDataReceive())
rspHandlerForMsgId.put(msgId, rspHandler);
}
}
public void writeDIMSE(int pcid, DicomObject cmd, DataWriter dataWriter,
String tsuid)
throws IOException
{
if (log.isInfoEnabled())
log.info(as.toString() + " << " + CommandUtils.toString(cmd, pcid, tsuid));
if (log.isDebugEnabled()) {
log.debug("Command:\n" + cmd);
if (dataWriter instanceof DataWriterAdapter)
log.debug("Dataset:\n" + ((DataWriterAdapter) dataWriter).getDataset());
}
synchronized (dimseLock)
{
this.th = Thread.currentThread();
maxpdulen = as.getMaxPDULengthSend();
if (buf.length < maxpdulen + 6)
buf = new byte[maxpdulen + 6];
pdvpcid = pcid;
pdvcmd = PDVType.COMMAND;
DicomOutputStream cmdout = new DicomOutputStream(this);
cmdout.writeCommand(cmd);
cmdout.close();
if (dataWriter != null)
{
if (!as.isPackPDV())
{
as.sendPDataTF();
}
else
{
pdvpos = pos;
pos += 6;
}
pdvcmd = PDVType.DATA;
dataWriter.writeTo(this, tsuid);
close();
}
as.sendPDataTF();
this.th = null;
}
}
void sendPDataTF() throws IOException {
try {
state.sendPDataTF(this);
} catch (IOException e) {
closeSocket();
throw e;
}
}
/**
* @see org.dcm4che2.net.State#sendPDataTF(org.dcm4che2.net.Association)
*/
@Override
void sendPDataTF(Association as) throws IOException {
as.writePDataTF();
}
void writePDataTF() throws IOException {
encoder.writePDataTF();
}
public synchronized void writePDataTF()
throws IOException
{
int pdulen = pos - 6;
pos = 0;
put(PDUType.P_DATA_TF);
put(0);
putInt(pdulen);
if (log.isDebugEnabled())
log.debug(as.toString() + " << P-DATA-TF[len=" + pdulen + "]");
writePDU(pdulen);
}
private void putInt(int v)
{
buf[pos++] = (byte) (v >> 24);
buf[pos++] = (byte) (v >> 16);
buf[pos++] = (byte) (v >> 8);
buf[pos++] = (byte) v;
}
private void writePDU(int pdulen) throws IOException
{
out.write(buf, 0, 6 + pdulen);
out.flush();
pdvpos = 6;
pos = 12;
}
最后附上echo的后台运行日志
16:38:27,847 INFO [EchoService] Try to bind socket to callingAE:dicom://DCM4CHEE@localhost:11112 hostname:localhost
16:38:27,847 INFO [EchoService] Socket bound to localhost
16:38:27,848 INFO [Association] Association(2) initiated Socket[addr=localhost/127.0.0.1,port=11112,localport=54798]
16:38:27,848 INFO [ServerImpl] handle - Socket[addr=/127.0.0.1,port=54798,localport=11112]
16:38:27,848 INFO [PDUEncoder] DCM4CHEE(2): A-ASSOCIATE-RQ DCM4CHEE << DCM4CHEE
16:38:27,848 INFO [FsmImpl] Socket[addr=/127.0.0.1,port=54798,localport=11112]
16:38:27,849 INFO [FsmImpl] received AAssociateRQ
appCtxName: 1.2.840.10008.3.1.1.1/DICOM Application Context Name
implClass: 1.2.40.0.13.1.1
implVersion: dcm4che-2.0
calledAET: DCM4CHEE
callingAET: DCM4CHEE
maxPDULen: 16352
asyncOpsWindow: maxOpsInvoked=0, maxOpsPerformed=0
pc-1: as=1.2.840.10008.1.1/Verification SOP Class
ts=1.2.840.10008.1.2/Implicit VR Little Endian
pc-3: as=1.2.840.10008.1.1/Verification SOP Class
ts=1.2.840.10008.1.2.1/Explicit VR Little Endian
ExtNegotiation[sop=1.2.840.10008.1.1/Verification SOP Class, info=00]
16:38:27,849 INFO [FsmImpl] sending AAssociateAC
appCtxName: 1.2.840.10008.3.1.1.1/DICOM Application Context Name
implClass: 1.2.40.0.13.1.1.1
implVersion: dcm4che-1.4.34
calledAET: DCM4CHEE
callingAET: DCM4CHEE
maxPDULen: 16352
asyncOpsWindow: maxOpsInvoked=1, maxOpsPerformed=1
pc-1: 0 - acceptance
ts=1.2.840.10008.1.2/Implicit VR Little Endian
pc-3: 0 - acceptance
ts=1.2.840.10008.1.2.1/Explicit VR Little Endian
16:38:27,850 INFO [Association] DCM4CHEE(2): A-ASSOCIATE-AC DCM4CHEE >> DCM4CHEE
16:38:27,850 INFO [PDUEncoder] DCM4CHEE(2) << 2:C-ECHO-RQ[pcid=3
cuid=1.2.840.10008.1.1/Verification SOP Class]
16:38:27,850 INFO [FsmImpl] received [pc-3] 2:C_ECHO_RQ
class: 1.2.840.10008.1.1/Verification SOP Class
16:38:27,850 INFO [FsmImpl] sending [pc-3] 2:C_ECHO_RSP
class: 1.2.840.10008.1.1/Verification SOP Class
status: 0
16:38:27,851 INFO [PDUDecoder] DCM4CHEE(2) >> 2:C-ECHO-RSP[pcid=3, status=0H
cuid=1.2.840.10008.1.1/Verification SOP Class]
16:38:27,851 INFO [PDUEncoder] DCM4CHEE(2) << A-RELEASE-RQ
16:38:27,851 INFO [FsmImpl] received A-RELEASE-RQ
16:38:27,851 INFO [FsmImpl] sending A-RELEASE-RP
16:38:27,851 INFO [Association] DCM4CHEE(2) >> A-RELEASE-RP
16:38:27,852 INFO [Association] DCM4CHEE(2): close Socket[addr=localhost/127.0.0.1,port=11112,localport=54798]
16:38:27,901 INFO [FsmImpl] closing connection - Socket[addr=/127.0.0.1,port=54798,localport=11112]
16:38:27,901 INFO [ServerImpl] finished - Socket[addr=/127.0.0.1,port=54798,localport=11112]