一. 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;
}