#ifndef __TalkWithServer64_h_ #define __TalkWithServer64_h_ /* TalkWithServer64 is similar to TalkWithServer. They perform the same basic * function, but the network protocol is slightly different. The programmer's * interface is very similar, so it should be easy to modify old code to use * TalkWithServer64. * * The primary difference is that TalkWithServer64 uses a 64 bit message id. * This will require some changes on the server side. At a bare minimum, * the server needs a new output mode that will include the 64 bit field * instead of a 32 bit field. However, some other units may need small * updates. In particular, you often read the id and store it in a local * variable. That variable will need to change from 32 to 64 bits. * * Notice the ExternalRequest::MessageId class in CommandDispatcher.h. We * created a new class to help find places where we were storing the message * id. If we just changed from int to int64_t, and we only made the change in * half the places, the compiler would naively convert back and forth between * 32 and 64 bit integers without complaining. Worse yet, until you got to a * high enough message id, you wouldn't see any bugs! This new class has no * automatic conversions to or from a number. * * A 64 bit message id means that we never reuse a message id. That means * that streaming messages are cheap. With the original TalkWithServer you * could send as many single response messages as you wanted. But you sent * a finite number of streaming messages. We always handled that by creating * a TopListManager, a HistoryManager, etc. Each class would send one special * message to listen for streaming data. Each object would send a request * and expect to get the streaming data back on the shared channel. This * worked but it made things much more complicated than they should have been. * With TalkWithServer64 you can make as many streaming requests as you want, * and they are all seperate. * * The CancelId and the message id are now the same. That simplifies some * code. The client id can now be a 64 bit integer. That may be overkill * but it wasn't very expensive. The message length is still limited by * a 32 bit integer. This protocol was never made for huge messages and the * current size should be more than sufficient. * * We no longer use the term CancelId. Intead we say MessageId, which is more * generic. In TalkWithServer, the message id was completely hidden inside * the class. The CancelId was availble to the user. So if you have old code * which used TalkWithServer and you want it to use TalkWithServer64, you need * to replace TalkWithServer::CancelId with TalkWithServer64::MessageId. */ #include "TalkWithServer.h" class TalkWithServer64 : public IPollElement { public: typedef uint64_t MessageId; // This is the callback for a specific message. It's common to send a // request, and then listen for a response to that request. So when the // caller sends the initial message, the caller also provides this to // receive a callback. This includes a messageId and a clientId, in case the // caller wants to have one object listing for multiple responses. class IMessageListener { public: virtual void onMessage(std::string bytes, int64_t clientId, MessageId messageId) =0; virtual void onAbort(int64_t clientId, MessageId messageId) =0; virtual ~IMessageListener() { } }; // This is the callback for all messages. This was mostly aimed at the // proxy. The proxy doesn't want to keep track of which requests still // have an outstanding response. The client and server take care of that. // The proxy just passes every response from the server to the client. // // This is different from the preview listener available in some clients. // Preview includes successful and failed responses, so the client can // display red or green tick marks. This only includes successful responses. // This will include all responses, even the ones we were not expecting. // Preview does not. class IAllResponsesListener { public: virtual void onResponse(std::string bytes, MessageId messageId) =0; virtual ~IAllResponsesListener() { } }; typedef std::map< std::string, std::string > Message; private: const std::string _name; const MessageId _uniqueId; TcpIpConnection _tcpIpConnection; ZLibOutputStream _zLibOutputStream; ZLibInputStream _zLibInputStream; std::vector< Message > _outgoing; static MessageId _lastMessageId; enum Status { NotYetOpen, Open, Closed }; Status _status; struct ServerWrapper { // If we send a message to the server and we expect one or more responses, // we create one of these for the message. IMessageListener *listener; int64_t clientId; bool streaming; }; std::map< MessageId, ServerWrapper > _atServer; struct LocalWrapper { // When we receive a message from the server we create one of these. We // queue these until there's an appropriate time to call the callbacks. MessageId messageId; std::string body; bool success; }; std::vector< LocalWrapper > _localQueue; struct AllResponsesWrapper { // If we have an all responses listener, store each incoming response in // one of these objects. MessageId messageId; std::string body; }; std::vector< AllResponsesWrapper > _allResponses; IAllResponsesListener *_allResponsesListener; void checkForClosed(); void addStringWithSize(std::string toCompress); void addInt32(int32_t toCompress); public: TalkWithServer64(std::string const &name); virtual ~TalkWithServer64(); // For debugging and logging. std::string const &getName() const { return _name; } // Each Tcl MessageId getUniqueId() const { return _uniqueId; } // See TcpIpConnection::connect() for the meanings of the inputs. void connect(std::string address, std::string port, bool asyncConnect); void disconnect(); bool disconnected(); void cancel(MessageId id); void cancelAll(); // No one has called connect() yet. bool notYetOpen() const { return _status == NotYetOpen; } // At least one write() succeeded. See TcpIpConnection::writeSucceeded() for // more details. bool writeSucceeded() { return _tcpIpConnection.writeSucceeded(); } void setAllResponsesListener(IAllResponsesListener *newListener); // This refers to IMessageListener messages, not IAllResponsesListener // messages. int pendingResponseCount(); // This refers to IMessageListener messages and IAllResponsesListener // messages. void doResponses(); // The caller is allowed to manually set the message_id field. However, if // he does that he should do that for all messages. Don't try to manually // set some message ids and let TalkWithServer64 pick others for you. void sendMessage(Message const &message); MessageId sendMessage(Message message, IMessageListener *listener, int64_t clientId, bool streaming = false); // This allows you to request a message id in advance and send the message // later. Message id should always come from getNextMessageId() and should // never be reused. // // This is specifically aimed at the send manager functionality in // ServerConnectin64. That will allow you to queue up messages in that class // and eventually send them to TalkWithServer64. The caller will want the // messageId immediately, even though we are not sending the message // immediately. // // There is no corresponding functionality in the C# code. C# does not allow // you to cancel a message. That was required by Pascal and C++ because // otherwise you might send a message to an object that has been deleted. void sendMessageWithId(Message message, IMessageListener *listener, int64_t clientId, MessageId messageId, bool streaming = false); // This is exported for use with sendMessageWithId(). This is thread safe. // All TalkWithServer64 objects share the same message ids, so it is safe to // ask for an id before creating the TalkWithServer64 object. The MessageId // will never be 0. static MessageId getNextMessageId(); virtual bool wantsRead(); virtual bool wantsWrite(); virtual int getHandle(); virtual void wakeUp(); }; #endif