Making the Connection

After creating a socket, you are ready to exchange data—or are you? You cannot exchange data on sockets of the SOCK_STREAM type until the socket is in a connected state. We say that a connection exists when a local socket is connected to the remote socket. With sockets of the SOCK_DGRAM type, you do not normally need to connect with a peer before transmitting the data; however, see the sidebar later in this section titled "Connected and Connectionless Sockets."

There are two functions that you can use to set up a connection with a socket on the remote machine—connect() or WSAConnect(). You should use the WSAConnect() function if you want to specify a minimum level of service for the connection. To specify the required level of service, use the QOS (Quality of Service) specific parameters based on the supplied flow specification. We will not cover QOS, as it is beyond the scope of this book.

Let's consider the simpler function first, which is connect(). We give the prototype, which is defined in Winsock2.pas:

function connect(s: TSocket; name: PSockAddr; namelen: Integer): Integer; stdcall;

To create a connection with a peer, you need to supply three parameters to the connect() function, which are s, the unconnected socket; name, a pointer to the sockaddr_in record; and namelen, the size of the sockaddr_in record. You have already seen how to create a socket, but we still need to define the details of the peer with which to connect. To define the details of the peer, assign the values to the sockaddr_in record, which is defined in WinSock2.pas as:

sockaddr_in = record sin_family: Smallint; sin_port: u_short; sin_addr: in_addr; sin_zero: array [0..7] of Char;

end;

TSockAddrIn = sockaddr_in;

PSockAddrIn = ~sockaddr_in;

Usually, you need to only assign sensible values to the first three fields—sin_family, sin_port, and sin_addr. The last field, sin_zero, can be safely ignored, as it is used to make the size of the record 16 bytes long. However, some implementations use this field to distinguish different addresses bound to the interfaces, which requires sin_zero to be populated with zeroes. Delphi automatically assigns the sin_zero field to zero. To belabor the point, you should ensure that the sin_zero field is set to zero by calling the Win32 function ZeroMemory(), like this:

ZeroMemory(sockAddr, SizeOf(TSockAddrIn))

Calling this function will zero out all fields including sin_zero. Obviously, you should call this function before assigning values.

The sinjamily field is the protocol family, which is usually PF_INET for the Internet. Note that when you create a socket that uses an address family, say, AF_INET, you must also use the same family, which is PF_INET. The sin_port field is the port for the service an application requires. For example, for FTP this would be 21.

TIP: Recall the fact about byte ordering from Chapter 3 that you use the network byte order for the sin_port field. For example, to use the port for FTP, you would do the following assignment:

sockaddr. sin_port := htons(21)

where sockAddr is a sockaddr_in record Ignore this simple caveat at your peril!

The sin_addr field is actually a variant record, as shown below:

in_addr = record case Integer of 0: (S_un_b: SunB); 1: (S_un_c: SunC); 2: (S_un_w: SunW); 3: (S_addr: u_long);

end;

TInAddr = in_addr;

How you assign these fields depends on how you resolve the name of the peer with which you wish to connect. When you call any of the following functions, you must use the THostEnt record (see Chapter 3 for details of the structure and how to call these functions to fill the THostEnt record) to populate the fields of the sockaddr_in record: gethostbyname(), WSAGetHostByName(), gethostbyaddr(), and WSAGetHostByAddr(). The following code snippet shows how this is done:

Hostent: PHostent;

h_addr: PChar;

HostAddress: TSockAddrIn; // remember this is an alias for sockaddr_in begin

Hostent := gethostbyname(PChar(HostName));

if Hostent <> NIL then begin

Move(Hostent~.h_addr_list~, h_addr, SizeOf(Hostent~.h_addr_list~));

with HostAddress.sin_addr do begin

S_un_b.s_b1 := Byte(h_addr[0]); S_un_b.s_b2 := Byte(h_addr[l]); S_un_b.s_b3 := Byte(h_addr[2]); S_un_b.s_b4 := Byte(h_addr[3]);

After assigning the fields of the TSockAddrIn record, call connect() like this:

Res:= connect(skt, @HostAddr, SizeOf(TSockAddrIn));

If no error occurs, connect() returns zero to indicate that the connection now exists. Otherwise, it returns SOCKET_ERROR, and you should always call WSAGetLastError() to retrieve the error code.

Similarly, use the WSAConnect() function to set up a connection. However, the function has four more parameters, as the following prototype clearly shows:

function WSAConnect(s: TSocket; name: PSockAddr; namelen: Integer; lpCallerData: LPWSABUF; lpCalleeData: LPWSABUF; lpSQOS: LPQOS; lpGQOS: LPQOS): Integer; stdcall;

However, by setting the last four parameters to NIL, you can call the function in the same way you would call connect(), like this:

Res: = WSAConnect(skt, @HostAddr, SizeOf(TSockAddrIn), NIL, NIL, NIL, NIL);

However, using WSAConnect() this way is rather pointless as you can achieve the same purpose with the simpler connect() function. To use the WSAConnect() function to its full potential, you need to use parameters like lpCallerData, lpCalleeData, lpSQOS, and lpGQOS. The parameters lpCallerData and lpCalleeData are pointers to user data that is transferred to and from the peer, respectively. The definition of LPWSABUF is defined in Winsock2.pas:

_WSABUF = record len: u_long; // the length of the buffer buf: PChar; // the pointer to the buffer end;

WSABUF = _WSABUF; LPWSABUF = ~_WSABUF; TWsaBuf = WSABUF; PWsaBuf = LPWSABUF;

The WSAConnect() function enables the application to request Quality of Service (QOS) for incoming and outgoing traffic. QOS is not discussed in detail in this book.

After a successful connection, you can use the getsockname() and getpeer-name() functions to retrieve the names of the local and remote sockets, respectively.

Connected and Connectionless Sockets

One of the established wisdoms in Winsock 1.1 is that all connected sockets use SOCK_STREAM (TCP protocol) and connectionless sockets use SOCK_DGRAM (UDP protocol). To use connectionless sockets, you would use the sendto() and recvfrom() functions to send and receive data. With the introduction of Winsock 2, these wisdoms are no longer strictly true. In Winsock 2, you can use the send() and recv() functions, which are normally used for connected sockets, with connectionless sockets. As you shall discover later in this chapter, Winsock 2 has introduced the WSARecv() and WSASend() functions, which are extended versions of recv() and send(), respectively, that you can also use with connectionless sockets. This will only be true provided you use either WSAConnect() or connect() to create the connection in the first place, which you can use to get the default peer address that is required for a connectionless socket. You can also use connected sockets with the sendto(), recvfrom(), WSARecvFrom() and WSASendTo() functions.

Was this article helpful?

+1 0

Post a comment