学习套接字接口的最好方法是研究示例代码。下面展示了 echo 客户端的代码。在和服务器建立连接之后,客户端进入一个循环,反复从标准输入读取文本行,发送文本行给服务器,从服务器读取回送的行,并输出结果到标准输出。当 fgets 在标准输入上遇到 EOF 时,或者因为用户在键盘上键入 ctrl-d,或者因为在一个重定向的输入文件中用尽了所有的文本行时,循环就终止。
/* echoclient.c */
#include "csapp.h"
int main(int argc, char **argv)
{
int clientfd, port;
char *host, buf[MAXLINE];
rio_t rio;
if (argc != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(0);
}
host = argv[1];
port = atoi(argv[2]);
clientfd = Open_clientfd(host, port);
Rio_readinitb(&rio, clientfd);
while (Fgets(buf, MAXLINE, stdin) != NULL) {
Rio_writen(clientfd, buf, strlen(buf));
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd);
exit(0);
}
下面展示了 echo 服务器的主程序。在打开监听描述符后,它进入一个无限循环。每次循环都等待一个来自客户端的连接请求,输出已连接客户端的域名和IP地址,并调用 echo 函数为这些客户端服务。在 echo 程序返回后,主程序关闭已连接描述符。一旦客户端和服务器关闭了它们各自的描述符,连接也就终止了。
注意,简单的 echo 服务器一次只能处理一个客户端。这种类型的服务器一次一个地在客户端间迭代,称为迭代服务器(iterative server)。
/* echoserveri.c */
#include "csapp.c"
void echo(int connfd);
int main(int argc, char **argv)
{
int listenfd, connfd, port, clientlen;
struct sockaddr_in clientaddr;
struct hostent *hp;
char *haddrp;
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
port = atoi(argv[1]);
listenfd = Open_listenfd(port);
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
hp = Gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr,
sizeof(clientaddr.sin_addr.s_addr), AF_INET);
haddrp = inet_ntoa(clientaddr.sin_addr);
printf("server connected to %S (%s)\n", hp->h_name, haddrp);
echo(connfd);
Close(connfd);
}
exit(0);
}
void echo(int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, connfd);
while ((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
printf("server received %d bytes\n", n);
Rio_writen(connfd, buf, n);
}
}
运行及结果验证
首先开启服务程序:
$ ./echoserver 1024
然后开启客户端程序:
$ ./echoclient localhost 1024
客户端输入 hello 后服务端的输出:
server connected to server received 6 bytes
新开一个客户端进程,输入无响应,被阻塞。