C # uses Thrift as the TSocket of RPC framework

Keywords: thrift

preface

In the first few sections, we talked about the basic concepts of Thrift framework and important namespaces. In the next few sections, we will explain some important types of Thrift from a practical point of view. In this section, I will first talk about the classes that the Thrift framework supports TCP communication, client-side TSocket and server-side TServerSocket.

Client TSocket

Tsocket is the underlying type of TCP communication implemented by Thrift framework (the above two layers are Protocol layer and Client layer respectively). Let's first take a look at the constructor of tsocket:

  public TSocket(TcpClient client);
  public TSocket(string host, int port);
  public TSocket(string host, int port, int timeout);

Tsocket has three constructors:

  • +The first construction requires us to maintain a TcpClient. For students familiar with. net Socket communication, this is very simple, so we won't repeat it here
  • +The second constructor is similar to the third constructor. The only difference is whether the TimeOut timeout is set

Whether the constructor has a timeout parameter

Let's focus on the latter two constructors. In fact, there is no difference in the internal implementation of the latter two constructors. We can see from the source code:

public TSocket(string host, int port) : this(host, port, 0)
		{
		}

public TSocket(string host, int port, int timeout)
		{
			this.host = host;
			this.port = port;
			this.timeout = timeout;
			this.InitSocket();
		}

In the internal implementation, if we do not set the timeout parameter, it will be set to 0, and then the constructor with three parameters will be called. Is there no difference between calling these two constructors to instantiate this class? The answer is No. When they call the * * Open * * method, they take different code branches:

 

if (this.timeout == 0)
 {
	this.client.Connect(this.host, this.port);
 }
 else
 {
				TSocket.ConnectHelper connectHelper = new TSocket.ConnectHelper(this.client);
				IAsyncResult asyncResult = 
this.client.BeginConnect(this.host, this.port, 
new AsyncCallback(TSocket.ConnectCallback), connectHelper);
				if (!asyncResult.AsyncWaitHandle.WaitOne(this.timeout) || !this.client.Connected)
				{
               ......

 

  

 

Let's first explain that the Timeout parameter is assigned to the SendTimeout and ReceiveTimeout parameters of TcpLient type:

public int Timeout
		{
			set
			{
				TcpClient arg_1E_0 = this.client;
				TcpClient arg_18_0 = this.client;
				this.timeout = value;
				arg_18_0.SendTimeout = value;
				arg_1E_0.ReceiveTimeout = value;
			}
		}

If you do not set the timeout parameter, you need to remember that the host parameter needs to pass the string corresponding to IPv6. If you pass the string corresponding to ipv4, you will receive three types of inexplicable errors:

  1. The remote endpoint was not set before the sendto method was called
  2. The remote host closed an existing link
  3. internal error

The thrift framework is a little unfriendly on error prompts, and the error prompts given are of no use. We can find the problem from the source code. Please take a look at the code:

internal static TcpClient CreateTcpClient()
		{
			return new TcpClient(AddressFamily.InterNetworkV6)
			{
				Client = 
				{
					DualMode = true
				}
			};
		}

In the above code, we found the method to create the TcpClient type in the * * InitSocket * * method. We already know the reason in the code, because the TcpClient type is located in the type of InterNetworkV6. If we pass the ipv4 address when creating, the above problem will occur. If we set the timeout parameter, there will be no problem even if we pass the ipv4 address. This may be related to the internal implementation of connect and beginconnect.

Server

The server side is a process of listening to connections and processing requests. We have talked about the general processing process of the server side above, which will not be repeated here.

TMultiplexedProtocol and TMultiplexedProcessor

Next, we will merge the main processing classes of the listening port. TMultiplexedProtocol is used for the client and TMultiplexedProcessor is used for the server. In the previous article, we mentioned how to use these two classes and how to encapsulate the calling methods of the two classes. Here I want to talk about how to handle requests in their internal.

To illustrate the implementation principle of a class, the best way is to take you to its source code. First, let's take a look at some of the source code of TMultiplexedProtocol:

 

public override void WriteMessageBegin(TMessage tMessage)
		{
			TMessageType type = tMessage.Type;
			if (type == TMessageType.Call || type == TMessageType.Oneway)
			{
				base.WriteMessageBegin(new 
    TMessage(this.ServiceName + TMultiplexedProtocol.SEPARATOR + tMessage.Name, tMessage.Type, tMessage.SeqID));
				return;
			}
			base.WriteMessageBegin(tMessage);
		}    

 

After reading the source code, we can see at a glance that yes, it writes the ServiceName into the request. How to deal with it on the server side? Similarly, let's look at the processing method on the server side:

 

public bool Process(TProtocol iprot, TProtocol oprot)
		{
			bool result;
			try
			{
				TMessage message = iprot.ReadMessageBegin();
				if (message.Type != TMessageType.Call && message.Type != TMessageType.Oneway)
				{
					this.Fail(oprot, message, 
TApplicationException.ExceptionType.InvalidMessageType, "Message type CALL or ONEWAY expected");
					result = false;
				}
				else
				{
					int num = message.Name.IndexOf(TMultiplexedProtocol.SEPARATOR);
					if (num < 0)
					{
						this.Fail(oprot, message, 
TApplicationException.ExceptionType.InvalidProtocol, "Service name not found in message name: " + message.Name + ". Did you forget to use a TMultiplexProtocol in your client?");
						result = false;
					}
					else
					{
						string text = message.Name.Substring(0, num);
						TProcessor tProcessor;
						if (!this.ServiceProcessorMap.TryGetValue(text, out tProcessor))
						{
							this.Fail(oprot, message, 
TApplicationException.ExceptionType.InternalError, "Service name not found: " + text + ". Did you forget to call RegisterProcessor()?");
							result = false;
						}
						else
						{
							TMessage messageBegin = new 
TMessage(message.Name.Substring(text.Length + 
TMultiplexedProtocol.SEPARATOR.Length), message.Type, message.SeqID);
							result = tProcessor.Process(new 
TMultiplexedProcessor.StoredMessageProtocol(iprot, messageBegin), oprot);
						}
					}
				}
			}

 

  

 

See the first else branch in the source code, which parses the serviceName, and then obtains the corresponding request processor we previously registered in the ServiceProcessorMap collection.

Other issues

  • +The method called on the server side cannot return Null type, otherwise the calling method will throw an exception

  • +Is it thread safe for the thrift framework to make RPC calls? Therefore, the thread safe part needs to be handled by itself

ending

In this section, we talked about some pitfalls that Tsocket will encounter in practice. I hope it will be helpful to you.

Posted by Hell Toupee on Tue, 30 Nov 2021 04:54:09 -0800