11.close和shutdown函数

一. close 与 shutdown

       #include <unistd.h>

       int close(int fd);

                   假设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(Broken pipe)。(非阻塞地write)

      #include <sys/socket.h>

       int shutdown(int sockfd, int how);

                       shutdown 可以选择关闭某个方向或者同时关闭两个方向,shutdown how = 0 or how = 1 or how = 2 (SHUT_RD or SHUT_WR or SHUT_RDWR),后两者可以保证对等方接收到一个EOF字符(即发送了一个FIN段),而不管其他进程是否已经打开了这个套接字。而close不能保证,只有当某个sockfd的引用计数为0,close 才会发送FIN段,否则只是将引用计数减1而已。也就是说只有当所有进程(可能fork多个子进程都打开了这个套接字)都关闭了这个套接字,close 才会发送FIN 段。

     close 终止数据传送的两个方向.

     shutdown 可以有选择的终止某个方向数据传送或者终止数据传送的两个方向.

     shutdown   how=1就可以保证对等方接收到一个EOF字符,而不管其他进程是否已经打开了套接字.而close不能保证,直到套接字引用计数减到0才发送,即所有进程都关闭套接字了.

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


修改的客户端代码:


            if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
            {
            	stdineof =1;// 表示已经写入完毕
            	shutdown(sock,SHUT_WR);
            	
            }
  			else
  			{
  				writen(sock,sendbuf,strlen(sendbuf));
  				memset(sendbuf,0,sizeof(sendbuf));
  			}


完整C/S代码:

/// echoser.


/* 
        select所能承受的最大并发数受.一个进程所能打开的最大文件描述符数,可以通过ulimit -n来调整  但一个系统所能打开的最大数也是有限的,跟内存有关,可以通过cat /proc/sys/fs/file-max 查看
                                                                        
 */

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#include<sys/wait.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while (0)


ssize_t readn(int fd,void *buf,size_t count)
{
	size_t nleft = count ; // 未读取的数据
	ssize_t nread;// 已读取的数据
	char *bufp= (char*)buf;
	while(nleft > 0)
	{
		if( (nread = read(fd,bufp,nleft)) < 0)
		{
			if( errno == EINTR)
				 nread = 0;//  继续读取数据
			else
				return -1;
		}
		else if( nread == 0) // 对方关闭或已经读到eof
			break;
		bufp +=nread;
		nleft -= nread;
	
	}
	return count-nleft;
}

ssize_t writen(int fd,const void *buf,size_t count)
{
	size_t nleft=count;  // 未读取的
	ssize_t nwritten;    // 已读取的
 	char *bufp = (char*)buf;
 	
 	while(nleft > 0)
 	{
 		if((nwritten = write(fd,bufp,nleft)) < 0)
 		{
 			if( errno == EINTR)
 				continue;
 			else
 				return -1;
 		}
 		else if( nwritten == 0)
 			continue;
 		bufp  += nwritten;
 		nleft -= nwritten;
 	}
 	return count;
}


/// recv()只能读取套接字,而不能读取一般文件描述符
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
	while(1)
	{
		int ret = recv(sockfd,buf,len,MSG_PEEK);//MSG_PEEK接收缓冲区的数据,但是并没有清除
		if( ret == -1 && errno == EINTR)
			continue;
		return ret;
	}
}

// 读到'\n' 就返回,加上'\n'一行最多为maxline个字符
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
	int ret;
	int nread;
	char *bufp =(char *) buf;
	int nleft = maxline;
	int count=0;
	
	while(1)
	{	
		// recv_peek读取缓冲区的字符个数,并放入到bufp缓存里面
		ret = recv_peek(sockfd,bufp,nleft);
		if(ret < 0)
			return ret;// 表示失败
		else if(ret == 0)
			return ret; // 表示对方关闭连接了
			
		nread = ret;
		// 判断接收到字符是否有'\n'
		int i;
		for(i=0;i<nread;++i)
		{
			if(bufp[i] == '\n')
			{
			    // readn读取数据,这部分缓冲会被清空的
				ret = readn(sockfd,bufp,i+1);
				if(ret != (i+1))
					exit(EXIT_FAILURE);
				return ret + count;
			}
		}
		if( nread > nleft)
			exit(EXIT_FAILURE);
		nleft -= nread;
		ret = readn(sockfd,bufp,nread);
		if(ret != nread)
			exit(EXIT_FAILURE);
		bufp += nread;// 下一次指针偏移	
		count += nread;
	}
	return -1;
}


int main(void)
{
    
    signal(SIGPIPE, SIG_IGN);
    int listenfd; //被动套接字(文件描述符),即只可以accept, 监听套接字
    if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
//  listenfd = socket(AF_INET, SOCK_STREAM, 0)  
        ERR_EXIT("socket error");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(5188);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
    /* inet_aton("127.0.0.1", &servaddr.sin_addr); */
    
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
        ERR_EXIT("setsockopt error");

    if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
        ERR_EXIT("bind error");

    if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
        ERR_EXIT("listen error");
    
    struct sockaddr_in peeraddr; //传出参数
    socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值
    
    int conn; // 已连接套接字(变为主动套接字,即可以主动connect)
    int i;
    int client[FD_SETSIZE];
    int maxi = 0; // client数组中最大不空闲位置的下标
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;

    int nready;
    int maxfd = listenfd;
    fd_set rset;
    fd_set allset;
    FD_ZERO(&rset);
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    while (1) {
        rset = allset;
        nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
        if (nready == -1) {
            if (errno == EINTR)
                continue;
            ERR_EXIT("select error");
        }

        if (nready == 0)
            continue;

        if (FD_ISSET(listenfd, &rset)) {
        
            conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);  //accept不再阻塞
            if (conn == -1)
                ERR_EXIT("accept error");
            
            for (i = 0; i < FD_SETSIZE; i++) {
                if (client[i] < 0) {
                    client[i] = conn;
                    if (i > maxi)
                        maxi = i;
                    break;
                } 
            }
            
            if (i == FD_SETSIZE) {
                fprintf(stderr, "too many clients\n");
                exit(EXIT_FAILURE);
            }

            printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),
                ntohs(peeraddr.sin_port));

            FD_SET(conn, &allset);
            if (conn > maxfd)
                maxfd = conn;

            if (--nready <= 0)
                continue;
        }

        for (i = 0; i <= maxi; i++) {
            conn = client[i];
            if (conn == -1)
                continue;

            if (FD_ISSET(conn, &rset)) {
                
                char recvbuf[1024] = {0};
                int ret = readline(conn, recvbuf, 1024);
                if (ret == -1)
                    ERR_EXIT("readline error");
                else if (ret  == 0) { //客户端关闭 
                    printf("client close \n");
                    FD_CLR(conn, &allset);
                    client[i] = -1;
                    close(conn);
                }
        
                fputs(recvbuf, stdout);
                
                sleep(3);
                
                writen(conn, recvbuf, strlen(recvbuf));
                
                if (--nready <= 0)
                    break; 
            }
        }


    }
        
    return 0;
}


/// echolic.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

#define ERR_EXIT(m) \
		do{ \
			perror(m); \
			exit(EXIT_FAILURE); \
		}while(0)
		
		


ssize_t readn(int fd,void *buf,size_t count)
{
	size_t nleft = count ; // 未读取的数据
	ssize_t nread;// 已读取的数据
	char *bufp= (char*)buf;
	while(nleft > 0)
	{
		if( (nread = read(fd,bufp,nleft)) < 0)
		{
			if( errno == EINTR)
				 nread = 0;//  继续读取数据
			else
				return -1;
		}
		else if( nread == 0) // 对方关闭或已经读到eof
			break;
		bufp +=nread;
		nleft -= nread;
	
	}
	return count-nleft;
}

ssize_t writen(int fd,const void *buf,size_t count)
{
	size_t nleft=count;  // 未读取的
	ssize_t nwritten;    // 已读取的
 	char *bufp = (char*)buf;
 	
 	while(nleft > 0)
 	{
 		if((nwritten = write(fd,bufp,nleft)) < 0)
 		{
 			if( errno == EINTR)
 				continue;
 			else
 				return -1;
 		}
 		else if( nwritten == 0)
 			continue;
 		bufp  += nwritten;
 		nleft -= nwritten;
 	}
 	return count;
}


/// recv()只能读取套接字,而不能读取一般文件描述符
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
	while(1)
	{
		int ret = recv(sockfd,buf,len,MSG_PEEK);//MSG_PEEK接收缓冲区的数据,但是并没有清除
		if( ret == -1 && errno == EINTR)
			continue;
		return ret;
	}
}

// 读到'\n' 就返回,加上'\n'一行最多为maxline个字符
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
	int ret;
	int nread;
	char *bufp =(char *) buf;
	int nleft = maxline;
	//int count=0;
	
	while(1)
	{	
		// recv_peek读取缓冲区的字符个数,并放入到bufp缓存里面
		ret = recv_peek(sockfd,bufp,nleft);
		if(ret < 0)
			return ret;// 表示失败
		else if(ret == 0)
			return ret; // 表示对方关闭连接了
			
		nread = ret;
		// 判断接收到字符是否有'\n'
		int i;
		for(i=0; i<nread; ++i)
		{
			if(bufp[i] == '\n')
			{
			    // readn读取数据,这部分缓冲会被清空的
				ret = readn(sockfd,bufp,i+1);
				if(ret != (i+1))
					exit(EXIT_FAILURE);
				return ret; //+ count;
			}
		}
		if( nread > nleft)
			exit(EXIT_FAILURE);
		nleft -= nread;
		ret = readn(sockfd,bufp,nread);
		if(ret != nread)
			exit(EXIT_FAILURE);
		bufp += nread;// 下一次指针偏移	
		//count += nread;
	}
	return -1;
}

/*
void echo_cli(int sock)
{

	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while( fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)  
	{
		writen(sock,sendbuf,strlen(sendbuf));// 需要的注意的的时sizeof(sendbuf)和strlen(recvbuf)不一样的,容易出现混淆
		int ret = readline(sock,recvbuf,1024);///最后一个参数为缓冲区的最大值
		if( ret == -1 )
			ERR_EXIT("readline");
		else if(ret == 0)
		{
			printf("client close \n");
			break;
		}
		fputs(recvbuf,stdout);
		memset(recvbuf,0,sizeof(recvbuf));
		memset(sendbuf,0,sizeof(sendbuf));
	}
    close(sock); 	
}
*/

//// 核心修改代码
void echo_cli(int sock)
{
    fd_set rset;// fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄
    FD_ZERO(&rset);

    int nready;
    int maxfd;
    int fd_stdin = fileno(stdin); //取得参数stream指定的文件流所使用的文件描述符
    if (fd_stdin > sock)
        maxfd = fd_stdin;
    else
        maxfd = sock;

    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};

	int stdineof=0;//标记
    while (1)
    {
		if( stdineof == 0)
			FD_SET(stdineof,&rset);
        FD_SET(fd_stdin, &rset);// 输入流文件描述符添加到文件描述符中
        FD_SET(sock, &rset);// sock文件描述符添加到集合中
        nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示检测到可读事件
        if (nready == -1)// 负值:select错误
            ERR_EXIT("select error");

        if (nready == 0)///  0:等待超时,没有可读写或错误的文件
            continue;
		///// 正值:某些文件可读写或出错
        if (FD_ISSET(sock, &rset)) // 判断sock 是否在集合中,如果在,进行读操作
        {

            int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取
            if (ret == -1)
                ERR_EXIT("read error");
            else if (ret  == 0)   //服务器关闭
            {
                printf("server close\n");
                break;
            }

            fputs(recvbuf, stdout);
            memset(recvbuf, 0, sizeof(recvbuf));
        }

        if (FD_ISSET(fd_stdin, &rset))// 判断fd_stdin 是否在集合中,如果在,进行写操作
        {

            if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
            {
            	stdineof =1;// 表示已经写入完毕
            	shutdown(sock,SHUT_WR);
            	
            }
  			else
  			{
  				writen(sock,sendbuf,strlen(sendbuf));
  				memset(sendbuf,0,sizeof(sendbuf));
  			}
  			
        }
    }

    close(sock);
}



int main()
{
	int sock;
	if( (sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
			ERR_EXIT("sock err");
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));

	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
	
	if( connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
			ERR_EXIT("connect error");
	
	struct sockaddr_in localaddr;
	socklen_t addrlen = sizeof(localaddr);
	if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen) < 0)
		ERR_EXIT("getsockname err");
	printf("ip=%s ,port = %d \n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
	
	
	echo_cli(sock);
      
	return 0;
}








发布了112 篇原创文章 · 获赞 16 · 访问量 18万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章