dicom协议研究之echo流程分析

创建本地AE
        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]

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值