如果在写入套接字时,读取端已经关闭,那么此时可以得到一个 SIGPIPE 信号,该信号默认情况下会终止当前进程。
例如,当服务器关闭时,而客户端还试图向套接字写入数据的时候会产生一个 SIGPIPE 信号,此时会造成客户端的非正常的退出,防止这种情况可以给客户端安装一个 signal() 函数用来处理 SIGPIPE 信号。
服务器端代码:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#define PORT 8099
#define BACKLOG 5
void process_conn_server(int s);
int main(int argc, char *argv[])
{
int sock_serv_fd, sock_client_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int err;
pid_t pid;
sock_serv_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_serv_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
err = bind(sock_serv_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(err < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
err = listen(sock_serv_fd, BACKLOG);
if (err < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
for(;;) {
int addrlen = sizeof(struct sockaddr);
sock_client_fd = accept(sock_serv_fd, (struct sockaddr *)&client_addr, &addrlen);
if (sock_client_fd < 0)
continue;
pid = fork();
if (pid == 0) {
close(sock_serv_fd);
process_conn_server(sock_client_fd);
} else {
close(sock_client_fd);
}
}
}
void process_conn_server(int s)
{
ssize_t size = 0;
char buffer[1024];
for(;;) {
size = read(s, buffer, 1024);
if (size == 0)
return;
sprintf(buffer, "Server: %ld bytes readed\n", size);
write(s, buffer, strlen(buffer)+1);
}
}
客户端代码:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#define PORT 8099
void process_conn_client(int s)
{
ssize_t size = 0;
char buffer[1024];
for(;;) {
size = read(0, buffer, 1024);
if (size > 0) {
write(s, buffer, size);
size = read(s, buffer, 1024);
write(1, buffer, size);
}
}
}
int main(int argc, char **argv)
{
int sfd;
struct sockaddr_in server_addr;
int err;
if (argc != 2) {
printf ("Usage: %s <ip_address>\n", argv[0]);
exit(EXIT_FAILURE);
}
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
connect(sfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
process_conn_client(sfd);
close(sfd);
}
先运行服务器端,然后运行客户端,然后在客户端里随意输入字符:# ./client 192.168.1.104
sdfa
Server: 5 bytes readed
dsfas
Server: 6 bytes readed
sdfasdf
Server: 8 bytes readed 由上可见,服务器端和客户端的通讯是正常的。那么现在结束掉服务端程序,如果再在客户端上连续输入两次,客户端就会退出。为了验证客户端是接收到 SIGPIPE 信号而退出的,因此在客户端的代码里添加下面一个函数:
[C++] 纯文本查看 复制代码 void sig_pipe(int sign)
{
printf("Catch a SIGPIPE signal\n");
}
然后在主函数里添加 signal(SIGPIPE, sig_pipe); 函数,接着做上面的实验,那么会在客户端一边看到输出:# ./client 192.168.1.104
aaa
Server: 4 bytes readed
ddd
Server: 4 bytes readed
iiii
Server: 5 bytes readed
hhjhjh
Server: 7 bytes readed
5222
Server: 5 bytes readed
aaa
bbb
Catch a SIGPIPE signal
2222
Catch a SIGPIPE signal 现在来考察一下,为什么上面的输出中需要连续两次才会导致客户端退出(第一次输入“aaa",第二次输入”bbb“)。可以用 wireshark 这样的嗅探器进行相关数据的捕获:
第 1 条记录是客户端向服务器端发送 5222 这几个字符:
第 2 条记录服务器向客户端发送数据(PSH 标志表示”推送数据“):
第 3 条记录是客户端向服务器简单的发送一个应答。
第 4 条记录是我们按下 Ctrl+ C 结束服务器端时,服务器向客户端发送一个 FIN 标志和一个 ACK 标志。
第 5 条记录是客户端向服务器端的结束通知产生一个应答。
第 6 条记录记录中,客户端又向服务器推送数据了(这时是发送 "aaa"):
第 7 条记录”连接复位“。
一端的 TCP 可以拒绝一个连接请求,可以异常终止一条连接,或可以终止一条空闲的连接 --- 这些都可以用 RST (复位) 标志来完成。
假定在某一端的 TCP 请求要和并不存在的端口进行连接。在另一端的 TCP 就可以把TCP 数据包的控制字段中的 RST 标志位置 1 ,然后将报文发送出去,表示取消该请求。因此,当客户端发送 aaa 时,客户端还能收到服务器发送过来的信息。但是当再次发送时("bbb")时,系统自身就会给它来个 SIGPIPE 信号了,这个可以由程序中所安装的捕捉 SIGPIPE 函数可以证明。 |