![]() |
Plugging into OS/2 Socket Programming - Part 1/3Written by Edward D. Boykin |
IntroductionHeya! I would like to welcome you folks to a new series of EDM/2 articles on Client/Server programming with OS/2 Warp. Client/Server programming encompasses a whole slew of different forms; so, for now, this series will focus entirely on the peer-to-peer LAN protocol referred to as "sockets". I am guessing that there will be three or four articles in this series but there might be more or less depending on how well I feel I am covering the topics. This is my first shot at writing anything even remotely like this so if I stumble some please bear with me. Thanks! If anybody notices some errors or better ways of doing some of the things I am doing please feel free to e-mail me. The same goes if you have any questions on any of the subjects discussed here. I must also make another apology. The code I use here is based on the IBM TCP/IP Development Toolkit which is a commercial product. I am therefore unable to distribute the header files needed to compile the source code. I have included copies of the executables so that you may see the results of the code. The executables are compiled with full debugging on so you can step through it if you have the IPMD debugger. I would make the code portable between EMX and C- Set++ but I don't have the time. Path of StudyOver the next few months I will cover everything necessary for you to get started in socket programming. The articles will start with a very simple client and server program which I will then develop into a basic message system allowing multiple client connections to a single server. It will allow the people on the client end to connect to the server and chat with anyone else connected to the same server in near real-time. However, we have to walk before we can run. This month I will start with an introduction to the basic functions in the socket API. These are the minimal functions that are necessary to create the most basic client and server programs. I have included just such a set of programs and encourage you to examine them while you read through this article. What is a Socket?HistoryBack in 1981, sockets were first introduced as the UNIX BSD 4.2 generic interface that would provide point to point communications for TCP/IP networks. After a couple of revisions and some stiff competition from other similar protocols, the socket API has become a very prevalent peer to peer protocol. This is good because it means my articles are not wasted electrons. Anyway, since then support for sockets has migrated to just about every operating environment including MS-DOS, OS/2, UNIX, MacOS etc. In the BSD UNIX system, sockets are integrated in the system kernel while in the non-BSD systems, like OS/2, the socket services are provided in the form of libraries. This wide range of availability makes sockets one of the best portable network communication protocols. DescriptionA socket is a peer to peer communications construct. This construct basically consists of a name, or descriptor, and a network address. There are a few different protocol families in which a socket can operate. These include AF_INET, AF_UNIX, AF_PUP and AF_APPLETALK. OS/2 currently only supports AF_INET. This family designation is mainly used to define the addressing scheme which the socket will use. There are three types of sockets which OS/2 can use. These are stream, datagram, and raw sockets. Stream sockets interface with TCP, datagram sockets interface with UDP, and raw sockets interface with ICMP and IP. The most commonly used socket type is the stream socket and this is what we will use in our sample programs. The stream socket is the most commonly used because of its reliability. Packets sent over a stream socket are guaranteed to be sent without error or duplication and they will be received in the same order they are sent in. There are no boundaries imposed on the data. Think of the data as a simple stream of bytes flowing from one computer to another. The only real bummer with stream sockets is that they are slower and require some extra programming overhead. NOTE: Because there are no data boundaries, TCP does not know where a message ends or begins. This means that if you send two packets to someplace you must have some way of letting they receiving end know how to delimit the two different packets. Don't worry; it's a piece of cake. TCP/IP Internet AddressingThe basic socket address consists of an internet address and a port address. The internet address is a 32 bit number that many of us are used to seeing as a series of four three digit numbers that are separated by decimals. This number is unique for every network interface within a domain. For instance, I have two internet addresses on my computer at home. One of the addresses is for my PPP connection and the other is for my Ethernet card. A port address is a 16 bit integer representing where the entry point for a particular service is on a server machine. If you have TCP/IP installed, you can look in the \TCPIP\ETC\SERVICES file and see a long list of the most commonly used ports. These are reserved and you should not set up any servers which use the ports for anything other than what the SERVICES file specifies. The Socket APIThe complete socket API consists of 58 different functions which can be broken down into 21 core functions and 37 utility functions. Since there are so many I will only explain 10 of the core functions and then only the utility functions I need for this months programs. As the series continues, I will use more functions and I'll be sure to explain them at that time. Socket Setup FunctionsThese are the functions which are used by the client and server to set up the sockets on their respective ends. int sock_init()Figure 1) sock_init() function The sock_init() call is used to initialize the socket data structures and check to see if INET.SYS is running. Every program which uses sockets must use this call before doing any socket related activities. The return values for this call are: 0 - Failure 1 - Success int socket ( int domain, // This is the address type. It must always be AF INET int type, // This is the type of socket created. SOCK_STREAM, // SOCK_DGRAM, and SOCK_RAW are the possible choices int protocol // The protocol requested. Most commonly 0. Others are // available. )figure 2) socket() function The purpose of the socket() function is to create a socket and return it descriptor. It will return a non-negative integer if the function succeeds and will return -1 if an error occurs. int bind ( int s, // This is the socket to which an address is // bound struct sockaddr *name, // Pointer to the sockaddr structure containing // the name to bind to socket s int namelen // Size of the buffer which name points to in // bytes )Figure 3) bind() function bind() is used to bind a unique port number to a socket. This is how a server uses a specific port for it's service. The system can assign a port for you automatically but there is no way for clients wanting to connect to your server to know what that port is. bind() will return 0 on success and -1 on failure. A Brief Aside.....The sockaddr structureThe sockaddr structure, defined in <sys/socket.h>, is what it used to define a socket address. It is defined as follows: struct sockaddr { ushort sa_family, //The address family which is always //AF_INET for OS/2 char sa_data[14], // 14 bytes of direct access data which //is different for each family type }Figure 4) sockaddr structure Because OS/2 only uses the AF_INET family, the sockaddr structure may be substituted with the sockaddr_in structure. This is just a structure with the 14 data bytes defined for the internet addressing scheme. It is defined in <netinet/in.h> header and looks like this: struct sockaddr_in { short sin_family; // AF_INET always for OS/2 ushort sin_port; // Port to be used in network byte order struct in_addr sin_addr; // The internet address to be used. // It is also in network byte order char sin_zero[8]; // Extra junk. Set it to all zeros }Figure 5) sockadd_in structure Host and Network Byte Orders - Yet Another Brief AsideMachines on a network can have different ways of storing integers. Intel machines, such as the PC we run OS/2 on, use "little endian", while Motorola and SPARC machines use "big endian". Sockets require that all data coming in and going out be in network byte order, which is most significant byte first or "big endian". There are a couple of socket utility functions used to convert integers from little to big endian and vise-versa. The naming scheme for most of these functions makes it very easy to remember them so rather than listing all of the functions I will only explain the scheme. The function names are composed of 4 parts. First is the byte order to translate from. This is either n or h for network or host respectively. Second is just "to". Third is the byte order to translate to. This is also either n or h. Last is the type of integer being translated. It will be l or s for a long or short integer. The functions all return the same integer type as what they are converting. For instance: To convert a port number to network byte order, which must be done for the port in the sockaddr_in structure, you would use the function htons(). There are also two functions which can be used to swap the bytes in an
integer. These are bswap() and lswap() for short and long integer
respectively. All of these functions are defined in The soclose() function is used to shut a socket down, free its resources,
and close the TCP connection if one is open. It will return 0 if
successful and -1 if it failed.
These functions are used by clients and server to initiate and manage
socket connections from one another.
connect() is one of the mainstay functions in a socket program. You use it
with a clients to connect between two machines on a network. Connect will
actually perform two tasks when it is called. The first is to complete any
binding necessary for a stream socket (in case you haven't already used
bind() for this socket). Second, It attempts to make the connection to the
host supplied in the sockaddr structure.
In order for connect() to succeed, the point to which it is attempting to
connect must have a passive open socket pending. In other words, the
server had better be listening after calling bind() and listen(). If this
is not the case, connect() will return a -1.
Depending on how the socket you supply in s is configured, connect() will
either block or not block. Using our unmodified sockets for now, we will
assume that connect() is always in blocking mode. This means that while
connect() is trying to connect the program or thread will not continue
until a connection is established or an error is received. I will cover
the non-blocking situation in a future article.
The listen() call applies only to stream sockets and performs two tasks. It
will complete any binding if necessary (i.e. you forgot to do it) and then
it will create a connection request queue of length backlog. After this
queue is full, no further connections will be accepted.
listen() is called when your server is ready to begin accepting connections
from clients. You must first allocate a stream socket using the socket()
function then bind that socket to the socket address structure (or name) of
the server. Listen must be called before you can accept any connections.
Backlog can be any number between 0 and SOMAXCONN. SOMAXCONN is defined in
<sys/socket.h>.
accept() is what is used by a server to accept the incoming connection
requests from a client. The first connection request in the queue set up
by listen() is the connection which is accepted. A new socket will be
created by accept() with the same properties as socket s. It is this socket
which accept() will return. If there are no pending connection requests
then accept() will block until a request is made. Non-blocking will be
covered in a later article. The new socket which is returned cannot be
used to accept new connections; however, s is still available to accept
more requests.
The sockaddr structure in the parameter list is used to store the socket
address structure of the connection requester. This parameter can be NULL
if you have no need for this information. I find it handy to have this
information so I advise you to go ahead and grab it even if you don't plan
on using it. If you do get lazy and set the name to NULL then you don't
have to worry about the namelen parameter either.
These functions are for transferring data to and from connected sockets.
There are two more functions for receiving and sending data which I will
cover later. They are a bit harder to handle than the two I have included
here.
recv() is used to receive data from a connected socket. If successful it
will return the length, in bytes, of the message packet. A value of 0
indicates the connection is closed and -1 indicates an error.
recv() will block, if the socket is in blocking mode, until a message
arrives and then places the incoming data into the buffer and returns its
length. If the incoming data is too large to fit into the buffer than the
socket will read up to the parameter specified in len. There is a method
to check and see the amount of data waiting to be read but it is part an
advanced topic I don't want to sling at you right now.
There are two parameters you can "OR" together to modify how recv() reads
the incoming data. The first, MSG_OOB, is for datagram socket and will not
be covered. The other is MSG_PEEK. This flag allows you 'peek' at the
data without consuming it. This means the data is still waiting to be
fully read in from the socket. Normally, NULL is used in this parameter.
send() is recv()'s counterpart. It operates in the same manner as recv()
except it sends the data through the socket. send() will send all of the
data in msg as long as it doesn't exceed the total socket buffer space. If
is exceeds this buffer size then send will block until all of the data can
be sent. This will only occur if you send a large amount of data rapidly
through the socket.
Both of the flags which can be set for send() are not normally used;
therefore, I will skip them. Use a 0 in the flag parameter when using this
function call.
This wraps up the discussion on what I feel are the minimal functions
needed to set up a very basic client and server. At this point you should
have a basic understanding of how to program a sockets based application.
I hope this article will help you to get started in programming with
sockets. It may seem like there is a lot of information needed to program
with sockets; however, once this slightly steep learning curve is past it
becomes second nature to program sockets quickly and easily.
Next month I will be adding more features to the simple client/server
program and discuss more of the socket utility API. Until then I urge you
to read over my sample code and play with the two programs to get a good
feel of how they are working. Thanks for reading.. See you next month!
|