Introduction
The reason why I decided to write this article is because, I was learning
myself how to use sockets with under windows, I could not find one place that
had a tutorial which included every thing i was looking for, or if it did it was
way to complicated for me to understand. In the end after doing my research I
put together my resources and came up with this tutorial. I hope that it is
clear and informative so that other people can benefit from it
This article be divided into 3 section:
- part 1 - Create a server socket that listen for a client to connect
- part 2 - send / receive data from client to server
- part 3 - Read unknow size of data from client
Part 1 - Creating a listening socket
To use WinSock before we start writing any code we must include the
wsock32.lib and include the
#pragma comment(lib, "wsock32.lib")
Normally a socket that waits for a connection is a server, Once the
connection has been made it can spawn off a new Thread to deal with that
connection.
Before going into the acual code lets have a look some struct we will need to
set up a socket:
WSDATA:
Any code that is compiled using a winsock accesses the ws2_32.dll and this
struct is used during the process of doing so. The program MUST call
WSAstartup
to initialise the DLL for later use
SOCKADDR_IN:
This is used to specify how the socket is used and contains the field for the
IP and port
SOCKET:
This is an object that stores a handle to the socket
Key functions to create the listening server socket
WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData )
This function must be the first Windows Sockets function called by an
application or DLL. It allows an application or DLL to specify the version of
Windows Sockets API required and to retrieve details of the specific Windows
Sockets implementation. The application or DLL may only issue further Windows
Sockets API functions after a successful WSAStartup
invocation.
socket(int af, int type, int protocol)
This method creates the socket
bind(SOCKET s, const struct sockaddr FAR * name, int namelen)
Associates a local address with a socket This routine is used on an
unconnected datagram or stream socket, before subsequent connect
s
or listen
s. When a socket is created with socket
, it
exists in a name space (address family), but it has no name assigned.
bind
establishes the local association (host address/port number)
of the socket by assigning a local name to an unnamed socket. In the Internet
address family, a name consists of several components. For
SOCK_DGRAM
and
SOCK_STREAM
, the name consists of three parts: a host
address, the protocol number (set implicitly to UDP or TCP, respectively), and a
port number which identifies the application. If an application does not care
what address is assigned to it, it may specify an Internet address equal to
INADDR_ANY
, a port equal to 0, or both. If the Internet address is
equal to
INADDR_ANY
, any appropriate network interface will be
used; this simplifies application programming in the presence of multi- homed
hosts. If the port is specified as 0, the Windows Sockets implementation will
assign a unique port to the application with a value between 1024 and 5000. The
application may use
getsockname
after
bind
to learn
the address that has been assigned to it, but note that
getsockname
will not necessarily fill in the Internet address until the socket is connected,
since several Internet addresses may be valid if the host is multi-homed. If no
error occurs,
bind
returns 0. Otherwise, it returns
SOCKET_ERROR
, and a specific error code may be retrieved by calling
WSAGetLastError
.
listen(SOCKET s, int backlog )
Establishes a socket to listen to a incoming connection To accept
connections, a socket is first created with socket
, a backlog for
incoming connections is specified with listen
, and then the
connections are accepted with accept
. listen
applies
only to sockets that support connections, i.e. those of type
SOCK_STREAM
. The socket s is put into "passive'' mode where
incoming connections are acknowledged and queued pending acceptance by the
process. This function is typically used by servers that could have more than
one connection request at a time: if a connection request arrives with the queue
full, the client will receive an error with an indication of
WSAECONNREFUSED
. listen
attempts to continue to
function rationally when there are no available descriptors. It will accept
connections until the queue is emptied. If descriptors become available, a later
call to listen
or accept
will re-fill the queue to the
current or most recent "backlog'', if possible, and resume listening for
incoming connections.
accept(SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen)
This routine extracts the first connection on the queue of pending
connections on s, creates a new socket with the same properties as s and returns
a handle to the new socket. If no pending connections are present on the queue,
and the socket is not marked as non- blocking, accept
blocks the
caller until a connection is present. If the socket is marked non-blocking and
no pending connections are present on the queue, accept
returns an
error as described below. The accepted socket may not be used to accept more
connections. The original socket remains open. The argument addr
is
a result parameter that is filled in with the address of the connecting entity,
as known to the communications layer. The exact format of the addr
parameter is determined by the address family in which the communication is
occurring. The addrlen
is a value-result parameter; it should
initially contain the amount of space pointed to by addr
; on return
it will contain the actual length (in bytes) of the address returned. This call
is used with connection-based socket types such as SOCK_STREAM
. If
addr and/or addrlen
are equal to NULL
, then no
information about the remote address of the accepted socket is returned.
closesocket(SOCKET s)
closes a socket
WSACleanup()
Ends the use of the Windows Sockets DLL.
Example program
#include <stdio.h>
#include <winsock.h>
#include <windows.h>
#define SERVER_SOCKET_ERROR 1
#define SOCKET_OK 0
#pragma comment(lib, "wsock32.lib")
void socketError(char*);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShow)
{
WORD sockVersion;
WSADATA wsaData;
int rVal;
sockVersion = MAKEWORD(1,1);
WSAStartup(sockVersion, &wsaData);
SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(s == INVALID_SOCKET)
{
socketError("Failed socket()");
WSACleanup();
return SERVER_SOCKET_ERROR;
}
SOCKADDR_IN sin;
sin.sin_family = PF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.s_addr = INADDR_ANY;
rVal = bind(s, (LPSOCKADDR)&sin, sizeof(sin));
if(rVal == SOCKET_ERROR)
{
socketError("Failed bind()");
WSACleanup();
return SERVER_SOCKET_ERROR;
}
rVal = listen(s, 2);
if(rVal == SOCKET_ERROR)
{
socketError("Failed listen()");
WSACleanup();
return SERVER_SOCKET_ERROR;
}
SOCKET client;
client = accept(s, NULL, NULL);
if(client == INVALID_SOCKET)
{
socketError("Failed accept()");
WSACleanup();
return SERVER_SOCKET_ERROR;
}
closesocket(client);
closesocket(s);
WSACleanup();
return SOCKET_OK;
};
void socketError(char* str)
{
MessageBox(NULL, str, "SOCKET ERROR", MB_OK);
};
Making client connection with server
In order to create a socket that connects to an other socket uses most of the
functions from the previous code with the exception of a struct called
HOSTENT
HOSTENT:
This struct is used to tell the socket to which computer and port to connect
to. These struct can appear as LPHOSTENT
, but it actually means
that they are pointer to HOSTENT
.
Client key function
Most of the functions that have been used for the client to connect to the
server are the same as the server with the exception of a few. I will just go
through the different functions that have been used for the client.
gethostbyname(const char* FAR name)
gethostbyname
returns a pointer to a hostent structure as
described under gethostbyaddr
. The contents of this structure
correspond to the hostname name. The pointer which is returned points to a
structure which is allocated by the Windows Sockets implementation. The
application must never attempt to modify this structure or to free any of its
components. Furthermore, only one copy of this structure is allocated per
thread, and so the application should copy any information which it needs before
issuing any other Windows Sockets API calls. A gethostbyname
implementation must not resolve IP address strings passed to it. Such a request
should be treated exactly as if an unknown host name were passed. An application
with an IP address string to resolve should use inet_addr
to
convert the string to an IP address, then gethostbyaddr
to obtain
the hostent
structure.
example code
#include <windows.h>
#include <winsock.h>
#pragma comment(lib, "wsock32.lib")
#define CS_ERROR 1
#define CS_OK 0
void sError(char*);
int WINAPI WinMain(HINSTANCE hHinst, HINSTANCE hPrevHinst, LPSTR lpCmdLine,
int nShow)
{
WORD version;
WSADATA wsaData;
int rVal=0;
version = MAKEWORD(1,1);
WSAStartup(version,(LPWSADATA)&wsaData);
LPHOSTENT hostEntry;
hostEntry = gethostbyname("hibbert");
if(!hostEntry)
{
sError("Failed gethostbyname()");
return CS_ERROR;
}
SOCKET theSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(theSocket == SOCKET_ERROR)
{
sError("Failed socket()");
return CS_ERROR;
}
SOCKADDR_IN serverInfo;
serverInfo.sin_family = PF_INET;
serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);
serverInfo.sin_port = htons(8888);
rVal=connect(theSocket,(LPSOCKADDR)&serverInfo, sizeof(serverInfo));
if(rVal==SOCKET_ERROR)
{
sError("Failed connect()");
return CS_ERROR;
}
closesocket(theSocket);
WSACleanup();
MessageBox(NULL, "Connection was made", "SOCKET", MB_OK);
return CS_OK;
}
void sError(char *str)
{
MessageBox(NULL, str, "SOCKET ERROR", MB_OK);
WSACleanup();
}
Part 2 - Send / recieve
Up to this point we have managed to connect with our client to the server.
Clearly this is not going to be enough in a real-life application. In this
section we are going to look into more details how to use the
send
/recv
functions in order to get some communication
going between the two applications.
Factually this is not going to be difficult because most of the hard work has
been done setting up the server and the client app. before going into the code
we are going to look into more details the two functions
send(SOCKET s, const char FAR * buf, int len, int flags)
send
is used on connected datagram or stream sockets and is used
to write outgoing data on a socket. For datagram sockets, care must be taken not
to exceed the maximum IP packet size of the underlying subnets, which is given
by the iMaxUdpDg
element in the WSAData
structure
returned by WSAStartup
. If the data is too long to pass atomically
through the underlying protocol the error WSAEMSGSIZE
is returned,
and no data is transmitted.
recv(SOCKET s, const char FAR * buf, int len, int flags)
For sockets of type SOCK_STREAM
, as much information as is
currently available up to the size of the buffer supplied is returned. If the
socket has been configured for in- line reception of out-of-band data (socket
option SO_OOBINLINE
) and out-of-band data is unread, only
out-of-band data will be returned. The application may use the
ioctlsocket
SIOCATMARK
to determine whether any more
out-of-band data remains to be read.
Code Example
The following code example demonstrates how to make use of the
recv
function. The recv
function is used after the
accept
function, and the socket must be connected in order to
receive the data.
client = accept(s, NULL, NULL);
cout << "newclient found" << endl;
if(client == INVALID_SOCKET)
{
socketError("Failed accept()");
WSACleanup();
return SERVER_SOCKET_ERROR;
}
char buf[4];
rVal = recv(client, buf, 4, 0);
if(rVal == SOCKET_ERROR)
{
int val = WSAGetLastError();
if(val == WSAENOTCONN)
{
cout << "socket not connected" << endl;
}
else if(val == WSAESHUTDOWN )
{
cout << "socket has been shut down!" << endl;
}
socketError("Failed recv()");
return SERVER_SOCKET_ERROR;
}
cout << buf << endl;
This code example below works fine when you know exactly how much data you
are about to receive. The problem comes when you do not know how much data will
arrive. For now we will ignore this problem because the aim here is actually
prove that data has been received. In the next section we will evolve the way we
receive data.
Now we are going to look at how to implement the send
function.
In Actual fact it is the reverse of receiving data!
rVal=connect(theSocket,(LPSOCKADDR)&serverInfo, sizeof(serverInfo));
if(rVal==SOCKET_ERROR)
{
sError("Failed connect()");
return CS_ERROR;
}
char *buf = "data";
rVal = send(theSocket, buf, strlen(buf), 0);
if(rVal == SOCKET_ERROR)
{
sError("Failed send()");
return CS_ERROR;
}
part 3 - Read unknow size of data from client
Us mentioned earlier in part 2, we are noe going to expand on the way that we
receive data. The problem we had before is that if we did not know the size of
data that we where expecting, then the would end up with problems.
In order to fix this here we create a new function that receive a pointer to
the client socket, and then read a char at the time, placing each char into a
vector until we find the '\n' character that signifies the end of the
message.
This solution is clearly not a robust or industrial way the read data from
one socket to an other, because but its a way to start reading unknown length
strings. the function will be called after the accept
method
char * readline(SOCKET *client)
{
vector<char> theVector;
char buffer;
int rVal;
while(true)
{
rVal = recv(*(client), &buffer, 1, 0);
if(rVal == SOCKET_ERROR)
{
int errorVal = WSAGetLastError();
if(errorVal == WSAENOTCONN)
{
socketError("Socket not connected!");
}
socketError("Failed recv()");
WSACleanup();
}
if(buffer == '\n')
{
char *data = new char[theVector.size() + 1];
memset(data, 0, theVector.size()+1);
for(int i=0; i<theVector.size(); i+=1)
{
data[i] = theVector[i];
}
cout << data << endl;
return data;
}
else
{
theVector.push_back(buffer);
}
}
}
As we can see this is simple and rudimentary way to read a line, we can
increase its functionality adding support for \b, \r, \0, and others depending
on what the need is.
Conclusion
I hope this tutorial has been of some use, even if the code implementation is
fairly simple it might be of help to further develop programs that need to
implement the socket API.
There are many more things to consider when developing with sockets, i.e.
NON-Blocking and Asynchronous Socket. I intend to write some more regarding the
obove topics mentioned as they are very important for more robust and industrial
programs