Q : say the server is not up in that case the recv() in the client will be blocked forever which I don't want.
ZeroMQ is a fabulous framework for doing smart signaling/messaging in distributed-systems
Let's sketch a demo of a principally non-blocking modus-operandi, with some inspirations of how the resources ought be both acquired and also gracefully released before process termination.
Maybe a bit of reading about the main conceptual differences in ZeroMQ hierarchy in less than a five seconds will also help.
Server.py
 aContext     = zmq.Context()
 aLightHouse  =    aContext.socket( zmq.PUB )
 aRepSocket   =    aContext.socket( zmq.REP )
 aRepSocket.setsockopt(             zmq.LINGER,   0 )
 aRepSocket.setsockopt(             zmq.COMPLETE, 1 )
 aRepSocket.bind(                  "tcp://*:2222" )
 aLightHouse.bind(                 "tcp://*:3333" )
 aLightHouse.setsockopt(            zmq.LINGER,   0 )
 aLightHouse.setsockopt(            zmq.CONFLATE, 1 )
 aLightHouse_counter = 0
 #------------------------------------------------------------
 print( "INF: Server InS: ZeroMQ({0:}) going RTO:".format( zmq.zmq_version() )  )
 #------------------------------------------------------------
 while True:
    try:
        aLightHouse_counter += 1
        aLightHouse.send( "INF: server-RTO blink {0:}".format( repr( aLightHouse_counter ) ),
                           zmq.NOBLOCK
                           )
        if ( 0 < aRepSocket.poll( 0, zmq.POLLIN ) ):
            try:
                message = aRepSocket.recv(         zmq.NOBLOCK ); print( "INF: .recv()ed {0:}".format( message ) )
                pass;     aRepSocket.send( b"Ack", zmq.NOBLOCK ); print( "INF: .sent() ACK" )
            except:
                # handle EXC: based on ...
                print(  "EXC: reported as Errno == {0:}".format( zmq.zmq_errno() ) )
        else:
            # NOP / Sleep / do other system work-units to get processed during the infinite-loop
    except:
        # handle EXC:
        print(  "EXC: will break ... and terminate OoS ..." )
        break
#------------------------------------------------------------
print( "INF: will soft-SIG Server going-OoS..." )
aLightHouse.send(   "INF: server goes OoS ... " )
#------------------------------------------------------------
print( "INF: will .close() and .term() resources on clean & graceful exit..." )
Sleep( 0.987654321 )
aRepSocket.unbind(  "tcp://*:2222" )
aRepSocket.close()
aLightHouse.unbind( "tcp://*:3333" )
aLightHouse.close()
aContext.term()
#------------------------------------------------------------
print( "INF: over and out" )
Client.py
try:
  aContext   = zmq.Context()
  aReqSocket =    aContext.socket( zmq.REQ )
  aBeeper    =    aContext.socket( zmq.SUB )
  aReqSocket.setsockopt(           zmq.LINGER,   0 )
  aReqSocket.setsockopt(           zmq.COMPLETE, 1 )
  aReqSocket.connect(             "tcp://localhost:2222" )
  aBeeper.connect(                "tcp://localhost:3333" )
  aBeeper.setsockopt(              zmq.SUBSCRIBE, "" )
  aBeeper.setsockopt(              zmq.CONFLATE, 1 )
  #------------------------------------------------------------
  print( "INF: Client InS: ZeroMQ({0:}) going RTO.".format( zmq.zmq_version() )  )
  #------------------------------------------------------------
  try:
      while True:
           if ( 0 == aBeeper.poll( 1234 ) ):
                print( "INF: Server OoS or no beep visible within a LoS for the last 1234 [ms] ... " )
           else:
                print( "INF: Server InS-beep[{0:}]".format( aBeeper.recv( zmq.NOBLOCK ) ) )
                try:
                     print( "INF: Going to sending a request" )
                     aReqSocket.send( b"send the message", zmq.NOBLOCK )
                     print( "INF: Sent. Going to poll for a response to arrive..." )
                     while ( 0 == aReqSocket.poll( 123, zmq.POLLIN ) ):
                           print( "INF:  .poll( 123 ) = 0, will wait longer ... " )
                     message = socket.recv( flags = zmq.NOBLOCK )
                     print( "INF: Received a reply %s " % message )
                
                 except Exception as e:
                     print( "EXC: {0:}".format( str( e ) ) )
                     print( "INF: ZeroMQ Errno == {0:}".format( zmq.zmq_errno() ) )
                     print( "INF: will break and terminate" )
                     break
  except Exception as e:
      print( "EXC: {0:}".format( str( e ) ) )
  finally:
      #------------------------------------------------------------
      print( "INF: will .close() and .term() resources on clean & graceful exit..." )
      aBeeper.close()
      aReqSocket.close()
      aContext.term()
      #------------------------------------------------------------
      print( "INF: over and out" )