这个 server 所做的事情就是透过 stream connection(串流连接)送出"Hello, World!\n"字符串。你所需要做就是用一个窗口来测试执行 server,并用另一个窗口来 telnet 到 server:
$ telnet remotehostname 3490
这里的 remotehostname 就是你运行 server 的主机名。
Server 的代码如下 [20]:
/*
** server.c - 展示一个stream socket server
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define PORT "3490" // 提供给用戶连接的 port
#define BACKLOG 10 // 有多少个特定的连接队列(pending connections queue)
void sigchld_handler(int s)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
// 取得 sockaddr,IPv4 或 IPv6:
void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
int sockfd, new_fd; // 在 sock_fd 进行 listen,new_fd 是新的连接
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr; // 连接者的地址资料
socklen_t sin_size;
struct sigaction sa;
int yes=1;
char s[INET6_ADDRSTRLEN];
int rv;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // 使用我的 IP
if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// 以循环找出全部的结果,并绑定(bind)到第一个能用的结果
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("server: socket");
continue;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sockfd);
perror("server: bind");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
return 2;
}
freeaddrinfo(servinfo); // 全部都用这个 structure
if (listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
sa.sa_handler = sigchld_handler; // 收拾全部死掉的 processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
printf("server: waiting for connections...\n");
while(1) { // 主要的 accept() 循环
sin_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
if (new_fd == -1) {
perror("accept");
continue;
}
inet_ntop(their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
s, sizeof s);
printf("server: got connection from %s\n", s);
if (!fork()) { // 这个是 child process
close(sockfd); // child 不需要 listener
if (send(new_fd, "Hello, world!", 13, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); // parent 不需要这个
}
return 0;
}
趁着你对这个例子还感到很好奇,我为了让句子比较清楚(我个人觉得),所以将代码放在一个大的 main() 函数中,如果你觉得将它分成几个小一点的函数会比较好的话,可以尽管去做。
[还有,sigaction() 这个东西对你而言应该是蛮陌生的。没有关系,这个代码是用来清理 zombie processes(僵尸进程),当 parent process 所 fork() 出来的 child process 结束时,且 parent process 没有取得 child process 的离开状态时,就会出现 zombie process。如果你产生了许多 zombies,但却无法清除他们时,你的系统管理员就会开始焦虑不安了]。
你可以利用下一节所列出的 client,来取得 server 的资料。
[20] http://beej.us/guide/bgnet/examples/server.c