CS 620 Advanced Operating Systems Lecture 3 – UNIX IPC Programming Review Professor Timothy Arndt BU 331 UNIX Programming Review • UNIX manages multiple concurrent processes. Each process is represented by an entry in the process table. A process goes through a number of states before running to completion • • • • Running Asleep Ready Zombie Process Creation • The fork system call is used inside a C program to create another process. After the fork call, both a parent and child process are running. The child process has a unique PID, and PPID, but otherwise is equivalent to the parent (i.e. it is a copy of the same program). The PID is used to distinguish the child from the parent process. Process Creation #include <stdio.h> #include <unistd.h> #include <stdlib.h> main() { int child_id; child_id = fork(); /* process creation */ if ( child_id == 0 ) /* child code begin */ { printf("Child says my pid = %d and my parent pid = %d\n", getpid(), getppid()); _exit(0); /* child terminates (i) */ } /* child code end */ /* remaining parent code */ if ( child_id < 0 ) { fprintf(stderr, "fork failed\n"); exit(1); } printf("Parent says child pid = %d\n", child_id); } Program Execution: exec • The child process can also run a different program than the parent by overlaying a new executable file on itself. This is done using one of the exec routines: execl, execv, execve, etc. The execed file can either be an executable binary file or an executable text file (e.g. a shell script). The various exec routines vary in the type and number of arguments they take. Exec Example #include <stdio.h> #include <unistd.h> #include <string.h> #define MAXLINE 80 int main() { char cmd[MAXLINE]; void background(char *cmd); for (;;) { printf("mysh ready%%"); /* prompt */ fgets(cmd, MAXLINE, stdin); /* read command */ if ( strcmp(cmd,"exit") == 0 ) return(0); background(cmd); /* start background job */ } } Exec Example #define WHITE "\t \n" #define MAXARG 20 void background(char *cmd) { char *argv[MAXARG]; int id, i = 0; /* to fill in argv */ argv[i++] = strtok(cmd, WHITE); while ( i < MAXARG && (argv[i++] = strtok(NULL, WHITE)) != NULL ); if ( (id = fork()) == 0) /* child executes background job */ { execv(argv[0], argv); _exit(1); /* execv failed */ } else if ( id < 0 ) { fprintf(stderr, "fork failed\n"); perror("background:"); } } Synchronization of Parent and Child After creating a child process using fork, the parent may run independently, or it may wait for the child process to terminate before proceeding further. The wait call searches for a terminated child (in the zombie state) of the calling process. If there are no child processes, wait returns with a value of -1 If one or more child processes are in the zombie state, wait selects an arbitrary child, frees its process table slot, stores its exit status and returns it PID Otherwise, wait blocks until one of the child processes terminates. Synchronization of Parent and Child #include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main() { int pid1, pid2, pid; union wait status; if ((pid1 = fork()) == 0) /* child one */ { printf("child pid=%d\n", getpid()); _exit(0); } printf("forking again\n"); if ((pid2 = fork()) == 0) /* child two */ { printf("child pid=%d\n", getpid()); _exit(1); } Synchronization of Parent and Child printf("first wait\n"); pid = wait(&status); printf("pid=%d, status=%d\n", pid, status); printf("2nd wait\n"); pid = wait(&status); printf("pid=%d, status=%d\n", pid, status); return(0); } More on Wait • The waitpid system call can be used to search for a particular child Another system call – waitid – provides even more flexibility. • The ‘status’ returned can be examined more closely to get info about how the process terminated by using one of several macros WEXITSTATUS(status), WIFSIGNALED(status), WCOREDUMP(status), WSTOPSIG(status), etc. Interrupts and Signals Signals are used in UNIX to tell a running process that some event has occurred. After receiving a signal, a process reacts to it in a well-defined manner. There are a number of different signals that can be sent: SIGHUP 1; SIGINT 2; SIGQUIT 3; SIGILL 4; SIGTRAP 5; SIGFPE 8; SIGKILL 9; SIGBUS 10; SIGSEGV 11; SIGSYS 12; SIGVTALRM 26; SIGPROF 27. Interrupts and Signals From the shell, a signal can be sent using the kill command with the signal to be sent along with the PID. From a C program, the function raise sends a signal to the same process, while the function kill sends a signal to some other process. If a signal is not currently blocked by a process, further occurrences of the signal are blocked during signal handling. Interrupts and Signals The function is suspended and the handler function for the signal is called, and finally if the handler function returns, the signal is unblocked and normal execution of the process resumes from the point of interrupt. After receiving a signal, a process normally either exits or stops. This default behavior can be changed using the signal function, specifying a handler function for a specific signal. When that signal is received, the handler function will be invoked. Signal Trapping /* this program demonstrates the use of signal to trap * interrupts from the terminal. To terminate the program * type ^\ */ #include <signal.h> #include <stdio.h> #include <unistd.h> main() { void cnt(int sig); signal(SIGINT, cnt); printf("Begin counting and INTERRUPTs\n"); for(;;); /* infinite loop */ } void cnt(int sig) { static int count=0; printf("Total of %d INTERRUPTS received\n", ++count); IPC and Network Communication Two processes residing either on the same computer or on different computers may need to exchange data. This is known as Inter Process Communication (IPC). If the two processes are related by a fork, IPC can be done through the pipe mechanism (the processes are on the same machine). If the two processes are not related, IPC can be performed using the socket mechanism. Pipes A pipe is a direct (in memory) I/O channel between processes. It is used with the calls fork, exec, wait, and exit to make multiple processes cooperate. To establish a pipe use the system call int pipe(int fildes[2]) • This establishes a buffer and two descriptors fildes[0] for reading the pipe and fildes[1] for writing the pipe. • The pipe is created before the fork, so both parent and child have copies of the pipe. Pipes I/O is performed through a pipe using the read and write calls. Read removes characters from the buffer and write deposits them. The buffer size is usually 4096 characters. If we write a full pipe buffer, the process blocks until more space is available in the pipe. If we read an empty buffer, the reading process blocks unless the write end of the pipe has been closed - in this case 0 (end of file) is returned. Pipes #include #include #include #include <stdio.h> <unistd.h> <string.h> <sys/wait.h> int main(int argc, char *argv[]) { int p[2]; int i, pid, status; char buffer[20]; pipe(p); /* setting up the pipe */ if ((pid = fork()) == 0) /* in child */ { close(p[1]); /* child closes p[1] */ while ((i = read(p[0], buffer, 6)) != 0) { buffer[i] = '\0'; /* string terminator */ printf("%d chars :%s: received by child\n", i, buffer); } _exit(0); /* child terminates */ } Pipes /* in parent */ close(p[0]); /* parent writes p[1] */ write(p[1], "Hello there,", sizeof("Hello there,")); write(p[1], " from me.", sizeof(" from me.")); close(p[1]); /* finished writing p[1] */ while (wait(&status)!=pid); /* waiting for pid */ if (status == 0) printf("child finished\n"); else printf("child failed\n"); return(0); } The dup2 Command • The following example shows the use of the dup2 command which duplicates an existing I/O descriptor. Pipes #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char *argv[]) { int p[2]; int i,pid1,pid2, status; argv++; /* lose argv[0] for (i = 1; i <= argc ; i++) if (strcmp(argv[i],"%") == 0) { argv[i] = '\0'; /* break into two commands break; } pipe(p); /* setting up the pipe if ((pid1 = fork ()) == 0) /* child one { close(p[1]); dup2(p[0],0); /* 0 becomes a duplicate of p[0] close(p[0]); execv(argv[i+1], &argv[i+1]); /* this reads the pipe */ */ */ */ */ */ Pipes _exit(1); /* bad error execl failed */ } if ((pid2 = fork ()) == 0) /* child two { close(p[0]); dup2(p[1],1); /* 1 becomes a duplicate of p[1] close(p[1]); execv(argv[0],argv); /* this writes the pipe _exit(1); /* bad error execv failed } /* parent does not use pipe */ close(p[0]); close(p[1]); while (wait(&status)!=pid2); /* waiting for pid2 if (status == 0) printf("child two terminated\n"); else printf("child two failed\n"); exit(0); } */ */ */ */ */ Two-Way Pipe Connections • In order to have two-way pipe connections between two processes, two pipes must be used. This is shown in the following example. Two-Way Pipe Connections #include <stdio.h> #include <string.h> int readl(int fd, char s[], int size) { char *tmp = s; while (0 < --size && read(fd, tmp, 1) != 0 && *tmp++ != '\n'); *tmp = '\0'; /* string terminator */ return(tmp - s); } int pipe_2way(char *cmd[], int piped[]) { int pid, wt[2], rd[2]; pipe(rd); /* incoming pipe: read by parent */ pipe(wt); /* outgoing pipe: write to child */ if ((pid=vfork()) == 0) Two-Way Pipe Connections { close(wt[1]); /* in child */ dup2(wt[0],0); /* 0 identified with wt[0] close(wt[0]); close(rd[0]); dup2(rd[1], 1); /* 1 identified with rd[1] close(rd[1]); execv(cmd[0],cmd); /* execute given command perror("execv failed"); /* normally not reached _exit(1); } close(wt[0]); /* in parent */ piped[1] = wt[1]; close(rd[1]); piped[0] = rd[0]; return(0); } */ */ */ */ Two-Way Pipe Connections #define SIZE 256 int main() { int pd[2]; char *str[2]; char test_string[] = "IPC WITH TWO-WAY PIPE.\n"; char buf[SIZE]; char *tmp = buf; str[0] = "./lowercase"; str[1] = NULL; pipe_2way(str, pd); /* write to lowercase process */ write(pd[1], test_string, strlen(test_string)); readl(pd[0], buf, SIZE); /* read from lowercase process */ printf("Received from lowercase process:\n%s", buf); return(0); } Two-Way Pipe Connections /***** The lowercase program *****/ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define BUFSIZE 1024 void lower(char *buf, int length) { while (length-- > 0) { *buf = tolower( *buf ); buf++; } } Two-Way Pipe Connections int main(int argc, char *argv[]) { char buffer[BUFSIZE]; int nc; while ((nc = read(0, buffer, BUFSIZE)) > 0) { lower(buffer,nc); nc = write(1, buffer, nc); if (nc == -1) break; } if (nc == -1) { perror(argv[0]); exit(1); } return(0); } Named Pipes • Linux provides a similar IPC mechanism called named pipes (or FIFOs). Can be used to communicate between 2 unrelated processes • • • • • Have an entry in the (file) directory Create using mknod (from shell or program) Have permissions (like files) Survive the end of a program (like files) Must be explicitly removed (like files) Sockets Sockets are abstractions that serve as endpoints of communication within a networking domain. The socket is the ipc mechanism’s interface to application programs. Each socket can exchange data with any other socket within the same domain (e.g. the Internet domain). Each socket is assigned a type property. Different type sockets use different protocols. • Stream sockets support bidirectional, reliable, sequenced flow of data Stream sockets in the Internet domain use TCP/IP. Sockets Datagram sockets provide bidirectional flow of data packets called messages • The communication channel is not sequenced, reliable, or unduplicated • A datagram socket does not have to be connected to a peer • A message is sent to a datagram socket by specifying its address • Datagram sockets in the Internet domain use UDP/IP. Raw sockets give access to the underlying communications protocol • They are not intended for the general user • In the Internet domain, they give direct access to the Internet Protocol (IP). Sockets The socket system call creates a socket of a particular type in a particular domain • The type and domain are given by constants defined in sys/socket.h Sockets must then be given an address so that other processes can refer to them • In Internet domain, socket address consists of combination of host IP address and port number • Standard network services are assigned the same port number on each host • A database file contains a list of services and port number. Sockets grail:/users/faculty/arndt> cd /etc grail:/etc> more services … daytime 13/tcp daytime 13/udp qotd 17/tcp quote qotd 17/udp quote msp 18/tcp msp 18/udp chargen 19/tcp ttytst source chargen 19/udp ttytst source ftp-data 20/tcp ftp-data 20/udp # 21 is registered to ftp, but also used by fsp ftp 21/tcp ftp 21/udp fsp fspd ssh 22/tcp ssh 22/udp telnet 23/tcp telnet 23/udp # 24 - private mail system lmtp 24/tcp lmtp 24/udp smtp 25/tcp mail … # message send protocol # message send protocol # SSH Remote Login Protocol # SSH Remote Login Protocol # LMTP Mail Delivery # LMTP Mail Delivery Sockets • To bind a name to socket, use the system call bind(int soc, struct sockaddr *addr, int addrlen) • The following example illustrates the use of datagram sockets with a sender process and a receiver process. Datagram Sockets #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/file.h> #include <sys/socket.h> #include <sys/un.h> /* UNIX domain header */ int main() { int soc; char buf[] = "Hello there"; const char *NAME = "./receiver_soc"; struct sockaddr_un peer; /* (1) */ int n; peer.sun_family = AF_UNIX; strcpy(peer.sun_path, NAME); soc = socket(AF_UNIX, SOCK_DGRAM, 0); /* (2) */ Datagram Sockets if ( access(peer.sun_path, F_OK) > -1 ) /* (3) */ { n = sendto(soc, buf, strlen(buf), /* (4) */ 0, (struct sockaddr *)&peer, sizeof(peer)); if ( n < 0 ) { fprintf(stderr, "sendto failed\n"); exit(1); } printf("Sender: %d characters sent!\n", n); /* (5) */ close(soc); /* (6) */ } return(0); } Datagram Sockets /***** File : receiver.c *****/ /***** datagram socket example: receiver process *****/ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> /* UNIX domain header */ void cleanup(int soc, char *file) { close(soc); unlink(file); } Datagram Sockets int main() { int soc; char buf[64]; const char *NAME = "./receiver_soc2"; struct sockaddr_un self; /* (A) */ struct sockaddr_un peer; int n, len; self.sun_family = AF_UNIX; strcpy(self.sun_path, NAME); soc = socket(AF_UNIX, SOCK_DGRAM, 0); /* (B) */ n = bind(soc, (const struct sockaddr *)&self, sizeof(self)); if ( n < 0 ) { fprintf(stderr, "bind failed\n"); exit(1); } n = recvfrom(soc, buf, sizeof(buf), /* (D) */ 0, (struct sockaddr *)&peer, &len); Datagram Sockets if ( n < 0 ) { fprintf(stderr, "recvfrom failed\n"); cleanup(soc, self.sun_path); exit(1); } buf[n] = '\0'; printf("Datagram received = %s\n", buf); cleanup(soc, self.sun_path); return(0); } /* (E) */ /* (F) */ Datagram Sockets • Note that we have two separate processes (we have two main functions). Both must be compiled. • In order to run the example, both processes must be launched: receiver & sender • The sendto and recvfrom system calls are normally used with datagram sockets. Stream Sockets A stream socket is connected with its peer to form a two-way pipe between a client and a server. • The server process listens for connection requests and accepts connections. The server process binds a known address to a socket. • The client process uses its socket to initiate a connection to a socket of a server process. The client process finds the correct address of the server. Then initiates a connection to the server process. • After connection, data communication takes place using the read and write I/O system calls. Stream Sockets The connect system call - connect(int soc, struct sockaddr *name, int namelen) associates the client socket given by the descriptor soc to a peer socket in a server specified by *name. The connect call is often used by client programs to connect to stream sockets of known services on the Internet. A pair of sockets forms a virtual circuit between the client and server process. Stream Sockets A server process with a stream socket needs to take the following steps to get ready to accept a connection: • Create a socket in the appropriate domains of type SOCK_STREAM • Construct the correct address and bind it to the socket • Indicate a willingness to accept connection requests by executing the listen system call. • Use the accept call to wait for a connection request and establish a connection. Stream Sockets int listen(int soc, int n) initializes the socket for incoming requests and sets the maximum number of pending connections. int accept(int soc, struct sockaddr *addr, int *addrlen) accepts connections on the socket on which a listen has been executed • If there are pending connections, the first is chosen and a new socket is created with the same properties as soc This socket is connected and a descriptor of the socket is returned. Stream Sockets The listening socket remains ready to receive connection requests. If no connections are pending and the socket is not marked as nonblocking (using system call fcntl), accept blocks until a connection request arrives. If the connection is marked as nonblocking and no requests are pending, accept returns an error. • The accepted socket communicates with its peer and may not accept additional connections. Stream Sockets For connected sockets, the basic read and write system calls send and receive data: • write(soc, buffer, sizeof(buffer)); • read(soc, buffer, sizeof(buffer)); Each process reads and writes its own socket, resulting in a bidirectional data flow between the connected peers. The socket I/O calls • send(soc, buffer, sizeof(buffer), opt); • recv(soc, buffer, sizeof(buffer), opt); Can be used by stream sockets to send out-ofband data by setting opt to MSG_OOB. Stream Socket Example /***** stream socket example: server.c *****/ #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> /* UNIX domain header */ #include <stdlib.h> #include <stdio.h> int main() { int soc, ns, k; char buf[256]; struct sockaddr_un self = {AF_UNIX, "serversoc"}; struct sockaddr_un peer = {AF_UNIX}; int peer_len = sizeof(peer); /* set up listening socket soc */ soc = socket(AF_UNIX, SOCK_STREAM, 0); /* (1) */ if (soc < 0) { perror("server:socket"); exit(1); } Stream Socket Example if (bind(soc, (struct sockaddr *)&self, sizeof(self)) == -1) { perror("server:bind"); close(soc); exit(1); } listen(soc, 1); /* accept connection request */ ns = accept(soc, (struct sockaddr *)&peer, &peer_len); if (ns < 0) { perror("server:accept"); close(soc); unlink(self.sun_path); exit(1); } /* data transfer on connected socket ns */ k = read(ns, buf, sizeof(buf)); printf("SERVER RECEIVED: %s\n", buf); write(ns, buf, k); close(ns); close(soc); unlink(self.sun_path); return(0); } Stream Socket Example /***** File : client.c *****/ /***** socket example: receiver process *****/ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> /* UNIX domain header */ int main() { int soc; char buf[256]; struct sockaddr_un self={AF_UNIX, "clientsoc"}; struct sockaddr_un peer={AF_UNIX, "serversoc"}; soc = socket(AF_UNIX, SOCK_STREAM, 0); bind(soc, (struct sockaddr *)&self, sizeof(self)); /* request connection to serversoc */ if (connect(soc, (struct sockaddr *)&peer, sizeof(peer)) == -1) { perror("client:connect"); close(soc); Stream Socket Example unlink(self.sun_path); exit(1); } write(soc, "hello from client", 18); read(soc, buf, sizeof(buf)); printf("SERVER ECHOED: %s\n", buf); close(soc); unlink(self.sun_path); return(0); } /*** end of client.c ***/ Stream Socket Example • Note that it is not strictly necessary to bind an explicit address for a client process • The close system call closes both halves of a socket • To close the read and write halves independently, the shutdown system call can be used Network Databases and Address Mapping The previous example programs involved sockets with UNIX domain addresses - a simple name For Internet domain sockets, the socket address involves both the numeric IP address of a host as well as a port number for a particular server The DNS, a set of database files, and a collection of library functions combine to help construct required Internet socket addresses in application programs Network Databases and Address Mapping Some of the important files for name resolution are: • /etc/resolv.conf - the configuration file for the DNS resolver. Lists name servers for the local domain. • /etc/named.boot - the DNS name server boot file. Needed only on a host that runs a name server. Gives the locations of root name servers, e-mail exchange information, etc. • /etc/services - contains information regarding well known Internet services. • /etc/protocols - contains information regarding known Internet protocols. Network Databases and Address Mapping grail:/etc> more /etc/resolv.conf search cba.csuohio.edu csuohio.edu nameserver 137.148.49.12 nameserver 137.148.49.11 grail:/etc> more named.boot ; ; type domain source file ; directory /etc/named.data ; running directory for named primary 0.0.127.IN-ADDR.ARPA db.127.0.0 primary cba.csuohio.edu db.cba primary 20.148.137.IN-ADDR.ARPA db.137.148.20 secondary csuohio.edu 137.148.5.2 db.137.148 cache . db.cache primary 21.148.137.IN-ADDR.ARPA db.137.148.21 Network Databases and Address Mapping grail:/etc> more /etc/protocols # /etc/protocols: # $Id: protocols,v 1.6 2007/05/23 15:55:03 pknirsch Exp $ # # Internet (IP) protocols # # from: @(#)protocols 5.1 (Berkeley) 4/17/89 # # Updated for NetBSD based on RFC 1340, Assigned Numbers (July 1992). # # See also http://www.iana.org/assignments/protocol-numbers ip 0 number hopopt 0 icmp 1 igmp 2 ggp 3 ipencap 4 ``IP'’) IP # internet protocol, pseudo protocol HOPOPT ICMP IGMP GGP IP-ENCAP # # # # # hop-by-hop options for ipv6 internet control message protocol internet group management protocol gateway-gateway protocol IP encapsulated in IP (officially Network Databases and Address Mapping st tcp cbt 5 ST 6 TCP 7 CBT <A.Ballardie@cs.ucl.ac.uk> egp 8 EGP igp 9 IGP IGRP) bbn-rcc 10 BBN-RCC-MON # Network Voice Protocol pup 12 PUP argus 13 ARGUS emcon 14 EMCON xnet 15 XNET chaos 16 CHAOS udp 17 UDP mux 18 MUX dcn 19 DCN-MEAS hmp 20 HMP prm 21 PRM … # ST datagram mode # transmission control protocol # CBT, Tony Ballardie # exterior gateway protocol # any private interior gateway (Cisco: for # BBN RCC Monitoringnvp # # # # # # # # # # 11 PARC universal packet protocol ARGUS EMCON Cross Net Debugger Chaos user datagram protocol Multiplexing protocol DCN Measurement Subsystems host monitoring protocol packet radio measurement protocol NVP-II Network Databases and Address Mapping The name resolver can be used in an application program by a set of standard routines. • The library function gethostbyname: #include <netdb.h> hostent *gethostbyname(char *host) • given a host name (a string) returns a pointer to a hostent structure struct hostent { char *h_name; /* official name of the host */ char **h_aliases; /* aliases */ int h_addrtype; /* address type: AF_INET */ int h_length; /* length of address */ char **h_addr_list; /* IP addresses */ }; Network Databases and Address Mapping The IP address can then be copied into the sin_addr field of a sockaddr_in structure for a target socket. To determine the port number for standard network services use: struct servent *getservbyname(char *service, char *proto) struct servent { char *s_name; /* official name of service */ char **s_aliases; /* alias list */ int s_port; /* port number: network byte order */ char *s_proto; /* protocol used */ }; Internet Stream Socket Example /***** inetserver.c *****/ #include <stdlib.h> /* for getenv */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> /* Internet domain header */ #include <strings.h> #define SERVER_PORT 59154 struct sockaddr_in self = {AF_INET}; int main() { int soc, ns, k; char buf[256]; struct sockaddr_in peer = {AF_INET}; int peer_len = sizeof(peer); char *host; /* set up listening socket soc */ Internet Stream Socket Example soc = socket(AF_INET, SOCK_STREAM, 0); if (soc < 0) { perror("server:socket"); exit(1); } bzero(&self, sizeof(self)); self.sin_family = AF_INET; self.sin_addr.s_addr = htonl(INADDR_ANY); self.sin_port = htons(SERVER_PORT); if (bind(soc, (struct sockaddr *)&self, sizeof(self)) == -1) { perror("server:bind"); close(soc); exit(1); } listen(soc, 1); /* accept connection request */ ns = accept(soc, (struct sockaddr *)&peer, &peer_len); if (ns < 0) { perror("server:accept"); close(soc); exit(1); } /* data transfer on connected socket ns */ Internet Stream Socket Example k = read(ns, buf, sizeof(buf)); host = getenv("HOST"); printf("SERVER ON %s RECEIVED: %s\n”, host, buf); write(ns, buf, k); close(ns); close(soc); return(0); } /***** inetclient.c *****/ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> /* Internet domain header */ #include <netdb.h> Internet Stream Socket Example #include <strings.h> #define SERVER_PORT 59154 struct sockaddr_in peer = {AF_INET}; int main(int argc, char* argv[]) { int soc; char buf[256]; struct hostent *hp; if ( argc != 2 ) { fprintf(stderr, "Usage: %s hostname\n", argv[0]); exit(1); } /* fill in peer address */ hp = gethostbyname(argv[1]); if ( hp == NULL ) { fprintf(stderr, "%s: %s unknow host\n", argv[0], argv[1]); Internet Stream Socket Example exit(1); } bzero(&peer, sizeof(peer)); peer.sin_family = AF_INET; peer.sin_addr.s_addr = htonl(INADDR_ANY); peer.sin_port = htons(SERVER_PORT); bcopy(hp->h_addr_list[0], (char*)&peer.sin_addr, hp->h_length); /* create socket */ soc = socket(AF_INET, SOCK_STREAM, 0); /* request connection to server */ if (connect(soc, (struct sockaddr *)&peer, sizeof(peer)) == -1) { perror("client:connect"); close(soc); exit(1); } write(soc, "Hello Internet", 15); read(soc, buf, sizeof(buf)); printf("SERVER ECHOED: %s\n", buf); close(soc); return(0);