一. 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信號的缺省處理動作是終止程序。
#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;
}