int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if (argc != 2)
{
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
// 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。
// 注意,客户端不是不允许调用 bind(),只是没有必要调用 bind() 固定一个端口号,
// 服务器也不是必须调用 bind(),但如果服务器不调用 bind(),内核会自动给服务器分配监听端口,
// 每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
write(sockfd, str, strlen(str));
n = read(sockfd, buf, MAXLINE);
printf("Response from server:\n");
write(STDOUT_FILENO, buf, n);
printf("\n");
close(sockfd);
return 0;
}
把上面的代码保存到文件 client.c 文件中,并执行下面的命令编译:
$ gcc client.c -o client
然后运行编译出来的 client 程序:
$ ./client hello
此时服务器端会收到请求并返回转换为大写的字符串,并输出相应的信息:
而客户端在发送请求后会收到转换过的字符串:
在客户端的代码中有两点需要注意:
1. 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。
2. 客户端需要调用 connect() 连接服务器,connect 和 bind 的参数形式一致,区别在于 bind 的参数是自己的地址,而 connect 的参数是对方的地址。
至此我们已经使用 socket 技术完成了一个最简单的客户端服务器程序,虽然离实际应用还非常遥远,但就学习而言已经足够了。
虽然我们的服务器程序可以响应客户端的请求,但是这样的效率太低了。一般情况下服务器程序需要能够同时处理多个客户端的请求。可以通过 fork 系统调用创建子进程来处理每个请求,下面是大体的实现思路:
listenfd = socket(...);
bind(listenfd, ...);
listen(listenfd, ...);
while (1)
{
connfd = accept(listenfd, ...);
n = fork();
if (n == -1)
{
perror("call to fork");
exit(1);
}
else if (n == 0)
{
// 在子进程中处理客户端的请求。
close(listenfd);
while (1)
{
read(connfd, ...);
...
write(connfd, ...);
}
close(connfd);
exit(0);
}
else
{
close(connfd);
}
}