/* * this function reports the error and * exits back to the shell: */ static void bail(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what,stderr); fputs("\n",stderr); exit(1); }
int main(int argc,char **argv) { int z; char *srvr_addr = NULL; struct sockaddr_in adr_inet; /* AF_INET */ struct sockaddr_in adr_clnt; /* AF_INET */ int len_inet; /* length */ int s; /* socket */ char dgram[512]; /* recv buffer */ char dtfmt[512]; /* date/time result */ time_t td; /* current time and date */ struct tm tm; /* date time values */
/* * use a server address from the command * line,if one has been provided. * otherwise,this program will default * to using the arbitrary address * 127.0.0.23: */ if(argc >=2) { /* addr on cmdline */ srvr_addr = argv[1]; } else { /* use default address */ srvr_addr = "127.0.0.23"; }
/* * create a UDP socket to use: */ s = socket(AF_INET,SOCK_DGRAM,0); if(s==-1) bail("socket()");
/* * create a socket address,for use * with bind: */ memset(&adr_inet,0,sizeof adr_inet); adr_inet.sin_family = AF_INET; adr_inet.sin_port = htons(9090); adr_inet.sin_addr.s_addr = inet_addr(srvr_addr);
/* * bind a address to our socket,so that * client program can contact this * server: */ z = bind(s,(struct sockaddr *)&adr_inet,len_inet); if(z==-1) bail("bind()");
/* * now wait for requests: */ for(;;) { /* * block until the program receives a * datagram at our address an port: */ len_inet = sizeof adr_clnt; z = recvfrom(s, /* socket */ dgram, /* receiveing buffer */ sizeof dgram, /* max recv buf size */ 0, /* flags */ (struct sockaddr *)&adr_clnt, /* addr */ &len_inet); /* addr len,in & out */ if(z<0) bail("recvfrom()");
/* * process the request: */ if(!strcasecmp(dgram,"QUIT")) break; /* quit server */
/* * get the current date and time: */ time(&td); /* get current time & date */ tm = *localtime(&td); /* get componets */
/* * formate a new date and time string, * based upon the input formate string: */ strftime(dtfmt, /* formatted result */ sizeof dtfmt, /* max result size */ dgram, /* input date/time format */ &tm); /* input date/time values */ /* * send the formatted result back to the * client program: */ z = sendto(s, /* socket to send result */ dtfmt, /* the datagram result to send */ strlen(dtfmt), /* the datagram length */ 0, /* flags */ (struct sockaddr *)&adr_clnt, /* addr */ len_inet); /* client address length */
if(z<0) bail("sendto()");
}
/* * close the socket and exit: */ close(s); return 0; }
/* * This function reports the error and * exits back to the shell: */ static void bail(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what,stderr); fputs("\n",stderr); exit(1); }
int main(int argc,char **argv) { int z; int x; char *srvr_addr = NULL; struct sockaddr_in adr_srvr; /* AF_INET */ struct sockaddr_in adr; /* AF_INET */ int len_inet; /* length */ int s; /* socket */ char dgram[512]; /* recv buffer */
/* * use a server address from the command * line,if one has been provided. * otherwise,this programe will default * to using the arbitrary address * 127.0.0.23: */ if(argc>=2) { /* addr on command line: */ srvr_addr = argv[1]; } else { srvr_addr = "127.0.0.23"; }
/* * create a socket address,to use * to contact the server with: */ memset(&adr_srvr,0,sizeof adr_srvr); adr_srvr.sin_family = AF_INET; adr_srvr.sin_port = htons(9090); adr_srvr.sin_addr.s_addr = inet_addr(srvr_addr);
/* * create a UDP socket to use: */ s = socket(AF_INET,SOCK_DGRAM,0); if(s==-1) bail("socket()");
for(;;) { /* * prompt user for a date formate string: */ fputs("\nEnter format string: ",stdout); if(!fgets(dgram,sizeof dgram,stdin)) break; /* EOF */
/* * send format string to server: */ z = sendto(s, /* socket to send result */ dgram, /* the datagram result to snd */ strlen(dgram), /* the datagram length */ 0, /* flags */ (struct sockaddr *)&adr_srvr, /* addr */ len_inet); /* server address length */ if(z<0) bail("sendto()");
/* * test if we asked for a server shutdown: */ if(!strncmp(dgram,"QUIT",strlen(dgram)-1)) break; /* yes,we quit too */
/* * wait for a response: */ x = sizeof adr; z = recvfrom(s, /* socket */ dgram, /* receiving buffer */ sizeof dgram, /* max recv buf size */ 0, /* flags */ (struct sockaddr *)&adr, /* addr */ &x); /* addr len,in & out */ if(z<0) bail("recvfrom()"); dgram[z]=0;
/* * report result */ printf("Result from %s port %u:\n\t'%s'\n", inet_ntoa(adr.sin_addr), (unsigned)ntohs(adr.sin_port), dgram); }
/* * close the socket and exit: */ close(s); putchar('\n'); return 0; }
字符&将服务器程序放置在后台运行,这样我们就可以继续使用当前的终端来运行客户端程序。 服务器程序启动运行以后,我们就可以使用客户程序来进行测试了。下面显示如何启动客户程序并进行测试: $ ./dgramclnt Enter format string: %D Result from 127.0.0.23 port 9090 : 08/13/99' Enter format string: %A %D %H:%M:%S Result from 127.0.0.23 port 9090 : Friday 08/13/99 22:14:02' Enter format string: quit [1]+ Done ./dgramsrvr $
测试无服务器的情况
下面的输出显示没有服务器运行时运行客户程序的输出结果: $ ./dgramclnt Enter format string: %D Connection refused: recvfrom(2) $ 在这里我们可以看到客户程序可以运行,并且可以创建套接口,要求输入。甚至sendto函数也报告执行成功。这就进一步确认了数据报的发送只是确保发送成功,而不确保接收成功。 在这种情况下,程序很幸运的得到了错误号来表明错误原因 。错误标识是通过recvfrom函数调用得到的。当客户与服务器程序独立运行在一个大的网络并有多个路由的情况下,也许就不会得到这个错误号。
在前一节我们谈到可以在命令行指定IP地址。如果我们设置了我们自己的网络,我们可以试着在不同的主机上运行客户端与服务器程序。在下一个例子中,服务器程序运行在192.168.0.1的主机上,而客户端程序运行192.168.0.2的主机上。服务器程序的启动如下所示: $ ./dgramsrvr 192.168.0.1 & [1] 4416 $ 服务器成功启动以后,就可以在另一个主机上调用客户端程序。客户端的输出结果如下所示: $ ./dgramclnt 192.168.0.1 Enter format string: %D Result from 192.168.0.1 port 9090 : '08/13/99' Enter format string: %A (%D) Result from 192.168.0.1 port 9090 : 'Friday (08/13/99)' Enter format string: QUIT $
这是需要理解的一个重要概念,而且也许对于初学者来说也是最难理解的事情。如果我们现在并不能完全的理解这些内容,那么我们就需要回顾一下第五章的内容。作为练习,我们可以在我们的例子程序中加入下面的printf语句: printf("Client from %s port %u;\n", inet_ntoa(adr_clnt.sin_addr), (unsigned)ntohs(adr_clnt.sin_port));
编译以后重新运行程序,程序的运行结果如下: $ ./dgramsrvr & [1] 733 $ ./dgramclnt Enter format string: %D Client from 127.0.0.23 port 1027; Result from 127.0.0.23 port 9090 : '08/15/99' Enter format string: %A %D Client from 127.0.0.23 port 1027; Result from 127.0.0.23 port 9090 : 'Sunday 08/15/99' Enter format string: QUIT Client from 127.0.0.23 port 1027; [1]+ Done ./dgramsrvr $ 在这里我们可以注意到所有的数据报发送到服务器,而数据报的from地址报告如下: Client from 127.0.0.23 port 1027; 这再一次验证了当在客户端发送套接口没有使用bind函数时,会依据需求赋值合适的IP地址和最终的端口号。