linux网络编程之socket(十):shutdown 与 close 函数的区别  http://blog.csdn.net/yijiu0711/article/details/17349169

socket中关闭套接字的函数close和shutdown区别

#include <unistd.h> 
int close(int fd);

close 关闭了自身数据传输的两个方向。关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这个链接,能读或写这个socket id

#include <sys/socket.h> 
int shutdown(int sockfd, int how);

  shutdown 可以选择关闭某个方向或者同时关闭两个方向,shutdown how = 1 or how = 2 (SHUT_WR or SHUT_RDWR),可以保证对等方接收到一个EOF字符(即发送了一个FIN段),而不管其他进程是否已经打开了这个套接字。但close不能保证,只有当某个sockfd的引用计数为0,close 才会发送FIN段,否则只是将引用计数减1而已。也就是说只有当所有进程(可能fork多个子进程都打开了这个套接字)都关闭了这个套接字,close 才会发送FIN 段。而shutdown破坏了socket 链接,读的时候可能侦探到EOF结束符,写的时候可能会收到一个SIGPIPE信号,这个信号可能直到socket buffer被填充了才收到。

  所以说,如果是调用shutdown how = 1 ,则意味着往一个已经接收FIN的套接字中写是允许的,接收到FIN段仅代表对方不再发送数据,但对方还是可以读取数据的,可以让对方可以继续读取缓冲区剩余的数据。

假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0,但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。

  有时候代码中需要连续多次调用write,可能还来不及调用read得知对方已关闭了连接就被SIGPIPE信号终止掉了,这就需要在初始化时调用sigaction处理SIGPIPE信号,对于这个信号的处理我们通常忽略即可,signal(SIGPIPE, SIG_IGN); 如果SIGPIPE信号没有导致进程异常退出,write返回-1并且errno为EPIPE。

 socket 多进程中的shutdown, close使用

  使用close中止一个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。

使用shutdown可直接关闭描述符,不考虑描述符的参考数,可选择中止一个方向的连接。

注意:

   1>. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套

接字将被释放。
     2>. 在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信.

   如果一个进程close(sfd)将不会影响到其它进程. 得自己理解引用计数的用法了. 有Kernel编程知识的更好理解了.

  下面是使用shutdown改进的回射服务器程序

服务端

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<sys/wait.h> //*进程用的头文件*/
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/select.h> //
#include<sys/time.h>
#define MAXLINE 1024 //通信内容的最大长度 #ifndef FD_SETSIZE
#define FD_SETSIZE 25
#endif ssize_t readn(int fd, void *buf, size_t count)
{
ssize_t nleft=count;
ssize_t nread;
char *charbuf=(char*) buf; while(nleft>)
{
nread=read(fd,charbuf,nleft);
if(nread<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nread==)
return count-nleft; charbuf +=nread;
nleft=count-nread;
}
return count;
} ssize_t writen(int fd, const void *buf, size_t count)
{
ssize_t nleft=count;
ssize_t nwrite;
char *charbuf=(char*) buf; while(nleft>)
{
nwrite=write(fd,charbuf,nleft);
if(nwrite<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nwrite==)
return count-nleft;
charbuf +=nwrite;
nleft=count-nwrite; }
return count;
} ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
int ret;
while()
{
ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-&& errno==EINTR)
continue;
return ret;
}
} ssize_t readline(int sockfd, void *buf, size_t len)
{
ssize_t nleft=len,nread;
int ret;
char* bufchar=buf;
while()
{
ret=recv_peek(sockfd,bufchar,len);
if(ret<||ret==)
return ret;
nread=ret;
int i;
for(i=;i<nread;i++)
{
if(bufchar[i]=='\n')
{
ret=readn(sockfd,bufchar,i+);
if(ret!=i+)
exit(EXIT_FAILURE);
return ret;
}
}
if(nread>nleft)
exit(EXIT_FAILURE);
nleft-=nread;
ret=readn(sockfd,bufchar,nread);
if(ret!=nread)
exit(EXIT_FAILURE);
bufchar+=nread; }
return -; } int main()
{
int sock_fd,new_fd,fd;//sock_fd用于监听,new_fd用于连接
int maxi,maxfd,client[FD_SETSIZE],nready;
struct sockaddr_in srv_addr;//服务器的地址信息
struct sockaddr_in client_addr;//客户机的地址信息
int i,size; //地址结构数据的长度
ssize_t n;
fd_set rset,allset;
char sendbuf[],recvbuf[];
memset(sendbuf,,sizeof(sendbuf));
memset(recvbuf,,sizeof(recvbuf)); /*创建套接字*/
sock_fd=socket(AF_INET,SOCK_STREAM,);//采用IPv4协议
if(sock_fd==-)
{
perror("creat socket failed");
exit();
} /*服务器地址参数*/
srv_addr.sin_family=AF_INET;
srv_addr.sin_port=htons();
srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
bzero(&srv_addr.sin_zero,sizeof(struct sockaddr_in));//bzero位清零函数,将sin_zero清零,sin_zero为填充字段,必须全部为零 int on=; //表示开启reuseaddr
if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<) //打开地址、端口重用
perror("setsockopt"); /*绑定地址和端口*/
if(bind(sock_fd,(struct sockaddr*)&srv_addr,sizeof(struct sockaddr))==-)
{
perror("bind failed");
exit();
} /*设置监听模式,等待客户机的监听*/
if((listen(sock_fd,))==-)
{
perror("listen failed");
exit();
} maxfd=sock_fd;
maxi=-;
for(i=;i<FD_SETSIZE;i++)
client[i]=-;
FD_ZERO(&allset);
FD_SET(sock_fd,&allset); /*接受连接,采用非阻塞是的模式调用accep*/
while()
{
rset=allset;
nready=select(maxfd+,&rset,NULL,NULL,NULL);
if(FD_ISSET(sock_fd,&rset))
{
size=sizeof(struct sockaddr_in);
new_fd=accept(sock_fd,(struct sockaddr*)&client_addr,&size);
if(new_fd==-)
{
perror("accept failed");
//continue;//restart accept when EINTR
}
printf("server:got connection from IP= %s prot= %d \n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//连接成功,打印客户机IP地址和端口号
/*char *inet_nota(struct sockaddr_in in);
头文件:
arpa/inet.h
Winsock2.h
参数:
一个网络上的IP地址
返回值:
如果正确,返回一个字符指针,指向一块存储着点分格式IP地址的静态缓冲区(同一线程内共享此内存);错误,返回NULL。
uint31_t ntohs(uint32_t net32bitvalue);
头文件:
#include<netinet/in.h>
把net32bitvalue有网络字节序转换为主机字节序。
*/
if(send(new_fd,"Hello client,I am 192.168.229.125!\n",,)==-) //192.168.229.125为子进程IP,可更改
perror("send failed");
for(i=;i<FD_SETSIZE;i++)
if(client[i]<)
{
client[i]=new_fd;
break;
}
if(i==FD_SETSIZE)
perror("too many client");
FD_SET(new_fd,&allset);
if(new_fd>maxfd)
maxfd=new_fd;
if(i>maxi)
maxi=i;
if(--nready<=)
continue;
} for(i=;i<=maxi;i++)
{
if((fd=client[i])<)
continue;
if(FD_ISSET(fd,&rset))
{
memset(recvbuf,,sizeof(recvbuf));
if((n=read(fd,recvbuf,MAXLINE))==)
{
close(fd);
FD_CLR(fd,&rset);
client[i]=-;
}
else
{
fputs(recvbuf,stdout);
writen(fd,recvbuf,n);
}
if(--nready<=)
break;
}
} }
}

  客户端程序

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<sys/wait.h> //*进程用的头文件*/
#include<netinet/in.h>
#include<arpa/inet.h> #define MAXBYTEMUN 1024 ssize_t readn(int fd, void *buf, size_t count)
{
ssize_t nleft=count;
ssize_t nread;
char *charbuf=(char*) buf; while(nleft>)
{
nread=read(fd,charbuf,nleft);
if(nread<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nread==)
return count-nleft; charbuf +=nread;
nleft=count-nread;
} return count;
} ssize_t writen(int fd, const void *buf, size_t count)
{
ssize_t nleft=count;
ssize_t nwrite;
char *charbuf=(char*) buf; while(nleft>)
{
nwrite=write(fd,charbuf,nleft);
if(nwrite<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nwrite==)
return count-nleft; charbuf +=nwrite;
nleft=count-nwrite;
}
return count;
} ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
int ret;
while()
{
ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-&& errno==EINTR)
continue;
return ret;
}
} ssize_t readline(int sockfd, void *buf, size_t len)
{
ssize_t nleft=len,nread;
int ret;
char* bufchar=buf;
while()
{
ret=recv_peek(sockfd,bufchar,len);
if(ret<||ret==)
return ret;
nread=ret;
int i;
for(i=;i<nread;i++)
{
if(bufchar[i]=='\n')
{
ret=readn(sockfd,bufchar,i+);
if(ret!=i+)
exit(EXIT_FAILURE);
return ret;
}
}
if(nread>nleft)
exit(EXIT_FAILURE);
nleft-=nread;
ret=readn(sockfd,bufchar,nread);
if(ret!=nread)
exit(EXIT_FAILURE);
bufchar+=nread;
}
return -; } int main(int argc,char *argv[])
{
int sock_fd,numbytes,maxfd,fd_stdin,nready;
// char buf[MAXBYTEMUN];
struct hostent;
struct sockaddr_in client_addr;//客户机的地址信息
ssize_t ret;
char recvbuf[]={''},sendbuf[]={''};
fd_set rset;
int stdineof; if(argc!=)
{
fprintf(stderr,"usage: client IPAddress\n"); //执行客户端程序时,输入客户端程序名称和其IP地址
exit();
} /*创建套接字*/
sock_fd=socket(AF_INET,SOCK_STREAM,);//采用IPv4协议
if(sock_fd==-)
{
perror("creat socket failed");
exit();
} /*服务器地址参数*/
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons();
client_addr.sin_addr.s_addr=inet_addr(argv[]);
bzero(&client_addr.sin_zero,sizeof(struct sockaddr_in));//bzero位清零函数,将sin_zero清零,sin_zero为填充字段,必须全部为零 /*连接到服务器*/
if(connect(sock_fd,(struct sockaddr*)&client_addr,sizeof(struct sockaddr))==-)
{
perror("connect failed");
exit();
}
if((numbytes=recv(sock_fd,recvbuf,MAXBYTEMUN,))==-)
{
perror("receive failed");
exit();
}
recvbuf[numbytes]='\0';//在字符串末尾加上\0,否则字符串无法输出
printf("Received: %s\n",recvbuf); fd_stdin=fileno(stdin);
if(sock_fd>fd_stdin)
maxfd=sock_fd;
else
maxfd=fd_stdin;
stdineof=; while()
{
FD_SET(fd_stdin,&rset);
FD_SET(sock_fd,&rset);
nready=select(maxfd+,&rset,NULL,NULL,NULL);
if(nready==-)
perror("nready\n");
else if(nready==)
continue; if(FD_ISSET(sock_fd,&rset))
{
memset(recvbuf,,sizeof(recvbuf));
ret=readline(sock_fd,recvbuf,);
if(ret<)
perror("read from server error");
else if(ret==)
{
if(stdineof==) //如果是因为输入已经完毕造成的读到的数据个数为0,则正常终止
break;
else //否则是服务器端关闭
{
printf("server closed\n");
break;
}
}
writen(fileno(stdout),recvbuf,ret);
} if(FD_ISSET(fd_stdin,&rset))
{
if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)
{
stdineof=; //表示输入已经完毕
shutdown(sock_fd,SHUT_WR); //关闭sock_fd的写端
FD_CLR(fileno(stdin),&rset);
continue;
}
else
{
writen(sock_fd,sendbuf,strlen(sendbuf));
memset(sendbuf,,sizeof(sendbuf)); //清空,以免和下一次混淆
}
}
}
close(sock_fd);
return ;
}

运行程序,快速输入两次数据之后,按Ctrl+D,即fgets 会返回NULL,然后调用shutdown关闭写端,虽然服务器端延时才发送数据,此时客户端写端已经关闭,但还是可以读取到回射回来的数据,服务器端最后得到一个FIN段,read 返回0,打印输出 client close ,并且close(conn); 而客户端在读取服务端回射回来的两次数据后,再次read 也返回0,故打印 server connect close,break退出循环,进程顺利退出。

如果将客户端的197-200行程序改为 close(sock_fd); 即在输入终止后,关闭套接字使服务器在接收到数据之后,等待4s在回射。那么当延时后服务器端发送数据给客户端时,客户端的读端和写端都已经关闭,第一次发的数据会返回一个RST段,根据本文前面所说,再次发数据将直接产生SIGPIPE信号,默认会终止进程,但因为我们已经设置了忽略SIGPIPE信号,所以服务器端进程不会被终止,但客户端也会出错,因为回到while循环开头,select阻塞等待时发现套接字的读端已经关闭,所以不能再关心可读事件了,select会返回-1,错误码是 EBADF: Bad File Descriptor。

close与shutdown函数的更多相关文章

  1. close和shutdown函数的区别

    close函数首先是将传入的socket句柄引用数减1(因为fork进程时会导致socket句柄被多个进程引用),待到引用数等于0的时候,close才会真正关闭连接. shutdown函数是立刻关闭连 ...

  2. UNIX网络编程读书笔记:shutdown函数

    终止网络连接的通常方法是调用close函数.不过close有两个限制,却可以使用shutdown来避免. close 把描述字的引用计数减1,仅在该计数变为0时才关闭套接口.使用shutdown可以不 ...

  3. close()和shutdown()函数

    一·close(int sockfd) 当server和client建立连接,server调用close(),则server发送fin给client,server不在通过该套接字继续传送消息或者接收消 ...

  4. linux select函数 shutdown函数

    #include<sys/select.h> #include<sys/time.h> int select(int maxfdp1,fd_set *readset,fd_se ...

  5. shutdown函数

    #include <sys/socket.h> int shutdown(int sockfd, int howto); 返回:若成功则为0,若出错则为- (1)该函数的行为依赖于howt ...

  6. UNIX网络编程——shutdown 与 close 函数 的区别

    假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据 ...

  7. socket shutdown 与 close 函数 的区别

    假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据 ...

  8. linux网络编程中的shutdown()与close()函数

    1.close()函数 int close(int sockfd); //返回成功为0,出错为-1 close 一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字不能再由cl ...

  9. linux网络编程之shutdown() 与 close()函数详解

    linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...

随机推荐

  1. 固定IP 正常访问谷歌

    如题 地址栏直接输入 http://173.194.1.150/ 正常使用 ~标记一下~

  2. 缓存 HttpContext.Current.Cache和HttpRuntime.Cache的区别

    先看MSDN上的解释: HttpContext.Current.Cache:为当前 HTTP 请求获取Cache对象. HttpRuntime.Cache:获取当前应用程序的Cache.  我们再用. ...

  3. 今天是JQ 的slideUp 和 slideDown 的点击事件

    先贴代码,再讲详细事件 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> & ...

  4. typecho除了首页其他大部分网页404怎么办?

    server { listen ; server_name blog.localhost; #绑定域名 index index.htm index.html index.php; #默认文件 root ...

  5. \n\t\r代表什么意思

    \t水平制表(跳到下一个tab位置)\r回车(将当前位置移到本行开头)\n换行(将当前位置移到下一行行开头)

  6. C语言:输入输出

    C语言无I/O语句,i/o操作由函数实现 #include<stdio.h> 字符输出函数putchar: 格式:putchar(c) 参数:c为字符常量,变量或者表达式 功能:把字符c输 ...

  7. CentOS的SSH,Putty配置说明

    基本资源: CentOS5.5 (32位) , Mysql6.0 ,Putty ,SSH   Step: 1.VMWare中装好CentOS    A. 可能存在ifconfig等命令无法正常识别) ...

  8. loadrunner 学习笔记--AJAX(转)

    用loadrunner测试WEB程序的时候总是会碰到AJAX或者ActiveX实现的功能,而通常这些功能会包含很多客户端函数(一般为JavaScript).我们该如何处理?如果从功能实现的角度去考虑这 ...

  9. springmvc之定时器

    一.通过注解方式实现定时器 1.工程结构 2.所需jar包 3.spring-config.xml,springmvc配置文件 <?xml version="1.0" enc ...

  10. 查看SQL Server日志 Part 1

    曾经有朋友问我数据被删除了,不借助第三方工具能不能查是什么时候发生的. SQL Server提供了一个undocumented的函数fn_dblog可以让我们查看活动的transaction log. ...