Exemple #1
0
def test_circuit_properties():
    host = '127.0.0.1'
    port = 5555
    prio = 1

    circuit = ca.VirtualCircuit(ca.CLIENT, (host, port), prio)
    circuit.host == host
    circuit.port == port
    circuit.key == ((host, port), prio)

    # CLIENT circuit needs to know its prio at init time
    with pytest.raises(ca.CaprotoRuntimeError):
        ca.VirtualCircuit(ca.CLIENT, host, None)

    # SERVER circuit does not
    srv_circuit = ca.VirtualCircuit(ca.SERVER, (host, port), None)

    # 'key' is not defined until prio is defined
    with pytest.raises(ca.CaprotoRuntimeError):
        srv_circuit.key
    srv_circuit.priority = prio
    srv_circuit.key

    # VersionRequest priority must match prio set above.
    with pytest.raises(ca.LocalProtocolError):
        circuit.send(
            ca.VersionRequest(version=ca.DEFAULT_PROTOCOL_VERSION, priority=2))
Exemple #2
0
def test_broadcaster_checks():
    b = ca.Broadcaster(ca.CLIENT)
    with pytest.raises(ca.LocalProtocolError):
        b.send(
            ca.SearchRequest(name='LIRR',
                             cid=0,
                             version=ca.DEFAULT_PROTOCOL_VERSION))

    b.send(ca.RepeaterRegisterRequest('1.2.3.4'))
    res = ca.RepeaterConfirmResponse('5.6.7.8')
    commands = b.recv(bytes(res), ('5.6.7.8', 6666))
    assert commands[0] == res
    b.process_commands(commands)

    req = ca.SearchRequest(name='LIRR',
                           cid=0,
                           version=ca.DEFAULT_PROTOCOL_VERSION)
    with pytest.raises(ca.LocalProtocolError):
        b.send(req)
    b.send(ca.VersionRequest(priority=0, version=ca.DEFAULT_PROTOCOL_VERSION),
           req)

    res = ca.SearchResponse(port=6666,
                            ip='1.2.3.4',
                            cid=0,
                            version=ca.DEFAULT_PROTOCOL_VERSION)
    addr = ('1.2.3.4', 6666)
    commands = b.recv(bytes(res), addr)
    # see changes to _broadcaster.py.  Apparently rsrv does not always conform
    # to the protocol and include a version response before search responsesq
    # with pytest.raises(ca.RemoteProtocolError):
    #     b.process_commands(commands)
    # commands = b.recv(bytes(ca.VersionResponse(version=ca.DEFAULT_PROTOCOL_VERSION)) + bytes(res), addr)
    b.process_commands(commands)  # this gets both
Exemple #3
0
def make_channel(pv_name, udp_sock, priority, timeout):
    log = logging.LoggerAdapter(logging.getLogger('caproto.ch'),
                                {'pv': pv_name})
    address = search(pv_name, udp_sock, timeout)
    try:
        circuit = global_circuits[(address, priority)]
    except KeyError:

        circuit = global_circuits[(address, priority)] = ca.VirtualCircuit(
            our_role=ca.CLIENT, address=address, priority=priority)

    chan = ca.ClientChannel(pv_name, circuit)
    new = False
    if chan.circuit not in sockets:
        new = True
        sockets[chan.circuit] = socket.create_connection(
            chan.circuit.address, timeout)
        circuit.our_address = sockets[chan.circuit].getsockname()
    try:
        if new:
            # Initialize our new TCP-based CA connection with a VersionRequest.
            send(
                chan.circuit,
                ca.VersionRequest(priority=priority,
                                  version=ca.DEFAULT_PROTOCOL_VERSION),
                pv_name)
            send(chan.circuit, chan.host_name(socket.gethostname()))
            send(chan.circuit, chan.client_name(getpass.getuser()))
        send(chan.circuit, chan.create(), pv_name)
        t = time.monotonic()
        while True:
            try:
                commands = recv(chan.circuit)
                if time.monotonic() - t > timeout:
                    raise socket.timeout
            except socket.timeout:
                raise CaprotoTimeoutError("Timeout while awaiting channel "
                                          "creation.")
            tags = {
                'direction': '<<<---',
                'our_address': chan.circuit.our_address,
                'their_address': chan.circuit.address
            }
            for command in commands:
                if isinstance(command, ca.Message):
                    tags['bytesize'] = len(command)
                    logger.debug("%r", command, extra=tags)
                elif command is ca.DISCONNECTED:
                    raise CaprotoError('Disconnected during initialization')
            if chan.states[ca.CLIENT] is ca.CONNECTED:
                log.info("Channel connected.")
                break

    except BaseException:
        sockets[chan.circuit].close()
        del sockets[chan.circuit]
        del global_circuits[(chan.circuit.address, chan.circuit.priority)]
        raise
    return chan
Exemple #4
0
 async def connect(self):
     async with self._socket_lock:
         self.socket = await socket.create_connection(self.circuit.address)
     # Kick off background loops that read from the socket
     # and process the commands read from it.
     await curio.spawn(self._receive_loop(), daemon=True)
     await curio.spawn(self._command_queue_loop(), daemon=True)
     # Send commands that initialize the Circuit.
     await self.send(
         ca.VersionRequest(version=13, priority=self.circuit.priority))
     host_name = await socket.gethostname()
     await self.send(ca.HostNameRequest(name=host_name))
     client_name = getpass.getuser()
     await self.send(ca.ClientNameRequest(name=client_name))
Exemple #5
0
 async def connect(self):
     async with self._socket_lock:
         self.socket = await trio.open_tcp_stream(*self.circuit.address)
     # Kick off background loops that read from the socket
     # and process the commands read from it.
     self.nursery.start_soon(self._receive_loop)
     self.nursery.start_soon(self._command_queue_loop)
     # Send commands that initialize the Circuit.
     await self.send(ca.VersionRequest(version=ca.DEFAULT_PROTOCOL_VERSION,
                                       priority=self.circuit.priority))
     host_name = socket.gethostname()
     await self.send(ca.HostNameRequest(name=host_name))
     client_name = getpass.getuser()
     await self.send(ca.ClientNameRequest(name=client_name))
Exemple #6
0
    async def search_many(self, *names):
        "Generate, process, and transport search request(s)"
        # We have have already searched for these names recently.
        # Filter `pv_names` down to a subset, `needs_search`.
        needs_search = []
        use_cached_search = defaultdict(list)
        for name in names:
            try:
                address = self.get_cached_search_result(name)
            except KeyError:
                needs_search.append((self.new_id(), name))
            else:
                use_cached_search[address].append(name)

        for addr, names in use_cached_search.items():
            yield (address, names)

        use_cached_search.clear()

        # Generate search_ids and stash them on Context state so they can
        # be used to match SearchResponses with SearchRequests.
        for search_id, name in needs_search:
            self.unanswered_searches[search_id] = name

        results = defaultdict(list)

        while needs_search:
            self.log.debug('Searching for %r PVs....', len(needs_search))
            requests = (ca.SearchRequest(name, search_id,
                                         ca.DEFAULT_PROTOCOL_VERSION)
                        for search_id, name in needs_search)
            for batch in batch_requests(requests, SEARCH_MAX_DATAGRAM_BYTES):
                await self.send(self.ca_server_port,
                                ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION),
                                *batch)

            with trio.move_on_after(1):
                await self.wait_on_new_command()

            results.clear()
            found = [(search_id, name) for search_id, name in needs_search
                     if search_id not in self.unanswered_searches]
            needs_search = [key for key in needs_search
                            if key not in found]
            for search_id, name in found:
                address, timestamp = self.search_results[name]
                results[address].append(name)

            for addr, names in results.items():
                yield (address, names)
Exemple #7
0
def make_channel(pv_name, udp_sock, priority, timeout):
    log = logging.getLogger(f'caproto.ch.{pv_name}.{priority}')
    address = search(pv_name, udp_sock, timeout)
    try:
        circuit = global_circuits[(address, priority)]
    except KeyError:

        circuit = global_circuits[(address, priority)] = ca.VirtualCircuit(
            our_role=ca.CLIENT, address=address, priority=priority)

    chan = ca.ClientChannel(pv_name, circuit)
    new = False
    if chan.circuit not in sockets:
        new = True
        sockets[chan.circuit] = socket.create_connection(
            chan.circuit.address, timeout)

    try:
        if new:
            # Initialize our new TCP-based CA connection with a VersionRequest.
            send(
                chan.circuit,
                ca.VersionRequest(priority=priority,
                                  version=ca.DEFAULT_PROTOCOL_VERSION))
            send(chan.circuit, chan.host_name(socket.gethostname()))
            send(chan.circuit, chan.client_name(getpass.getuser()))
        send(chan.circuit, chan.create())
        t = time.monotonic()
        while True:
            try:
                commands = recv(chan.circuit)
                if time.monotonic() - t > timeout:
                    raise socket.timeout
            except socket.timeout:
                raise CaprotoTimeoutError("Timeout while awaiting channel "
                                          "creation.")
            if chan.states[ca.CLIENT] is ca.CONNECTED:
                log.info('%s connected' % pv_name)
                break
            for command in commands:
                if command is ca.DISCONNECTED:
                    raise CaprotoError('Disconnected during initialization')

    except BaseException:
        sockets[chan.circuit].close()
        del sockets[chan.circuit]
        del global_circuits[(chan.circuit.address, chan.circuit.priority)]
        raise
    return chan
Exemple #8
0
def circuit_pair(request):
    host = '127.0.0.1'
    port = 5555
    priority = 1
    version = 13
    cli_circuit = ca.VirtualCircuit(ca.CLIENT, (host, port), priority)
    buffers_to_send = cli_circuit.send(
        ca.VersionRequest(version=version, priority=priority))

    srv_circuit = ca.VirtualCircuit(ca.SERVER, (host, port), None)
    commands, _ = srv_circuit.recv(*buffers_to_send)
    for command in commands:
        srv_circuit.process_command(command)
    buffers_to_send = srv_circuit.send(ca.VersionResponse(version=version))
    commands, _ = cli_circuit.recv(*buffers_to_send)
    for command in commands:
        cli_circuit.process_command(command)
    return cli_circuit, srv_circuit
Exemple #9
0
def test_broadcaster_checks():
    b = ca.Broadcaster(ca.CLIENT)
    with pytest.raises(ca.LocalProtocolError):
        b.send(ca.SearchRequest(name='LIRR', cid=0, version=13))

    b.send(ca.RepeaterRegisterRequest('1.2.3.4'))
    res = ca.RepeaterConfirmResponse('5.6.7.8')
    commands = b.recv(bytes(res), ('5.6.7.8', 6666))
    assert commands[0] == res
    b.process_commands(commands)

    req = ca.SearchRequest(name='LIRR', cid=0, version=13)
    with pytest.raises(ca.LocalProtocolError):
        b.send(req)
    b.send(ca.VersionRequest(priority=0, version=13), req)

    res = ca.SearchResponse(port=6666, ip='1.2.3.4', cid=0, version=13)
    addr = ('1.2.3.4', 6666)
    commands = b.recv(bytes(res), addr)
    with pytest.raises(ca.RemoteProtocolError):
        b.process_commands(commands)
    commands = b.recv(bytes(ca.VersionResponse(version=13)) + bytes(res), addr)
    b.process_commands(commands)  # this gets both
Exemple #10
0
def test_nonet():
    # Register with the repeater.
    assert not cli_b._registered
    bytes_to_send = cli_b.send(ca.RepeaterRegisterRequest('0.0.0.0'))
    assert not cli_b._registered

    # Receive response
    data = bytes(ca.RepeaterConfirmResponse('127.0.0.1'))
    commands = cli_b.recv(data, cli_addr)
    cli_b.process_commands(commands)
    assert cli_b._registered

    # Search for pv1.
    # CA requires us to send a VersionRequest and a SearchRequest bundled into
    # one datagram.
    bytes_to_send = cli_b.send(ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION),
                               ca.SearchRequest(pv1, 0,
                                                ca.DEFAULT_PROTOCOL_VERSION))

    commands = srv_b.recv(bytes_to_send, cli_addr)
    srv_b.process_commands(commands)
    ver_req, search_req = commands
    bytes_to_send = srv_b.send(
        ca.VersionResponse(ca.DEFAULT_PROTOCOL_VERSION),
        ca.SearchResponse(5064, None, search_req.cid, ca.DEFAULT_PROTOCOL_VERSION))

    # Receive a VersionResponse and SearchResponse.
    commands = iter(cli_b.recv(bytes_to_send, cli_addr))
    command = next(commands)
    assert type(command) is ca.VersionResponse
    command = next(commands)
    assert type(command) is ca.SearchResponse
    address = ca.extract_address(command)

    circuit = ca.VirtualCircuit(our_role=ca.CLIENT,
                                address=address,
                                priority=0)
    circuit.log.setLevel('DEBUG')
    chan1 = ca.ClientChannel(pv1, circuit)
    assert chan1.states[ca.CLIENT] is ca.SEND_CREATE_CHAN_REQUEST
    assert chan1.states[ca.SERVER] is ca.IDLE

    srv_circuit = ca.VirtualCircuit(our_role=ca.SERVER,
                                    address=address, priority=None)

    cli_send(chan1.circuit, ca.VersionRequest(priority=0,
                                              version=ca.DEFAULT_PROTOCOL_VERSION))

    srv_recv(srv_circuit)

    srv_send(srv_circuit, ca.VersionResponse(version=ca.DEFAULT_PROTOCOL_VERSION))
    cli_recv(chan1.circuit)
    cli_send(chan1.circuit, ca.HostNameRequest('localhost'))
    cli_send(chan1.circuit, ca.ClientNameRequest('username'))
    cli_send(chan1.circuit, ca.CreateChanRequest(name=pv1, cid=chan1.cid,
                                                 version=ca.DEFAULT_PROTOCOL_VERSION))
    assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE

    srv_recv(srv_circuit)
    assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE
    srv_chan1, = srv_circuit.channels.values()
    assert srv_chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert srv_chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE

    srv_send(srv_circuit, ca.CreateChanResponse(cid=chan1.cid, sid=1,
                                                data_type=5, data_count=1))
    assert srv_chan1.states[ca.CLIENT] is ca.CONNECTED
    assert srv_chan1.states[ca.SERVER] is ca.CONNECTED

    # At this point the CLIENT is not aware that we are CONNECTED because it
    # has not yet received the CreateChanResponse. It should not be allowed to
    # read or write.
    assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE

    # Try sending a premature read request.
    read_req = ca.ReadNotifyRequest(sid=srv_chan1.sid,
                                    data_type=srv_chan1.native_data_type,
                                    data_count=srv_chan1.native_data_count,
                                    ioid=0)
    with pytest.raises(ca.LocalProtocolError):
        cli_send(chan1.circuit, read_req)

    # The above failed because the sid is not recognized. Remove that failure
    # by editing the sid cache, and check that it *still* fails, this time
    # because of the state machine prohibiting this command before the channel
    # is in a CONNECTED state.
    chan1.circuit.channels_sid[1] = chan1
    with pytest.raises(ca.LocalProtocolError):
        cli_send(chan1.circuit, read_req)

    cli_recv(chan1.circuit)
    assert chan1.states[ca.CLIENT] is ca.CONNECTED
    assert chan1.states[ca.SERVER] is ca.CONNECTED

    # Test subscriptions.
    assert chan1.native_data_type and chan1.native_data_count
    add_req = ca.EventAddRequest(data_type=chan1.native_data_type,
                                 data_count=chan1.native_data_count,
                                 sid=chan1.sid,
                                 subscriptionid=0,
                                 low=0, high=0, to=0, mask=1)
    cli_send(chan1.circuit, add_req)
    srv_recv(srv_circuit)
    add_res = ca.EventAddResponse(data=(3,),
                                  data_type=chan1.native_data_type,
                                  data_count=chan1.native_data_count,
                                  subscriptionid=0,
                                  status=1)

    srv_send(srv_circuit, add_res)
    cli_recv(chan1.circuit)

    cancel_req = ca.EventCancelRequest(data_type=add_req.data_type,
                                       sid=add_req.sid,
                                       subscriptionid=add_req.subscriptionid)

    cli_send(chan1.circuit, cancel_req)
    srv_recv(srv_circuit)

    # Test reading.
    cli_send(chan1.circuit, ca.ReadNotifyRequest(data_type=5, data_count=1,
                                                 sid=chan1.sid,
                                                 ioid=12))
    srv_recv(srv_circuit)
    srv_send(srv_circuit, ca.ReadNotifyResponse(data=(3,),
                                                data_type=5, data_count=1,
                                                ioid=12, status=1))
    cli_recv(chan1.circuit)

    # Test writing.
    request = ca.WriteNotifyRequest(data_type=2, data_count=1,
                                    sid=chan1.sid,
                                    ioid=13, data=(4,))

    cli_send(chan1.circuit, request)
    srv_recv(srv_circuit)
    srv_send(srv_circuit, ca.WriteNotifyResponse(data_type=5, data_count=1,
                                                 ioid=13, status=1))
    cli_recv(chan1.circuit)

    # Test "clearing" (closing) the channel.
    cli_send(chan1.circuit, ca.ClearChannelRequest(sid=chan1.sid, cid=chan1.cid))
    assert chan1.states[ca.CLIENT] is ca.MUST_CLOSE
    assert chan1.states[ca.SERVER] is ca.MUST_CLOSE

    srv_recv(srv_circuit)
    assert srv_chan1.states[ca.CLIENT] is ca.MUST_CLOSE
    assert srv_chan1.states[ca.SERVER] is ca.MUST_CLOSE

    srv_send(srv_circuit, ca.ClearChannelResponse(sid=chan1.sid, cid=chan1.cid))
    assert srv_chan1.states[ca.CLIENT] is ca.CLOSED
    assert srv_chan1.states[ca.SERVER] is ca.CLOSED
Exemple #11
0
def search(pv_name, udp_sock, timeout, *, max_retries=2):
    # Set Broadcaster log level to match our logger.
    b = ca.Broadcaster(our_role=ca.CLIENT)
    b.client_address = safe_getsockname(udp_sock)

    # Send registration request to the repeater
    logger.debug('Registering with the Channel Access repeater.')
    bytes_to_send = b.send(ca.RepeaterRegisterRequest())

    env = get_environment_variables()
    repeater_port = env['EPICS_CA_REPEATER_PORT']

    client_address_list = ca.get_client_address_list()
    local_address = ca.get_local_address()

    try:
        udp_sock.sendto(bytes_to_send, (local_address, repeater_port))
    except OSError as exc:
        raise ca.CaprotoNetworkError(
            f"Failed to send to {local_address}:{repeater_port}") from exc

    logger.debug("Searching for %r....", pv_name)
    commands = (ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION),
                ca.SearchRequest(pv_name, 0, ca.DEFAULT_PROTOCOL_VERSION))
    bytes_to_send = b.send(*commands)
    tags = {
        'role': 'CLIENT',
        'our_address': b.client_address,
        'direction': '--->>>'
    }

    def send_search():
        for dest in client_address_list:
            tags['their_address'] = dest
            b.log.debug('%d commands %dB',
                        len(commands),
                        len(bytes_to_send),
                        extra=tags)
            try:
                udp_sock.sendto(bytes_to_send, dest)
            except OSError as exc:
                host, port = dest
                raise ca.CaprotoNetworkError(
                    f"Failed to send to {host}:{port}") from exc

    def check_timeout():
        nonlocal retry_at

        if time.monotonic() >= retry_at:
            send_search()
            retry_at = time.monotonic() + retry_timeout

        if time.monotonic() - t > timeout:
            raise CaprotoTimeoutError(
                f"Timed out while awaiting a response "
                f"from the search for {pv_name!r}. Search "
                f"requests were sent to this address list: "
                f"{ca.get_address_list()}.")

    # Initial search attempt
    send_search()

    # Await a search response, and keep track of registration status
    retry_timeout = timeout / max((max_retries, 1))
    t = time.monotonic()
    retry_at = t + retry_timeout

    try:
        orig_timeout = udp_sock.gettimeout()
        udp_sock.settimeout(retry_timeout)
        while True:
            try:
                bytes_received, address = udp_sock.recvfrom(ca.MAX_UDP_RECV)
            except socket.timeout:
                check_timeout()
                continue

            check_timeout()

            commands = b.recv(bytes_received, address)
            b.process_commands(commands)
            for command in commands:
                if isinstance(command, ca.SearchResponse) and command.cid == 0:
                    address = ca.extract_address(command)
                    logger.debug('Found %r at %s:%d', pv_name, *address)
                    return address
            else:
                # None of the commands we have seen are a reply to our request.
                # Receive more data.
                continue
    finally:
        udp_sock.settimeout(orig_timeout)
Exemple #12
0
def search(pv_name, udp_sock, timeout, *, max_retries=2):
    # Set Broadcaster log level to match our logger.
    b = ca.Broadcaster(our_role=ca.CLIENT)

    # Send registration request to the repeater
    logger.debug('Registering with the Channel Access repeater.')
    bytes_to_send = b.send(ca.RepeaterRegisterRequest())

    repeater_port = os.environ.get('EPICS_CA_REPEATER_PORT', 5065)
    for host in ca.get_address_list():
        udp_sock.sendto(bytes_to_send, (host, repeater_port))

    logger.debug("Searching for '%s'....", pv_name)
    bytes_to_send = b.send(
        ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION),
        ca.SearchRequest(pv_name, 0, ca.DEFAULT_PROTOCOL_VERSION))

    def send_search():
        for host in ca.get_address_list():
            if ':' in host:
                host, _, specified_port = host.partition(':')
                dest = (host, int(specified_port))
            else:
                dest = (host, CA_SERVER_PORT)
            udp_sock.sendto(bytes_to_send, dest)
            logger.debug('Search request sent to %r.', dest)

    def check_timeout():
        nonlocal retry_at

        if time.monotonic() >= retry_at:
            send_search()
            retry_at = time.monotonic() + retry_timeout

        if time.monotonic() - t > timeout:
            raise TimeoutError(f"Timed out while awaiting a response "
                               f"from the search for {pv_name!r}. Search "
                               f"requests were sent to this address list: "
                               f"{ca.get_address_list()}.")

    # Initial search attempt
    send_search()

    # Await a search response, and keep track of registration status
    retry_timeout = timeout / max((max_retries, 1))
    t = time.monotonic()
    retry_at = t + retry_timeout

    try:
        orig_timeout = udp_sock.gettimeout()
        udp_sock.settimeout(retry_timeout)
        while True:
            try:
                bytes_received, address = udp_sock.recvfrom(ca.MAX_UDP_RECV)
            except socket.timeout:
                check_timeout()
                continue

            check_timeout()

            commands = b.recv(bytes_received, address)
            b.process_commands(commands)
            for command in commands:
                if isinstance(command, ca.SearchResponse) and command.cid == 0:
                    address = ca.extract_address(command)
                    logger.debug('Found %s at %s', pv_name, address)
                    return address
            else:
                # None of the commands we have seen are a reply to our request.
                # Receive more data.
                continue
    finally:
        udp_sock.settimeout(orig_timeout)
Exemple #13
0
def main(*, skip_monitor_section=False):
    # A broadcast socket
    udp_sock = ca.bcast_socket()

    # Register with the repeater.
    bytes_to_send = b.send(ca.RepeaterRegisterRequest('0.0.0.0'))

    # TODO: for test environment with specific hosts listed in
    # EPICS_CA_ADDR_LIST
    if False:
        fake_reg = (('127.0.0.1', ca.EPICS_CA1_PORT),
                    [ca.RepeaterConfirmResponse(repeater_address='127.0.0.1')])
        b.command_queue.put(fake_reg)
    else:
        udp_sock.sendto(bytes_to_send, ('', CA_REPEATER_PORT))

        # Receive response
        data, address = udp_sock.recvfrom(1024)
        commands = b.recv(data, address)

    b.process_commands(commands)

    # Search for pv1.
    # CA requires us to send a VersionRequest and a SearchRequest bundled into
    # one datagram.
    bytes_to_send = b.send(ca.VersionRequest(0, 13),
                           ca.SearchRequest(pv1, 0, 13))
    for host in ca.get_address_list():
        if ':' in host:
            host, _, specified_port = host.partition(':')
            udp_sock.sendto(bytes_to_send, (host, int(specified_port)))
        else:
            udp_sock.sendto(bytes_to_send, (host, CA_SERVER_PORT))
    print('searching for %s' % pv1)
    # Receive a VersionResponse and SearchResponse.
    bytes_received, address = udp_sock.recvfrom(1024)
    commands = b.recv(bytes_received, address)
    b.process_commands(commands)
    c1, c2 = commands
    assert type(c1) is ca.VersionResponse
    assert type(c2) is ca.SearchResponse
    address = ca.extract_address(c2)

    circuit = ca.VirtualCircuit(our_role=ca.CLIENT,
                                address=address,
                                priority=0)
    circuit.log.setLevel('DEBUG')
    chan1 = ca.ClientChannel(pv1, circuit)
    sockets[chan1.circuit] = socket.create_connection(chan1.circuit.address)

    # Initialize our new TCP-based CA connection with a VersionRequest.
    send(chan1.circuit, ca.VersionRequest(priority=0, version=13))
    recv(chan1.circuit)
    # Send info about us.
    send(chan1.circuit, ca.HostNameRequest('localhost'))
    send(chan1.circuit, ca.ClientNameRequest('username'))
    send(chan1.circuit,
         ca.CreateChanRequest(name=pv1, cid=chan1.cid, version=13))
    commands = recv(chan1.circuit)

    # Test subscriptions.
    assert chan1.native_data_type and chan1.native_data_count
    add_req = ca.EventAddRequest(data_type=chan1.native_data_type,
                                 data_count=chan1.native_data_count,
                                 sid=chan1.sid,
                                 subscriptionid=0,
                                 low=0,
                                 high=0,
                                 to=0,
                                 mask=1)
    send(chan1.circuit, add_req)

    commands = recv(chan1.circuit)

    if not skip_monitor_section:
        try:
            print('Monitoring until Ctrl-C is hit. Meanwhile, use caput to '
                  'change the value and watch for commands to arrive here.')
            while True:
                commands = recv(chan1.circuit)
                if commands:
                    print(commands)
        except KeyboardInterrupt:
            pass

    cancel_req = ca.EventCancelRequest(data_type=add_req.data_type,
                                       sid=add_req.sid,
                                       subscriptionid=add_req.subscriptionid)

    send(chan1.circuit, cancel_req)
    commands, = recv(chan1.circuit)

    # Test reading.
    send(
        chan1.circuit,
        ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid,
                             ioid=12))
    commands, = recv(chan1.circuit)

    # Test writing.
    request = ca.WriteNotifyRequest(data_type=2,
                                    data_count=1,
                                    sid=chan1.sid,
                                    ioid=13,
                                    data=(4, ))

    send(chan1.circuit, request)
    recv(chan1.circuit)
    time.sleep(2)
    send(
        chan1.circuit,
        ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid,
                             ioid=14))
    recv(chan1.circuit)

    # Test "clearing" (closing) the channel.
    send(chan1.circuit, ca.ClearChannelRequest(chan1.sid, chan1.cid))
    recv(chan1.circuit)

    sockets.pop(chan1.circuit).close()
    udp_sock.close()