socket

sockets 可以说是 standard Unix file descriptors。
read() write() 也可以,但是send() and recv() 会有更好的控制。A file descriptor is simply an integer associated with an open file. But (and here’s the catch), that file can be a network connection, a FIFO, a pipe, a terminal, a real on-the-disk file, or just about anything else. Everything in Unix is a file!

两种 Internet Sockets

Stream sockets 就是用的 TCP (the transimission control protocol)。IP 代表的是 Internet Protocol。
Datagram sockets 用 UDP (User Datagram protocol)。 tftp (trivial file transfer protocol, a little brother to FTP), dhcpcd (a DHCP client), multiplayer games, streaming audio, video conferencing, etc.

IP Addresses

1
2
3
4
struct sockaddr_in sa; // IPv4
struct sockaddr_in6 sa6; // IPv6
inet_pton(AF_INET, "10.12.110.57", &(sa.sin_addr)); // IPv4
inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr)); // IPv6

该函数将IP地址转换为 sockaddr_in 结构。
“pton” stands for “presentation to network”—you can call it “printable to network” if that’s easier to remember.
以前这个功能的函数叫 inet_addr 或者是 inet_aton, 但是用不了 ipv6 了。

inet_ntop() “ntop” means “network to presentation”

1
2
3
4
char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
struct sockaddr_in sa; // pretend this is loaded with something
inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);
printf("The IPv4 address is: %s\n", ip4);

getaddrinfo() 可以得到IP地址。

socket()

1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

参数可以硬编码,(domain is PF_INET or PF_INET6, type is SOCK_STREAM or SOCK_DGRAM, and protocol can be set to 0 to choose the proper protocol for the given type
也可以用getprotobyname() 传”TCP” “UDP”

1
2
3
4
5
6
7
8
9
10
int s;
struct addrinfo hints, *res;
// do the lookup
// [pretend we already filled out the "hints" struct]
getaddrinfo("www.example.com", "http", &hints, &res);
// [again, you should do error-checking on getaddrinfo(), and walk
// the "res" linked list looking for valid entries instead of just
// assuming the first one is good (like many of these examples do.)
// See the section on client/server for real examples.]
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

socket 调用成功则返回 a socket descriptor, 失败返回-1。
全局变量 errno 将会被设置。

一旦你有了 socket,你应该将它跟本地机器的端口绑定。
服务端就需要 listen(), 客户端需要 connect()。

1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

sockfd 就是 a socket descriptor, my_addr 需要指向 struct sockaddr,addrlen 是地址的字节数的长度

1
2
3
4
5
6
7
8
9
10
11
12
struct addrinfo hints, *res;
int sockfd;
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
getaddrinfo(NULL, "3490", &hints, &res);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// bind it to the port we passed in to getaddrinfo():
bind(sockfd, res->ai_addr, res->ai_addrlen);

如果已经被绑定了

1
2
3
4
5
// lose the pesky "Address already in use" error message
if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) {
perror("setsockopt");
exit(1);
}

其实客户端连接不用关心 bind(), 你调用 connect() 它会检查 socket 是否 unbound, 需要的话 它会自行绑定到本地未使用的端口。

Connect

1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd is our friendly neighborhood socket file descriptor, as returned by the socket() call, serv_addr is a struct sockaddr containing the destination port and IP address, and addrlen is the length in bytes of the server address structure.

建立一个socket 连接:

1
2
3
4
5
6
7
8
9
10
11
12
struct addrinfo hints, *res;
int sockfd;
// first, load up address structs with getaddrinfo():
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
getaddrinfo("www.example.com", "3490", &hints, &res);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// connect!
connect(sockfd, res->ai_addr, res->ai_addrlen);

旧的写法会填充 sockaddr_ins.
确保 connect 返回的值不等于-1, 不然要去检查一下 errno. 别担心端口, 无需 bind, 内核会做这些事.

listen()

1
int listen(int sockfd, int backlog);

sockfd 就是 socket file descriptor. backlog 是所能允许的连接数. 进入的连接在等待, 大多数系统默认限制为20, 你可以设置它.
跟以往一样, 当listen() 返回 -1 时, 需要检查 errno.
显然, 在调用 listen() 时, 先需要 bind().

所以, 调用的顺序基本上是

1
2
3
4
5
getaddrinfo();
socket();
bind();
listen();
/* accept() goes here */

accept()

连接都在等待被 Accept(). 你调用 accept(), 然后等待准备的连接, 它将放回全新的一个 socket file descriptor. 原来的 socket 仍然在等待新的连接, 全新的 socket 可以使用 send() 和 recv().

1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

错误处理一如既往. addr 指向本地的 struct sockaddr_storage. addrlen 应该是 sizeof(struct sockaddr_storage).

至此, 总结一下上面的吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MYPORT "3490" // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold

int main(void)
{
struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;

// !! don't forget your error checking for these calls !!

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // fill in my IP for me

getaddrinfo(NULL, MYPORT, &hints, &res);

// make a socket, bind it, and listen on it:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);

// now accept an incoming connection:

addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

// ready to communicate on socket descriptor new_fd!
.
.

new_fd 是新的, 如果你不想要再接受新的连接, 直接调用 close() 也行.

send() and recv()

TCP 用这两个.

1
int send(int sockfd, const void *msg, int len, int flags);

样例如:

1
2
3
4
5
6
7
8
9
char *msg = "Beej was here!";
int len, bytes_sent;
.
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.

send() 会返回实际上发送出去的数据大小. 如果一次性发送过大的数据量, 剩余的你要处理并继续发送. 如果整个数据包小于1KB, 可讷讷个不用担心这个问题.
错误处理-1.

recv() 有很多相似的地方.

1
int recv(int sockfd, void *buf, int len, int flags);

recv() 返回真实收到的字节数大小. 错误处理-1, 如果返回0, 说明对面关闭连接了.

sendto() 和 recvfrom() – datagram style

datagram socket 不需要连接远程主机。

1
2
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, socklen_t tolen);

跟 send() 相比, 这个函数就多两个参数。 sockaddr 包含了远程主机的地址和端口, tolen 其实就是 sockaddr 的长度.

sendto() 返回参数的意义和 send() 一样.

你可以用 getaddrinfo(), recvfrom(), 或者 你手工填充 *to 的数据.

1
2
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen);

多两个额外字段, 可见 sendto(). 返回结果如 recv 一致.

struct sockaddr_storage 可以让我们不要局限于 struct sockaddr_in 中. 通用结构 sockaddr_storage 足够容纳这两者. ( 为什么不能直接用 struct sockaddr 还要用 struct sockaddr_storage 去转成 struct sockaddr? 看起来很冗余. 就是因为 stcuct sockaddr 不能容纳, 所以他们做了一个新的.)

close() and shutdown()

1
close(sockfd);

它会阻止所有的读写在这个 socket file descriptor. 强行读写, 将发生错误.

1
int shutdown(int sockfd, int how);

这个函数拥有更细颗粒的控制度.

  • 0 Further receives are disallowed
  • 1 Further sends are disallowed
  • 2 Further sends and receives are disallowed (like close())

成功 返回 0 失败 返回 -1.

shutdown() 不能真正关闭 socket file desciptor, 只是改变了可用度. 为了释放它, 你需要使用 close(). (如果是 windows 平台, 那就使用 closesocket(). )

getpeername()

The function getpeername() will tell you who is at the other end of a connected stream socket

1
2
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

sockfd 已经连接的sfd, addr 指向 sockaddr 的指针, addrlen -> sizeof *addr.
inet_ntop(), getnameinfo(), or gethostbyaddr() 可以得到更多信息.
The function returns -1 on error and sets errno accordingly.

gethostname()

It returns the name of the computer that your program is running on

1
2
#include <unistd.h>
int gethostname(char *hostname, size_t size);

hostname is a pointer to an array of chars that will contain the hostname upon the function’s return, and size is the length in bytes of the hostname array. The function returns 0 on successful completion, and -1 on error, setting errno as usual.

Client-Server Background

there will only be one server on a machine, and that server will handle multiple clients using
fork(). The basic routine is: server will wait for a connection, accept() it, and fork() a child process to
handle it. This is what our sample server does in the next section.

多进程处理连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/*
** server.c -- a stream socket server demo
*/
#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" // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold

void sigchld_handler(int s) {
// waitpid() might overwrite errno, so we save and restore it:
int saved_errno = errno;
while (waitpid(-1, NULL, WNOHANG) > 0);
errno = saved_errno;
}
// get sockaddr, IPv4 or 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; // listen on sock_fd, new connection on new_fd
struct addrinfo hints, * servinfo, * p;
struct sockaddr_storage their_addr; // connector's address information
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; // use my IP
if ((rv = getaddrinfo(NULL, PORT, & hints, & servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and bind to the first we can
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;
}
freeaddrinfo(servinfo); // all done with this structure
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
exit(1);
}
if (listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
sa.sa_handler = sigchld_handler; // reap all dead 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) { // main accept() loop
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()) { // this is the child process
close(sockfd); // child doesn't need the listener
if (send(new_fd, "Hello, world!", 13, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); // parent doesn't need this
}
return 0;
}

sigaction() 主要负责杀死没有连接的 fork() 的僵尸进程.

A Simple Stream Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*
** client.c -- a stream socket client demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT "3490" // the port client will be connecting to
#define MAXDATASIZE 100 // max number of bytes we can get at once
// get sockaddr, IPv4 or 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(int argc, char * argv[]) {
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct addrinfo hints, * servinfo, * p;
int rv;
char s[INET6_ADDRSTRLEN];
if (argc != 2) {
fprintf(stderr, "usage: client hostname\n");
exit(1);
}
memset( & hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((rv = getaddrinfo(argv[1], PORT, & hints, & servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and connect to the first we can
for (p = servinfo; p != NULL; p = p -> ai_next) {
if ((sockfd = socket(p -> ai_family, p -> ai_socktype,
p -> ai_protocol)) == -1) {
perror("client: socket");
continue;
}
if (connect(sockfd, p -> ai_addr, p -> ai_addrlen) == -1) {
close(sockfd);
perror("client: connect");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "client: failed to connect\n");
return 2;
}
inet_ntop(p -> ai_family, get_in_addr((struct sockaddr * ) p -> ai_addr),
s, sizeof s);
printf("client: connecting to %s\n", s);
freeaddrinfo(servinfo); // all done with this structure
if ((numbytes = recv(sockfd, buf, MAXDATASIZE - 1, 0)) == -1) {
perror("recv");
exit(1);
}
buf[numbytes] = '\0';
printf("client: received '%s'\n", buf);
close(sockfd);
return 0;
}

如果服务端没开启, 将返回 Connection refused.

Datagram Sockets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
** listener.c -- a datagram sockets "server" demo
*/

#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 <arpa/inet.h>
#include <netdb.h>

#define MYPORT "4950" // the port users will be connecting to

#define MAXBUFLEN 100

// get sockaddr, IPv4 or 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;
struct addrinfo hints, *servinfo, *p;
int rv;
int numbytes;
struct sockaddr_storage their_addr;
char buf[MAXBUFLEN];
socklen_t addr_len;
char s[INET6_ADDRSTRLEN];

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE; // use my IP

if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}

// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("listener: socket");
continue;
}

if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sockfd);
perror("listener: bind");
continue;
}

break;
}

if (p == NULL) {
fprintf(stderr, "listener: failed to bind socket\n");
return 2;
}

freeaddrinfo(servinfo);

printf("listener: waiting to recvfrom...\n");

addr_len = sizeof their_addr;
if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0,
(struct sockaddr *)&their_addr, &addr_len)) == -1) {
perror("recvfrom");
exit(1);
}

printf("listener: got packet from %s\n",
inet_ntop(their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
s, sizeof s));
printf("listener: packet is %d bytes long\n", numbytes);
buf[numbytes] = '\0';
printf("listener: packet contains \"%s\"\n", buf);

close(sockfd);

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*
** talker.c -- a datagram "client" demo
*/

#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 <arpa/inet.h>
#include <netdb.h>

#define SERVERPORT "4950" // the port users will be connecting to

int main(int argc, char *argv[])
{
int sockfd;
struct addrinfo hints, *servinfo, *p;
int rv;
int numbytes;

if (argc != 3) {
fprintf(stderr,"usage: talker hostname message\n");
exit(1);
}

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;

if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}

// loop through all the results and make a socket
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("talker: socket");
continue;
}

break;
}

if (p == NULL) {
fprintf(stderr, "talker: failed to create socket\n");
return 2;
}

if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0,
p->ai_addr, p->ai_addrlen)) == -1) {
perror("talker: sendto");
exit(1);
}

freeaddrinfo(servinfo);

printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);
close(sockfd);

return 0;
}

Slightly Advanced Techniques

Blocking

recvfrom() 在等待数据的时候处于阻塞状态,许多函数调用都会如此,accept()会阻塞,recv()也会阻塞。当新创建一个 socket 时,内核默认阻塞。

1
2
3
4
5
#include <unistd.h>
#include <fcntl.h>

sockfd = socket(PF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);

错误的话,返回-1,error 将被设置为 EAGAIN or EWOULDBLOCK。

select() 同步 I/O 复用

当非阻塞的情况下,一边 accept() 一边 recv(),将导致CPU时间混乱。 select() 可以同时掌控几个 socket,它虽然很便捷,但是速度最慢。考虑 libevent 来作为替代品吧,对系统调用 socket 进行了封装。

1
2
3
4
5
6
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

该函数掌控 file descriptor 的集合:readfds, writefds 和 exceptfds. 如果你想要读标准输入和某一 socket , 就把 file descriptors 0 和 sockfd 加入到 readfds 的集合中. 参数 numfds 设置为 file descriptor 的最大值 + 1. 当 select() 返回的时候, readfds 对应了即将操作的 socket. 可以用 FD_ISSET() 宏进行测试.

一些宏函数

FD_SET(int fd, fd_set set); Add fd to the set.
FD_CLR(int fd, fd_set
set); Remove fd from the set.
FD_ISSET(int fd, fd_set set); Return true if fd is in the set.
FD_ZERO(fd_set
set); Clear all entries from the set.

struct timeval 相当于超时设置.

1
2
3
4
struct timeval {
int tv_sec; // seconds
int tv_usec; // microseconds
};

如果将struct timeval中的字段设置为0,则select()立即超时,有效地轮询集合中的所有文件描述符。 如果将参数timeout设置为
NULL,它将等到第一个文件描述符准备就绪. 最后,如果你不在乎关于等待某个集合,你可以在调用select()时将其设置为NULL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
** select.c -- a select() demo
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define STDIN 0 // file descriptor for standard input
int main(void)
{
struct timeval tv;
fd_set readfds;
tv.tv_sec = 2;
tv.tv_usec = 500000;
FD_ZERO(&readfds);
FD_SET(STDIN, &readfds);
// don't care about writefds and exceptfds:
select(STDIN+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET(STDIN, &readfds))
printf("A key was pressed!\n");
else
printf("Timed out.\n");
return 0;
}