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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章