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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
| /**
* 非阻塞式I/O的select版本
*/
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#define SERV_PORT 9877
#define MAXLINE 4096 /* max text line length */
#define max(a,b) ( ((a)>(b)) ? (a):(b) )
/**
* 优点:速度是最快的,可以防止进程在做任何工作时发生阻塞
* 缺点:同时管理4个不同的I/O流,每个流都是非阻塞的,需要考虑到4个流的部分读和部分写问题。编码量是最多的,需要引入缓冲区管理机制。
*/
void str_cli(FILE *fp, int sockfd)
{
// 将socket、标准输入和标准输出描述符设置为非阻塞方式
int val = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
val = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
val = fcntl(STDOUT_FILENO, F_GETFL, 0);
fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);
char to[MAXLINE], fr[MAXLINE];
char *toiptr, *tooptr, *friptr, *froptr;
toiptr = tooptr = to;
friptr = froptr = fr;
int stdineof = 0;
int maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
fd_set rset, wset;
for (; ;)
{
FD_ZERO(&rset);
FD_ZERO(&wset);
if (stdineof == 0 && toiptr < &to[MAXLINE])
{
FD_SET(STDIN_FILENO, &rset);
}
if (friptr < &fr[MAXLINE])
{
FD_SET(sockfd, &rset);
}
if (tooptr != toiptr)
{
FD_SET(sockfd, &wset);
}
if (froptr != friptr)
{
FD_SET(STDOUT_FILENO, &wset);
}
select(maxfdp1, &rset, &wset, NULL, NULL); // select函数仍然是阻塞的
// 标准输入
if (FD_ISSET(STDIN_FILENO, &rset))
{
int n;
if ((n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0)
{
// 对于非阻塞式IO,如果操作不能满足,相应系统调用会返回EWOULDBLOCK错误
if (errno != EWOULDBLOCK)
{
printf("read error on stdin\n");
exit(1);
}
}
else if (n == 0)
{
fprintf(stderr, "EOF on stdin\n");
stdineof = 1;
if (tooptr == toiptr)
{
shutdown(sockfd, SHUT_WR); // 缓冲区中没有数据要发送,关闭socket
}
}
else
{
fprintf(stderr, "read %d bytes from stdin\n", n);
toiptr += n;
FD_SET(sockfd, &wset);
}
}
// 从套接字读
if (FD_ISSET(sockfd, &rset))
{
int n;
if ((n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0)
{
if (errno != EWOULDBLOCK)
{
printf("read error on socket\n");
exit(1);
}
}
else if (n == 0)
{
fprintf(stderr, "EOF on socket\n");
if (stdineof)
{
return ;
}
else
{
printf("server terminated prematurely\n");
exit(1);
}
}
else
{
fprintf(stderr, "read %d bytes from socket\n", n);
friptr += n;
FD_SET(STDOUT_FILENO, &wset);
}
}
// 标准输出
int n;
if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0))
{
int nwritten;
if ((nwritten = write(STDOUT_FILENO, froptr, n)) < 0)
{
if (errno != EWOULDBLOCK)
{
printf("write error to stdout\n");
exit(1);
}
}
else
{
fprintf(stderr, "wrote %d bytes to stdout\n", nwritten);
froptr += nwritten;
if (froptr == friptr)
{
froptr = friptr = fr;
}
}
}
// 向socket写
if (FD_ISSET(sockfd, &wset) && ((n = toiptr - tooptr)) > 0)
{
int nwritten;
if ((nwritten = write(sockfd, tooptr, n)) < 0)
{
if (errno != EWOULDBLOCK)
{
printf("write error to socket\n");
exit(1);
}
}
else
{
fprintf(stderr, "wrote %d bytes to socket\n", nwritten);
tooptr += nwritten;
if (tooptr == toiptr)
{
toiptr = tooptr = to;
if (stdineof)
{
shutdown(sockfd, SHUT_WR);
}
}
}
}
}
return ;
}
/**
* connect的非阻塞版本
* 连接建立成功时,描述符变为可写;连接建立错误时,描述符变为即可读又可写
* 优点:
* 1、阻塞式的connect调用会消耗CPU时间,非阻塞式connect可以充分利用CPU时间,在等待的过程中可以处理其他工作
* 2、可以同时建立多个连接,浏览器中会用到此技术
* 3、阻塞式connect的函数超时过长,可以通过该函数设置超时时间
* 4、阻塞式的套接字调用connect时,在TCP的三次握手完成之前被某些信号中断时并且connect未设置内核自动重启的标志时,connect将返回EINTR错误
* 当再次调用connect等待未完成的连接时将会返回EADDRINUSE错误
*/
int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec)
{
// 将套接字设置为非阻塞状态
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
int error = 0;
int n;
if ((n = connect(sockfd, saptr, salen)) < 0)
{
// 连接未成功建立,正常情况下返回EINPROGRESS错误,表示操作正在处理
if (errno != EINPROGRESS)
{
// EINPROGRESS表示连接建立已经启动,但是尚未完成
return -1;
}
}
else if (n == 0)
{
// 当服务器和客户端在一台主机上时会立即建立连接
goto done;
}
// 当代码执行到如下过程中时,connect正在建立连接,可以在此位置执行业务相关代码
// 当然真正使用时,在此位置加入其他代码并不合适,需要根据具体情况重新调整代码
// 可以参照书中的web客户程序例子
fd_set rset, wset;
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
struct timeval tval;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ((n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0)
{
// 发生超时
close(sockfd);
errno = ETIMEDOUT;
return -1;
}
// 当连接建立成功时sockfd变为可写,当连接建立失败时sockfd变为即可读又可写
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset))
{
int len = sizeof(error);
// 非可移植性函数,连接建立成功返回0,连接建立失败将错误值返回给error
// 连接建立失败时,有返回-1和返回0的情况
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
{
// solaris连接建立失败返回-1
return -1;
}
}
else
{
printf("select error:sockfd not set");
exit(1);
}
done:
// 恢复套接字的文件状态标志
fcntl(sockfd, F_SETFL, flags);
if (error)
{
close(sockfd);
errno = error;
return -1;
}
return 0;
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
{
printf("usage: tcpcli <IPaddress>\n");
exit(1);
}
// 当一个进程向某个收到RST的套接字执行写操作时,内核会向该进程发送一个SIGPIPE信号
// 最好的方式是忽略此信号的处理方式,并在程序下面处理该异常情况
signal(SIGPIPE, SIG_IGN);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
//connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (connect_nonb(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr), 50) < 0)
{
printf("socket connect error\n");
exit(1);
}
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
|