Developers.Code.CConnection: Difference between revisions
(Importing page from Tikiwiki) |
m (1 revision) |
Latest revision as of 20:07, 20 June 2009
CConnection
In the Shareaza source code, the files Connection.h and Connection.cpp define the class CConnection. When Shareaza is running, each CConnection object represents a connection through the Internet to a remote computer running peer-to-peer software.
To connect to a remote computer, call ConnectTo with an IP address and port number. Or, if another computer wants to connect to us, use AcceptFrom. The socket is the member variable m_hSocket. Send data to the other computer by putting it in m_pOutput and calling DoRun. New incoming data will be waiting in m_pInput. Call Close when it's time to end the connection.
The most important member variable in CConnection is m_hSocket. The definition in Connection.h looks like this:
[code Connection.h] SOCKET m_hSocket; </source>
So, m_hSocket is a SOCKET. A socket is a type in Windows that is so old it is a normal English word written entirely with capital letters. Another one is FILE, for example. A socket is a two-way data connection through the Internet to another computer. If you have a socket, you can write bytes to the remote computer, and read bytes it sends back to you. The part of the Win32 API which deals with sockets is called Windows Sockets 2.
Constructor and Destructor
The first two methods in Connection.cpp are the constructor and destructor. A line of code like new CConnection c calls the constructor, while delete c calls the destructor.
The constructor initializes member variables to the default state, which is NULL, FALSE, or 0. The socket m_hSocket has a special null value, INVALID_SOCKET. Developers.Topic.WhatIsASocket
The destructor just closes the socket before your computer deletes the object.
ConnectTo
There are two versions of the ConnectTo method. The first takes a pointer to a SOCKADDR_IN structure, which is MFC's way of packaging an IP address and port number. Windows Sockets likes IP addresses in IN_ADDR structures. The second ConnectTo method takes the IP in this form, and the port number seprately. You can see how the IP address and port number are unpacked from a SOCKADDR_IN structure in the first method. It calls the second, where all the work is done.
Shareaza keeps a list of IP addresses and ranges of addresses that are owned by government agencies and global corporations. ConnectTo uses Security.IsDenied to look for the IP address in this list. If its found, we don't want to connect to it. The method records this and returns FALSE.
The method saves the IP address and port number it got passed in member variables. It uses the Windows Sockets function socket (another really simple name that shows how old this part of the Windows platform is) to create a new socket in m_hSocket. The function ioctlsocket controls the input and output method of the socket. ConnectTo uses it to make the socket asynchronous, or non-blocking. Developers.Topic.BlockingVersusAsynchronous Then, it uses bind to tell the socket our IP address on the Internet. A socket has two IP addresses, ours and theirs.
The API call WSAConnect tells the socket the remote IP address, and starts trying to make the connection. If we had left our socket blocking, your computer would pause on the line of code with WSAConnect, waiting for the connection to work or time out. It's just like waiting for a Web site to load in your browser, and can take several seconds or not work at all. Shareaza can't halt all execution for this. If it did, the user would wonder why the program is constantly freezing. That's why we set our socket to be non-blocking, or asynchronous. No matter what happens with the remote computer, the call to WSAConnect will return immediately. It will probably return FALSE, and WSAGetLastError will report WSAEWOULDBLOCK. This just means that the connection hasn't been made yet, and the call would have blocked.
At the end of ConnectTo, the method creates new input and output buffers. These memory buffers will temporarily hold the bytes Shareaza writes into and reads from the other computer through the socket. The member variable m_bInitiated is TRUE because we initiated the connection to the remote computer. The Win32 API call GetTickCount returns the time right now, and ConnectTo keeps it in m_tConnected.
AcceptFrom
The AcceptFrom method gets called when a remote computer wants to connect to us. It gets a new socket to use for the connection, and the IP address of the remote computer in a SOCKADDR_IN structure.
There is less to do here than in ConnectTo, because the remote computer instigated the connection. The method saves the socket and IP address in member variables. It sets up the input and output buffers. It records that we didn't initiate the connection, we're connected right now, and writes down the time all this happened. Just like ConnectTo did, AcceptFrom sets up asynchronous, non-blocking reading and writing for our end of the socket.
AttachTo
AttachTo seems to copy an existing CConnection object into this one. CShakeNeighbour::OnHandshakeComplete seems to use it to create a copy of itself before deleting itself.
Close
The Close method closes the connection with the remote computer. The Windows Sockets function closesocket accomplishes this.
DoRun and QueueRun
DoRun calls WSAEnumNetworkEvents to get the network events that happened to the sockeet since we last checked. If these events include FD_CONNECT, the connection has either been made or lost. If the connection has been made, DoRun sets m_bConnected to TRUE and saves the time in several member variables. If the connection has been lost, DoRun returns FALSE.
The end of DoRun calls OnWrite and OnRead several times, always making sure they return TRUE.
QueueRun calls OnWrite depending on the current state of m_nQueuedRun.
OnConnected, OnDropped, and OnRun
The methods OnConnected, OnDropped, and OnRun return TRUE or do nothing.
OnRead and OnWrite
Much of the code in OnRead makes sure Shareaza doesn't read data from the socket too quickly. This bandwidth throttling is done through the input bandwidth meter, a member variable called m_mInput of type TCPBandwidthMeter. The bandwidth meter structure contains two arrays. One is an array of tick counts. The other is an array of byte sizes. OnRead needs to know how many bytes have been read in the last second, so it loops through the array, adding each byte count that is less than one second old to nLimit. Developers.Topic.AssemblyLanguage
Next, the method calculates nActual, the speed limit in bytes per second. It gets the limit from the connection's bandwidth meter, and then multiplies it with the bandwidth scale for the entire program.
At this point, nActual is the speed limit in bytes per second, and nLimit is the number of bytes we've read in the last second. If nActual is more than nLimit, we can still read some more bytes. The method sets nLimit to this difference. If we're already over our limit, it makes nLimit zero.
The method calculates how many seconds have elapsed since the last read, and multplies it with the speed limit to get tCutoff. When they are compared in the last line of the if statement, both nLimit and nCutoff are the number of bytes we can read now. nLimit is calculated from the bytes we read in the last second, while nCutoff is calculated from the time that has elapsed since the last read. The method uses min to set the limit to the most restrictive of the two.
The while loop uses the Windows Sockets recv function to read data from the socket. When there is nothing left to read, recv returns 0, and the loop breaks. The loop is also careful to not read more than nLimit. The method reads bytes from the socket into pBuff, a local buffer, and then adds them to the input buffer in the connection object, m_pInput.
The last big loop in OnRead records what just happened in the bandwidth meter. The next time OnRead runs, it will use this information to set the new limit.
The OnWrite method is very similar to OnRead. The first if block calculates nLimit, the number of bytes we can safely write without going over the set bandwidth limit. In the while loop after that, the Windows Sockets function send actually sends data to the connected computer. The last if block updates the output bandwidth meter for the next time the method is called.
Measure
The Measure method looks at records of reads and writes made in the last 2 seconds to compute the average bandwidth in bytes per second in each direction. It stores this in m_mInput.nMeasure and m_mOutput.nMeasure.
Like the portions of OnRead and OnWrite that access the bandwidth meter structures, this code has been almost entirely translated from C++ to assembly language.
ReadHeaders and OnHeaderLine
Once Shareaza connects to a remote computer, it will start sending us bytes. The OnRead method reads these bytes from the socket into the m_pInput buffer. The ReadHeaders method has the job of looking at the input buffer and figuring out what's there. Developers.Topic.SocketHeaders ReadHeaders looks at the bytes in the input buffer as though they are lines of text separated by newline characters. For instance, the input buffer might contain text like this:
[code m_pInput] header1:value1\n header2:value2\n header3\n
value3a\n value3b\n value3c\n
header4:value4\n \n bytesafterheaderthatjustkeepgoing... </source>
Each line ends with a newline byte, written as \n in the code. Some of the lines are a header and value separated by a colon. If a line doesn't have a colon, the next lines will start with at least one space, and be several values on each line. If we encounter a completely blank line, it means that the headers are finished. Beyond it lies the data stream of bytes.
In ReadHeaders, a while loop reads lines from the input buffer one at a time. It then sorts the line into one of three possibilities:
1. The line is empty. This marks the end of the headers. Return TRUE 2. The line starts with a space. This must be another value from a previous header. Give OnHeaderLine the last header the loop found, and the whole line as its value. 3. The line has a colon. Read the header before it and the value after it, and then give both to OnHeaderLine.
OnHeaderLine looks for 3 headers common to several networks. The User-Agent header tells what brand and version peer-to-peer program the remote computer is running. The Remote-IP header advertises an IP address on the Internet that we might try connecting to later. The
When a computer sends a X-My-Address, Listen-IP, or Node header, it's telling the world what it thinks its own IP address is. We already know what IP address we connected to, but may not know the port number. OnHeaderLine looks for it after a colon in the value, translates it from little to big endian, and saves it in the m_pHost member variable. Developers.Topic.LittleAndBigEndian
SendMyAddress
Shareaza calls SendMyAddress to tell the connected computer our IP address and port number. It does this by composing a header that looks like this:
Listen-IP: 1.2.3.4:5
The method only does this if Shareaza is listening on a port. Our IP address is stored in Network.m_pHost.sin_addr and our port number is stored in Network.m_pHost.sin_port.
IsAgentBlocked
When Shareaza connects to a peer-to-peer program somehwere on the Internet, it tells us its name and version. It does this by sending a header like:
User-Agent: Shareaza 1.8.2.0
Shareaza keeps a list of programs to avoid talking to in Settings.Uploads.BlockAgents. The IsAgentBlocked sees if this user agent string is on that list.
URLEncode and URLDecode
You can give URLEncode text like hello world, and it will encode it like a URL, making it hello%20world. Each unsafe character is turned into a three-letter code of the form %00. Use URLDecode to convert it back. Developers.Topic.ASCIIVersusUnicode Developers.Topic.IfdefAndElse Developers.Topic.WhenToSearchWhere Developers.Topic.CallingTwiceForBufferLength
StartsWith
This little helper method takes two text pointers. If pszInput is hello world, and pszText is hello, it will return true, because pszInput starts with pszText.