Author : Reg Quinton
Page : << Previous 3 Next >>
< 0) then error...
return(unit);
}
The result returned is a file descriptor which is connected to a server process. A communications channel on which one can conduct an application specific protocol.
Client Communication:
Having connected a socket to a server to establish a file descriptor communication is with the usual Unix I/O calls. You have Inter Process Communication (or IPC) to a server.
Many programmers turn file descriptors into stdio(3S) streams so they can use fputs, fgets, fprintf, etc. -- use fdopen(3S).
main(argc,argv)
int argc;
char *argv[];
{
int unit,i;
char buf[BUFSIZ];
FILE *sockin,*sockout;
if ((unit=tcpopen(WHOHOST,WHOPORT))
< 0) then error...
sockin=fdopen(unit,"r");
sockout=fdopen(unit,"w");
etc...
fprintf(sockout,"%s\n",argv[i]);
etc...
while (fgets(buf,BUFSIZ,sockin)) etc...
Stdio Buffers:
Stdio streams have powerful manipulation tools (eg. fscanf is amazing). But beware, streams are buffered! This means a well placed fflush(3S) is often required to flush a buffer to the peer.
fprintf(sockout,"%s\n",argv[i]);
fflush(sockout);
while (fgets(buf,BUFSIZ,sockin)) etc...
Many client/server protocols are client driven -- the client sends a command and expects an answer. The server won't see the command if the client doesn't flush the output. Likewise, the client won't see the answer if the server doesn't flush it's output.
Watch out for client and server blocking -- both waiting for input from the other.
Server Applications:
A system offers a service by having an application running that is listening at the service port and willing to accept a connection from a client. If there is no application listening at the service port then the machine doesn't offer that service.
The SMTP service is provided by an application listening on port 25. On Unix systems this is usually the sendmail(1M) application which is started at boot time.
[2:20pm julian] ps -agx | grep sendmail
419 ? SW 0:03 /usr/lib/sendmail -bd -q15m
18438 ? IW 0:01 /usr/lib/sendmail -bd -q15m
[2:28pm julian] netstat -a | grep smtp
tcp 0 0 julian.3155 acad3.alask.smtp SYN_SENT
tcp 0 0 *.smtp *.* LISTEN
In the example we have a process listening to the smtp port (for inbound mail) and another process talking to the smtp port on acad3.alaska.edu (ie. sending mail to that system).
So how do we get a process bound behind a port?
Server Bind:
A Server uses bind(3N) to establish the local host.port assignment -- ie. so it is the process behind that port. That's really only required for servers -- applications which accept(3N) connections to provide a service.
struct servent *sp;
struct sockaddr_in sin;
if ((sp=getservbyname(service,"tcp")) == NULL) then error...
sin.sin_family=AF_INET;
sin.sin_port=sp->s_port;
sin.sin_addr.s_addr=htonl(INADDR_ANY);
if ((s=socket(AF_INET,SOCK_STREAM,0)) < 0) then error...
if (bind(s, &sin, sizeof(sin)) < 0) then error...
htonl(3N) converts a long to the right sequence (given different byte ordering on different machines). The IP address INADDR_ANY means all interfaces. You could, if you wanted, provide a service only on some interfaces -- eg. if you only provided the service on the loopback interface (127.0.0.1) then the service would only be available to clients on the same system.
What this code fragment does is specify a local interface and port (into the sin structure). The process is bound to that port -- it's now the process behind the local port.
Client applications usually aren't concerned about the local host.port assignment (the connect(3N) does a bind o some random but unused local port on the right interface). But rcp(1) and related programs (like rlogin(1) and rsh(1)) do connect from reserved port numbers.
I've done the same in some of my programming. See, for example, the version of tcpopen.c used in our Passwdd/Passwd -- An authentication Daemon/Client. There's an instance where a client application connects from a reserved port.
Listen and Accept:
To accept connections, a socket is created with socket(3N), it's bound to a service port with bind(3N), a queue for incoming connections is specified with listen(3N) and then the connections are accepted with accept(3N) as in this fragment:
struct servent *sp;
struct sockaddr_in sin,from;
if ((sp=getservbyname(service,"tcp")) == NULL) then error...
sin.sin_family=etc...
if ((s=socket(AF_INET,SOCK_STREAM,0)) < 0) then error...
if (bind(s, &sin, sizeof(sin)) < 0) then error...
if (listen(s,QUELEN) < 0) then error...
for (;;) {
if ((g=accept(f,&from,&len)) < 0) then error...
if (!fork()) {
child handles request...
...and exits
exit(0);
}
close(g); /* parent releases file */
}
This is the programming schema used by utilities like sendmail(1M) and others -- they create their socket and listen for connections. When connections are made, the process forks off a child to handle that service request and the parent process continues to listen for and accept further service requests.
But, you really don't want to use that programming paradigm unless you really have to. There are lots of hidden issues (like becoming a detached process and more) that you'd rather avoid.
Fortunately, there's an easier method.
Inetd Services:
Not all services are started at boot time by running a server application. Eg. you won't usually see a process running for the finger service like you do for the smtp service. Many are handled by the InterNet Daemon inetd(1M). This is a generic service configured by the file inetd.conf(4).
[2:35pm julian] page /etc/inetd.conf
# $Author: reggers $
# $Date: 1997/05/02 20:17:16 $
#
# Internet server configuration database
ftp stream tcp nowait root /usr/etc/ftpd ftpd
telnet stream tcp nowait root /usr/etc/telnetd telnetd
shell stream tcp nowait root /usr/etc/rshd rshd
login stream tcp nowait root /usr/etc/rlogind rlogind
exec stream tcp nowait root /usr/etc/rexecd rexecd
uucpd stream tcp nowait root /usr/etc/uucpd uucpd
finger stream tcp nowait nobody /usr/etc/fingerd fingerd
etc...
whois stream tcp nowait nobody /usr/lib/whois/whoisd whoisd
etc...
Inetd Comments:
For each service listed in /etc/inetd.conf the inetd(1M) process, and that is a process is started at boot time, executes the socket(3N), bind(3N), listen(3N) and accept(3N) calls as discussed above. Inetd also handles many of the daemon issues (signal handling, set process group and controlling tty) which we've studiously avoided.
The inetd(1M) process spawns the appropriate server application (with fork(2) and exec(2)) when a client connects to the service port. The daemon continues to listen for further connections to the service while the spawned child process handles the request which just came in.
The server application (ie. the child spawned by inetd(1M)) is started with stdin and stdout connected to the remote host.port of the client process which made the connection. Any input/output by the server appliation on stdin/stdout are sent/received by the client application. You have Inter Process Communication (or IPC)!
This means, any application written to use stdin/stdout can be a server application. Writing a server application should therefore be fairly simple.
Whois Daemon:
On julian we have an entry in /etc/inetd.conf for the UWO/ITS whois service:
[3:25pm julian] grep whois /etc/inetd.conf
whois stream tcp nowait nobody /usr/lib/whois/whoisd whoisd
This is our local directory service -- it's implemented on a TCP/IP stream (all whois services are), at the whois port (all whois services should be at that port), it's ran as user nobody (you don't need to run servers as user root), the program to run is /usr/lib/whois/whoisd, and the command line to the program is just whoisd.
This is a standard whois service -- it implements the trivial protocol required of all whois servers. Any whois client can use the service. The program conducts an application protocol on stdin/stdout (which is usually connected by a TCP/IP socket to a client application). The protocol is trivial -- server accepts a one line query, answers back and exits.
Running the Daemon:
You can
Page : << Previous 3 Next >>