我的套接字似乎有问题 . 下面,您将看到一些分叉服务器和客户端的代码 . 服务器打开TCP套接字,客户端连接到它,然后关闭它 . 睡眠用于协调时间 . 在客户端关闭()之后,服务器尝试将write()写入其自己的TCP连接端 . 根据write(2)手册页,这应该给我一个SIGPIPE和一个EPIPE错误 . 但是,我没有_179645_的观点,写入本地,关闭套接字成功,并且没有EPIPE我看不到服务器应该如何检测客户端已关闭套接字 .
在关闭其结束的客户端和尝试写入的服务器之间的间隙中,对netstat的调用将显示连接处于CLOSE_WAIT / FIN_WAIT2状态,因此服务器端应该绝对能够拒绝写入 .
作为参考,我在Debian Squeeze上,uname -r是2.6.39-bpo.2-amd64 .
这里发生了什么?
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <netdb.h>
#define SERVER_ADDRESS "127.0.0.7"
#define SERVER_PORT 4777
#define myfail_if( test, msg ) do { if((test)){ fprintf(stderr, msg "\n"); exit(1); } } while (0)
#define myfail_unless( test, msg ) myfail_if( !(test), msg )
int connect_client( char *addr, int actual_port )
{
int client_fd;
struct addrinfo hint;
struct addrinfo *ailist, *aip;
memset( &hint, '\0', sizeof( struct addrinfo ) );
hint.ai_socktype = SOCK_STREAM;
myfail_if( getaddrinfo( addr, NULL, &hint, &ailist ) != 0, "getaddrinfo failed." );
int connected = 0;
for( aip = ailist; aip; aip = aip->ai_next ) {
((struct sockaddr_in *)aip->ai_addr)->sin_port = htons( actual_port );
client_fd = socket( aip->ai_family, aip->ai_socktype, aip->ai_protocol );
if( client_fd == -1) { continue; }
if( connect( client_fd, aip->ai_addr, aip->ai_addrlen) == 0 ) {
connected = 1;
break;
}
close( client_fd );
}
freeaddrinfo( ailist );
myfail_unless( connected, "Didn't connect." );
return client_fd;
}
void client(){
sleep(1);
int client_fd = connect_client( SERVER_ADDRESS, SERVER_PORT );
printf("Client closing its fd... ");
myfail_unless( 0 == close( client_fd ), "close failed" );
fprintf(stdout, "Client exiting.\n");
exit(0);
}
int init_server( struct sockaddr * saddr, socklen_t saddr_len )
{
int sock_fd;
sock_fd = socket( saddr->sa_family, SOCK_STREAM, 0 );
if ( sock_fd < 0 ){
return sock_fd;
}
myfail_unless( bind( sock_fd, saddr, saddr_len ) == 0, "Failed to bind." );
return sock_fd;
}
int start_server( const char * addr, int port )
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sock_fd;
memset( &hint, '\0', sizeof( struct addrinfo ) );
hint.ai_socktype = SOCK_STREAM;
myfail_if( getaddrinfo( addr, NULL, &hint, &ailist ) != 0, "getaddrinfo failed." );
for( aip = ailist; aip; aip = aip->ai_next ){
((struct sockaddr_in *)aip->ai_addr)->sin_port = htons( port );
sock_fd = init_server( aip->ai_addr, aip->ai_addrlen );
if ( sock_fd > 0 ){
break;
}
}
freeaddrinfo( aip );
myfail_unless( listen( sock_fd, 2 ) == 0, "Failed to listen" );
return sock_fd;
}
int server_accept( int server_fd )
{
printf("Accepting\n");
int client_fd = accept( server_fd, NULL, NULL );
myfail_unless( client_fd > 0, "Failed to accept" );
return client_fd;
}
void server() {
int server_fd = start_server(SERVER_ADDRESS, SERVER_PORT);
int client_fd = server_accept( server_fd );
printf("Server sleeping\n");
sleep(60);
printf( "Errno before: %s\n", strerror( errno ) );
printf( "Write result: %d\n", write( client_fd, "123", 3 ) );
printf( "Errno after: %s\n", strerror( errno ) );
close( client_fd );
}
int main(void){
pid_t clientpid;
pid_t serverpid;
clientpid = fork();
if ( clientpid == 0 ) {
client();
} else {
serverpid = fork();
if ( serverpid == 0 ) {
server();
}
else {
int clientstatus;
int serverstatus;
waitpid( clientpid, &clientstatus, 0 );
waitpid( serverpid, &serverstatus, 0 );
printf( "Client status is %d, server status is %d\n",
clientstatus, serverstatus );
}
}
return 0;
}
5 回答
这是Linux手册页中关于
write
和EPIPE
的内容:当Linux使用
pipe
或socketpair
时,它可以并将检查该对的读取端,因为这两个程序将演示:Linux能够这样做,因为内核对管道或连接对的另一端有先天的了解 . 但是,使用
connect
时,协议栈会维护套接字的状态 . 您的测试演示了这种行为,但下面是一个程序,它在一个线程中完成所有操作,类似于上面的两个测试:如果运行上面的程序,您将获得类似于此的输出:
这表明套接字转换到
CLOSED
状态需要一个write
. 要找出发生这种情况的原因,事务的TCP转储可能很有用:前三行代表三次握手 . 第四行是客户端发送到服务器的
FIN
数据包,第五行是来自服务器的ACK
,确认收到 . 第六行是服务器尝试将1字节数据发送到设置了PUSH
标志的客户端 . 最后一行是客户端RESET
数据包,它导致连接的TCP状态被释放,这就是为什么第三个netstat
命令在上面的测试中没有产生任何输出的原因 .因此,服务器不知道客户端将重置连接,直到它尝试向其发送一些数据 . 重置的原因是因为客户端调用
close
而不是其他东西 .服务器无法确定客户端实际发出了什么系统调用,它只能遵循TCP状态 . 例如,我们可以通过调用
shutdown
替换close
调用 .shutdown
和close
之间的区别在于shutdown
仅控制连接的状态,而close
也控制表示套接字的文件描述符的状态 .shutdown
不会close
一个套接字 .输出将与
shutdown
更改不同:TCP转储也将显示不同的内容:
注意最后的复位是在最后一个
ACK
数据包后5秒 . 此重置是由于程序关闭而未正确关闭套接字 . 它是在重置之前从客户端到服务器的ACK
数据包与之前不同的数据包 . 这表明客户端未使用close
. 在TCP中,FIN
指示实际上表明没有更多数据要发送 . 但由于TCP连接是双向的,因此接收FIN
的服务器假定客户端仍然可以接收数据 . 在上面的例子中,客户端实际上确实接受了数据 .无论客户端使用
close
还是SHUT_WR
发出FIN
,在任何一种情况下,您都可以通过在服务器套接字上轮询可读事件来检测FIN
的到达 . 如果在调用read
后结果为0
,那么您知道FIN
已经到达,您可以使用该信息执行所需操作 .现在,如果服务器在尝试执行写操作之前发出
SHUT_WR
,那么它实际上会得到EPIPE
错误 .相反,如果您希望客户端指示立即重置服务器,则可以通过启用逗留来强制在大多数TCP堆栈上执行此操作选项,在调用
close
之前,延迟超时为0
.通过上述更改,程序的输出变为:
在这种情况下,
send
会立即收到错误,但它不是EPIPE
,而是ECONNRESET
. TCP转储也反映了这一点:RESET
数据包在3次握手完成后立即出现 . 但是,使用此选项有其危险性 . 如果RESET
到达时另一端在套接字缓冲区中有未读数据,则该数据将被清除,从而导致数据丢失 . 强制发送RESET
通常用于请求/响应样式协议 . 请求的发送方可以知道在收到对其请求的整个响应时不会丢失数据 . 然后,请求发送方可以安全地强制在连接上发送RESET
.在客户端
close()
编辑套接字后调用write()
一(第一)次(在您的示例中编码)后,您将在任何连续调用write()时获得预期的EPIPE
和SIGPIPE
.只是尝试添加另一个write()来激发错误:
输出将是:
由于第二次调用
write()
引发了SIGPIPE
,因此进程终止,最后两个printf()
的输出丢失 . 为了避免终止进程,您可能希望使进程忽略SIGPIPE
.你有两个套接字 - 一个用于客户端,另一个用于服务器 . 现在您的客户端正在进行主动关闭 . 这意味着TCP的连接终止已由客户端启动(已从客户端发送发送了tcp FIN段) .
在此阶段,您将看到FIN_WAIT1状态的客户端套接字 . 现在服务器套接字的状态是什么?它处于CLOSE_WAIT状态 . 因此服务器套接字未关闭 .
来自服务器的FIN尚未发送 . (为什么 - 因为应用程序没有关闭套接字) . 在此阶段,您正在编写服务器套接字,因此您不会收到错误 .
现在,如果你想看到错误,只需在写入套接字之前写入close(client_fd) .
这里服务器套接字不再处于CLOSE_WAIT状态,因此您可以看到write的返回值是-ve以指示错误 . 我希望这澄清一下 .
我怀疑正在发生的事情是服务器端套接字仍然有效,因此即使TCP会话处于关闭状态,您的写入调用也会有效地尝试写入文件描述符 . 如果我完全错了,请告诉我 .
我猜你正在运行TCP堆栈检测到发送失败并尝试重新传输 . 对
write()
的后续调用是否会无声地失败?换句话说,尝试五次写入已关闭的套接字,看看你是否最终获得了一个SIGPIPE . 当你说写'succeeds'时,你得到3的返回结果吗?