この付録では、基本的な IPv4 のクライアントおよびサーバーと、クライアントおよびサーバーのさまざまな IPv6 ポートについて例を示して説明します。それぞれの例は、コンパイルと実行ができることがわかっています。各クライアントは、各サーバーと自由に組み合わせて使用できます。これらの例は情報目的のためだけに提供されているので、Sun は、これらの例の使用に対して責任を負いません。
第 1 の例は、オリジナルで無修正の IPv4 クライアント、myconnect です。
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <string.h> #include <stdio.h> #include <unistd.h> int myconnect(char *hostname, int port) { struct sockaddr_in sin; struct hostent *hp; int sock; /* ソケットを開く */ sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); return (-1); } /* ホストアドレスの取得 */ hp = gethostbyname(hostname); if (hp == NULL || hp->h_addrtype != AF_INET || hp->h_length != 4) { (void) fprintf(stderr, "Unknown host ¥"%s¥"¥n", hostname); (void) close(sock); return (-1); } sin.sin_family = AF_INET; sin.sin_port = htons(port); (void) memcpy((void *)&sin.sin_addr, (void *)hp->h_addr, hp->h_length); /* ホストに接続 */ if (connect(sock, (struct sockaddr *)&sin, sizeof (sin)) == -1) { perror("connect"); (void) close(sock); return (-1); } return (sock); } main(int argc, char *argv[]) { int sock; char buf[BUFSIZ]; int cc; switch (argc) { case 2: sock = myconnect(argv[1], IPPORT_ECHO); break; case 3: sock = myconnect(argv[1], atoi(argv[2])); break; default: (void) fprintf(stderr, "Usage: %s <hostname> <port>¥n", argv[0]); exit(1); } if (sock == -1) exit(1); if (write(sock, "hello world", strlen("hello world") + 1) == -1) { perror("write"); exit(1); } cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } (void) printf("Read <%s>¥n", buf); return (0); }
第 2 の例は、IPv4 と IPv6 の両方のサーバーに接続できる、myconnect の最小のポート、myconnect2 です。
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <string.h> #include <stdio.h> #include <unistd.h> int myconnect2(char *hostname, int port) { struct sockaddr_in6 sin; struct hostent *hp; int sock, errnum; /* ソケットを開く */ sock = socket(AF_INET6, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); return (-1); } /* ホストアドレスの取得。IPv4 射影 IPv6 アドレス可 */ hp = getipnodebyname(hostname, AF_INET6, AI_DEFAULT, &errnum); if (hp == NULL) { (void) fprintf(stderr, "Unknown host ¥"%s¥"¥n", hostname); (void) close(sock); return (-1); } /* すべての sockaddr_in6 フィールドが 0 であることを確認 */ bzero(&sin, sizeof (sin)); sin.sin6_family = hp->h_addrtype; sin.sin6_port = htons(port); (void) memcpy((void *)&sin.sin6_addr, (void *)hp->h_addr, hp->h_length); freehostent(hp); /* ホストに接続 */ if (connect(sock, (struct sockaddr *)&sin, sizeof (sin)) == -1) { perror("connect"); (void) close(sock); return (-1); } return (sock); } main(int argc, char *argv[]) { int sock; char buf[BUFSIZ]; int cc; switch (argc) { case 2: sock = myconnect2(argv[1], IPPORT_ECHO); break; case 3: sock = myconnect2(argv[1], atoi(argv[2])); break; default: (void) fprintf(stderr, "Usage: %s <hostname> <port>¥n", argv[0]); exit(1); } if (sock == -1) exit(1); if (write(sock, "hello world", strlen("hello world") + 1) == -1) { perror("write"); exit(1); } cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } (void) printf("Read <%s>¥n", buf); return (0); }
第 3 の例は、第 2 の例の拡張版で、1 つが接続するまですべてのアドレスを試行しようとします。これは myconnect2all です。第 3 の例は、同一の機能を持つ第 4 の例よりも複雑であることに注意してください。
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <string.h> #include <stdio.h> #include <unistd.h> #include <errno.h> int myconnect2all(char *hostname, int port) { struct sockaddr_in6 sin; struct hostent *hp; int sock, errnum; int h_addr_index; /* ソケットを開く */ sock = socket(AF_INET6, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); return (-1); } /* ホストアドレスの取得。IPv4 射影 IPv6 アドレスが可 */ hp = getipnodebyname(hostname, AF_INET6, AI_DEFAULT|AI_ALL, &errnum); if (hp == NULL) { (void) fprintf(stderr, "Unknown host ¥"%s¥"¥n", hostname); (void) close(sock); return (-1); } /* すべての sockaddr_in6 フィールドが 0 であることを確認 */ bzero(&sin, sizeof (sin)); sin.sin6_family = hp->h_addrtype; sin.sin6_port = htons(port); /* 1 つが接続するまで戻されたすべてのアドレスを試行 */ h_addr_index = 0; while (hp->h_addr_list[h_addr_index] != NULL) { bcopy(hp->h_addr_list[h_addr_index], &sin.sin6_addr, hp->h_length); /* ホストに接続 */ if (connect(sock, (struct sockaddr *)&sin, sizeof (sin)) == -1) { if (hp->h_addr_list[++h_addr_index] != NULL) { /* 次のアドレスを試行 */ continue; } perror("connect"); freehostent(hp); (void) close(sock); return (-1); } break; } freehostent(hp); return (sock); } main(int argc, char *argv[]) { int sock; char buf[BUFSIZ]; int cc; switch (argc) { case 2: sock = myconnect2all(argv[1], IPPORT_ECHO); break; case 3: sock = myconnect2all(argv[1], atoi(argv[2])); break; default: (void) fprintf(stderr, "Usage: %s <hostname> <port>¥n", argv[0]); exit(1); } if (sock == -1) exit(1); if (write(sock, "hello world", strlen("hello world") + 1) == -1) { perror("write"); exit(1); } cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } (void) printf("Read <%s>¥n", buf); return (0); }
第 4 の例は、新しい getaddrinfo(3SOCKET) インタフェースを使用する myconnect のポートです。このルーチンは、1 つが接続するまですべてのアドレスを試行しようとします。これは myconnect3 です。このプログラムは、myconnect2all よりも単純であり、IPv4 から IPv6 へこのポートを実行する方法としてお奨めします。
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <string.h> #include <stdio.h> #include <unistd.h> int myconnect3(char *hostname, char *servicename) { struct addrinfo *res, *aip; struct addrinfo hints; int sock = -1; int error; /* ホストアドレスの取得。どのタイプのアドレスも可 */ bzero(&hints, sizeof (hints)); hints.ai_flags = AI_ALL|AI_ADDRCONFIG; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(hostname, servicename, &hints, &res); if (error != 0) { (void) fprintf(stderr, "getaddrinfo: %s for host %s service %s¥n", gai_strerror(error), hostname, servicename); return (-1); } /* 1 つが接続するまですべてのアドレスを試行 */ for (aip = res; aip != NULL; aip = aip->ai_next) { /* * ソケットを開く。このアドレスタイプは、 * getaddrinfo() の返す値に依存 */ sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); if (sock == -1) { perror("socket"); freeaddrinfo(res); return (-1); } /* ホストに接続 */ if (connect(sock, aip->ai_addr, aip->ai_addrlen) == -1) { perror("connect"); (void) close(sock); sock = -1; continue; } break; } freeaddrinfo(res); return (sock); } main(int argc, char *argv[]) { int sock; char buf[BUFSIZ]; int cc; switch (argc) { case 1: sock = myconnect3(NULL, NULL); break; case 2: sock = myconnect3(argv[1], "echo"); break; case 3: sock = myconnect3(argv[1], argv[2]); break; default: (void) fprintf(stderr, "Usage: %s <hostname> <port>¥n", argv[0]); exit(1); } if (sock == -1) exit(1); if (write(sock, "hello world", strlen("hello world") + 1) == -1) { perror("write"); exit(1); } cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } (void) printf("Read <%s>¥n", buf); return (0); }
第 5 の例は、オリジナルで無修正の IPv4 サーバー、myserver です。
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #define BACKLOG 1024 /* 保留状態の接続の最大数 */ void do_work(int sock); int myserver(int port) { struct sockaddr_in laddr, faddr; int sock, new_sock, sock_opt; socklen_t faddrlen; /* 接続待機するソケットの設定 */ laddr.sin_family = AF_INET; laddr.sin_port = htons(port); laddr.sin_addr.s_addr = INADDR_ANY; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); return (-1); } /* ローカルアドレスを再使用できるようにシステムに通知 */ sock_opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof (sock_opt)) == -1) { perror("setsockopt(SO_REUSEADDR)"); (void) close(sock); return (-1); } if (bind(sock, (struct sockaddr *)&laddr, sizeof (laddr)) == -1) { perror("bind"); (void) close(sock); return (-1); } if (listen(sock, BACKLOG) == -1) { perror("listen"); (void) close(sock); return (-1); } /* 接続要求のための待機 */ for (;;) { faddrlen = sizeof (faddr); new_sock = accept(sock, (struct sockaddr *)&faddr, &faddrlen); if (new_sock == -1) { if (errno != EINTR && errno != ECONNABORTED) { perror("accept"); } continue; } (void) printf("Connection from %s/%d¥n", inet_ntoa(faddr.sin_addr), ntohs(faddr.sin_port)); do_work(new_sock); /* 作業の実行 */ } /*NOTREACHED*/ } void do_work(int sock) { char buf[BUFSIZ]; int cc; while (1) { cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } if (cc == 0) { /* EOF */ (void) close(sock); (void) printf("Connection closed¥n"); return; } buf[cc + 1] = '¥0'; (void) printf("Read <%s>¥n", buf); if (write(sock, buf, cc) == -1) { perror("write"); exit(1); } } } main(int argc, char *argv[]) { if (argc != 2) { (void) fprintf(stderr, "Usage: %s <port>¥n", argv[0]); exit(1); } (void) myserver(htons(atoi(argv[1]))); return (0); }
第 6 の例は、IPv4 と IPv6 の両方のクライアントとの接続を処理する myserver の最小ポートです。この例では単一のソケットを使うので、IPv4 接続は IPv4 射影アドレスとして表示されます。
include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #define BACKLOG 1024 /* 保留状態の接続の最大数 */ void do_work(int sock); int myserver2(int port) { struct sockaddr_in6 laddr, faddr; int sock, new_sock, sock_opt; socklen_t faddrlen; char addrbuf[INET6_ADDRSTRLEN]; /* * 接続待機するソケットの設定 * すべての sockaddr_in6 フィールドが 0 であることを確認 */ bzero(&laddr, sizeof (laddr)); laddr.sin6_family = AF_INET6; laddr.sin6_port = htons(port); laddr.sin6_addr = in6addr_any; /* 構造体割り当て */ sock = socket(AF_INET6, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); return (-1); } /* ローカルアドレスを再使用できるようにシステムに通知 */ sock_opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof (sock_opt)) == -1) { perror("setsockopt(SO_REUSEADDR)"); (void) close(sock); return (-1); } if (bind(sock, (struct sockaddr *)&laddr, sizeof (laddr)) == -1) { perror("bind"); (void) close(sock); return (-1); } if (listen(sock, BACKLOG) == -1) { perror("listen"); (void) close(sock); return (-1); } /* 接続要求のための待機 */ for (;;) { faddrlen = sizeof (faddr); new_sock = accept(sock, (struct sockaddr *)&faddr, &faddrlen); if (new_sock == -1) { if (errno != EINTR && errno != ECONNABORTED) { perror("accept"); } continue; } if (IN6_IS_ADDR_V4MAPPED(&faddr.sin6_addr)) { struct in_addr ina; IN6_V4MAPPED_TO_INADDR(&faddr.sin6_addr, &ina); (void) printf("Connection from %s/%d¥n", inet_ntop(AF_INET, (void *)&ina, addrbuf, sizeof (addrbuf)), ntohs(faddr.sin6_port)); } else { (void) printf("Connection from %s/%d¥n", inet_ntop(AF_INET6, (void *)&faddr.sin6_addr, addrbuf, sizeof (addrbuf)), ntohs(faddr.sin6_port)); } do_work(new_sock); /* 作業の実行 */ } /*NOTREACHED*/ } void do_work(int sock) { char buf[BUFSIZ]; int cc; while (1) { cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } if (cc == 0) { /* EOF */ (void) close(sock); (void) printf("Connection closed¥n"); return; } buf[cc + 1] = '¥0'; (void) printf("Read <%s>¥n", buf); if (write(sock, buf, cc) == -1) { perror("write"); exit(1); } } } main(int argc, char *argv[]) { if (argc != 2) { (void) fprintf(stderr, "Usage: %s <port>¥n", argv[0]); exit(1); } (void) myserver2(htons(atoi(argv[1]))); return (0); }
第 7 の例は、getnameinfo(3SOCKET) を使って対等アドレスと対等名のログを単純化する、IPv6 への myserver のポートです。これは、IPv4 から IPv6 へこのポートを実行する方法としてお奨めします。
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #define BACKLOG 1024 /* 保留状態の接続の最大数 */ void do_work(int sock); int myserver3(char *servicename) { struct addrinfo *aip; struct addrinfo hints; struct sockaddr_storage faddr; int sock, new_sock, sock_opt; socklen_t faddrlen; int error; char hname[NI_MAXHOST]; char sname[NI_MAXSERV]; /* 接続待機するソケットの設定 */ bzero(&hints, sizeof (hints)); hints.ai_flags = AI_ALL|AI_ADDRCONFIG|AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(NULL, servicename, &hints, &aip); if (error != 0) { (void) fprintf(stderr, "getaddrinfo: %s for service %s¥n", gai_strerror(error), servicename); return (-1); } sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); if (sock == -1) { perror("socket"); return (-1); } /* ローカルアドレスを再使用できるようにシステムに通知 */ sock_opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof (sock_opt)) == -1) { perror("setsockopt(SO_REUSEADDR)"); (void) close(sock); return (-1); } if (bind(sock, aip->ai_addr, aip->ai_addrlen) == -1) { perror("bind"); (void) close(sock); return (-1); } if (listen(sock, BACKLOG) == -1) { perror("listen"); (void) close(sock); return (-1); } /* 接続要求のための待機 */ for (;;) { faddrlen = sizeof (faddr); new_sock = accept(sock, (struct sockaddr *)&faddr, &faddrlen); if (new_sock == -1) { if (errno != EINTR && errno != ECONNABORTED) { perror("accept"); } continue; } error = getnameinfo((struct sockaddr *)&faddr, faddrlen, hname, sizeof (hname), sname, sizeof (sname), NI_NUMERICHOST|NI_NUMERICSERV); if (error) { (void) fprintf(stderr, "getnameinfo: %s¥n", gai_strerror(error)); } else { (void) printf("Connection from (addr) %s/%s¥n", hname, sname); } error = getnameinfo((struct sockaddr *)&faddr, faddrlen, hname, sizeof (hname), sname, sizeof (sname), 0); if (error) { (void) fprintf(stderr, "getnameinfo: %s¥n", gai_strerror(error)); } else { (void) printf("Connection from (name) %s/%s¥n", hname, sname); } do_work(new_sock); /* 作業の実行 */ } /*NOTREACHED*/ } void do_work(int sock) { char buf[BUFSIZ]; int cc; while (1) { cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } if (cc == 0) { /* EOF */ (void) close(sock); (void) printf("Connection closed¥n"); return; } buf[cc + 1] = '¥0'; (void) printf("Read <%s>¥n", buf); if (write(sock, buf, cc) == -1) { perror("write"); exit(1); } } } main(int argc, char *argv[]) { if (argc != 2) { (void) fprintf(stderr, "Usage: %s <servicename>¥n", argv[0]); exit(1); } (void) myserver3(argv[1]); return (0); }
第 8 の例は、射影アドレスの代わりに IPv4 と IPv6 用の個別のソケットを使用する myserver のポートです。この設定は poll(2) を使う必要があるので、さらに複雑です。
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #define BACKLOG 1024 /* 保留状態の接続の最大数 */ #define NUMSOCKS 2 /* ポーリングするソケット数 */ void do_work(int sock); int myserver4(int port) { struct sockaddr_in laddr4, faddr4; struct sockaddr_in6 laddr6, faddr6; int sock4, sock6, new_sock, sock_opt; socklen_t faddrlen; char addrbuf[INET6_ADDRSTRLEN]; struct pollfd pfd[NUMSOCKS]; /* * 接続待機するソケットの設定。1 つは IPv4 に、 * もう 1 つは IPv6 に。 */ laddr4.sin_family = AF_INET; laddr4.sin_port = htons(port); laddr4.sin_addr.s_addr = INADDR_ANY; sock4 = socket(AF_INET, SOCK_STREAM, 0); if (sock4 == -1) { perror("socket"); return (-1); } /* すべての sockaddr_in6 フィールドが 0 であることを確認 */ bzero(&laddr6, sizeof (laddr6)); laddr6.sin6_family = AF_INET6; laddr6.sin6_port = htons(port); laddr6.sin6_addr = in6addr_any; /* 構造体割り当て */ sock6 = socket(AF_INET6, SOCK_STREAM, 0); if (sock6 == -1) { perror("socket"); (void) close(sock4); return (-1); } /* ローカルアドレスを再使用できるようにシステムに通知 */ sock_opt = 1; if (setsockopt(sock4, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof (sock_opt)) == -1 || setsockopt(sock6, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof (sock_opt))) { perror("setsockopt(SO_REUSEADDR)"); (void) close(sock4); (void) close(sock6); return (-1); } if (bind(sock4, (struct sockaddr *)&laddr4, sizeof (laddr4)) == -1 || bind(sock6, (struct sockaddr *)&laddr6, sizeof (laddr6)) == -1) { perror("bind"); (void) close(sock4); (void) close(sock6); return (-1); } if (listen(sock4, BACKLOG) == -1 || listen(sock6, BACKLOG) == -1) { perror("listen"); (void) close(sock4); (void) close(sock6); return (-1); } /* 接続要求のための待機 */ pfd[0].fd = sock4; pfd[1].fd = sock6; pfd[0].events = pfd[1].events = POLLIN; for (;;) { if (poll(pfd, NUMSOCKS, -1) == -1) { if (errno != EINTR) { perror("poll"); (void) close(sock4); (void) close(sock6); return (-1); } continue; } if (pfd[0].revents & POLLIN) { /* IPv4 */ faddrlen = sizeof (faddr4); new_sock = accept(sock4, (struct sockaddr *)&faddr4, &faddrlen); if (new_sock == -1) { if (errno != EINTR && errno != ECONNABORTED) { perror("accept"); } continue; } (void) printf("Connection from %s/%d¥n", inet_ntop(AF_INET, (void *)&faddr4.sin_addr, addrbuf, sizeof (addrbuf)), ntohs(faddr4.sin_port)); do_work(new_sock); /* 作業の実行 */ } if (pfd[1].revents & POLLIN) { /* IPv6 */ faddrlen = sizeof (faddr6); new_sock = accept(sock6, (struct sockaddr *)&faddr6, &faddrlen); if (new_sock == -1) { if (errno != EINTR && errno != ECONNABORTED) { perror("accept"); } continue; } (void) printf("Connection from %s/%d¥n", inet_ntop(AF_INET6, (void *)&faddr6.sin6_addr, addrbuf, sizeof (addrbuf)), ntohs(faddr6.sin6_port)); do_work(new_sock); /* 作業の実行 */ } } /*NOTREACHED*/ } void do_work(int sock) { char buf[BUFSIZ]; int cc; while (1) { cc = read(sock, buf, sizeof (buf)); if (cc == -1) { perror("read"); exit(1); } if (cc == 0) { /* EOF */ (void) close(sock); (void) printf("Connection closed¥n"); return; } buf[cc + 1] = '¥0'; (void) printf("Read <%s>¥n", buf); if (write(sock, buf, cc) == -1) { perror("write"); exit(1); } } } main(int argc, char *argv[]) { if (argc != 2) { (void) fprintf(stderr, "Usage: %s <port>¥n", argv[0]); exit(1); } (void) myserver4(htons(atoi(argv[1]))); return (0); }
この make ファイルは、この付録の例の一部またはすべてをコンパイルおよびリンクします。また、lint も行います。
SRC = myconnect.c myconnect2.c myconnect3.c myconnect2all.c ¥ myserver.c myserver2.c myserver3.c myserver4.c EXES = $(SRC:%.c=%) LINTOUT = $(SRC:%.c=%.lint) #TOOLSDIR = /ws/on28-tools/SUNWspro/SC5.0/bin #CC = $(TOOLSDIR)/cc #LINT.c = $(TOOLSDIR)/lint CFLAGS = -g LDLIBS = -lsocket -lnsl LFLAGS = $(CFLAGS) LINTOPTS = $(LFLAGS) $(LIBS) all: $(EXES) lint: $(LINTOUT) myconnect.o: myconnect.c $(CC) -c -o $@ $(CFLAGS) myconnect.c myconnect2.o: myconnect2.c $(CC) -c -o $@ $(CFLAGS) myconnect2.c myconnect2all.o: myconnect2all.c $(CC) -c -o $@ $(CFLAGS) myconnect2all.c myconnect3.o: myconnect3.c $(CC) -c -o $@ $(CFLAGS) myconnect3.c myserver.o: myserver.c $(CC) -c -o $@ $(CFLAGS) myserver.c myserver2.o: myserver2.c $(CC) -c -o $@ $(CFLAGS) myserver2.c myserver3.o: myserver3.c $(CC) -c -o $@ $(CFLAGS) myserver3.c myconnect.lint: $(LINT.c) $(LINTOPTS) myconnect.c myconnect2.lint: $(LINT.c) $(LINTOPTS) myconnect2.c myconnect2all.lint: $(LINT.c) $(LINTOPTS) myconnect2all.c myconnect3.lint: $(LINT.c) $(LINTOPTS) myconnect3.c myserver.lint: $(LINT.c) $(LINTOPTS) myserver.c myserver2.lint: $(LINT.c) $(LINTOPTS) myserver2.c myserver3.lint: $(LINT.c) $(LINTOPTS) myserver3.c myserver4.lint: $(LINT.c) $(LINTOPTS) myserver4.c clean: rm -f *.o core