shutdown文件夹代码
为什么要做成三次握手:相互确认身份,tcpip是全双工双通道协议
1.僵死进程的产生
一个进程在调用exit命令结束自己生命的时候其实并没有真正被销毁,而是留下一个称为僵死进程的数据结构,在linux进程的状态中,僵死进程是非常特殊的一种,它已经放弃了几乎所有的内存空间,没有任何执行代码,也不能被调度,仅仅是在进程列表中保留 一个位置,记录该进程退出状态等信息供其他进程收集,除此之外,僵死进程不再占用任何内存空间,它需要它的父进程来为他收尸,如果它的父进程没有安装sigchld信号处理函数调用wait或者waitpid等待子进程结束,又没有显示的忽略这个信号,那么它就一直保持僵死状态,如果父进程结束了,那么init进程会接收这个子进程,为他收尸,它就可以被清除。
2.waitpid
3.避免僵死进程
客户端断开连接,同时发送五个SIGCHLD信号到服务端,但是sigchild是不可靠信号,父进程不一定能收到5个sigchild信号
避免僵尸进程的方法
4.tcp/ip协议的十一种状态
- 服务端调用socket初始化并通过bind绑定接口然后再调用listen函数使得服务端进入LISTEN状态
- 服务端调用accept函数,进入SYN_RCVD状态,accept阻塞等待客户端连接
- 客户端调用connect函数,客户端进入SYN_SENT状态
- connect发送一个syn a到服务端,服务端回一个syn b ack a+1,客户端通过判断a+1确认后进入ESTABLISHED状态,同时发给服务端一个ack b+1
- 服务端接收到b+1确认后进入ESTABLISHED状态
- 为什么会有三次握手?因为tcpip是全双工协议,需要双方都确认身份。
- 客户端和服务端都可以先close,先调用close的那一端他的socket的状态最终会成为TIME_WAIT状态
- 为什么主动关闭套接字的那一方需要进入TIME_WAIT状态?先关闭的那一端并不是进入closed状态是因为需要发送一个ack y+1到对等方让对等方完全关闭,在网络环境下并不能保证这个ack发送成功,在TIME_WAIT状态下可以多次重发,让最后一个确认包LAST_ACK得到真正的确认
- 为什么双方都需要显示的close?
- 半链接状态FIN_WAIT_2,调用close之后,进入FIN_WAIT_1状态,在对等方读到0的时候,tcpip协议会回一个ack,然后进入半链接状态。
6.sigpipe
如果对方socket已关闭,对等方再发写数据,则会产生SIGPIPE信号
- SIGPIPE信号会让进程终止(man 7 signal,阅读SIGPIPE默认ACT)
- 往一个已经接收FIN的套接中写是允许的,接收到FIN仅仅代表对方不再发送数据。
- 在收到RST段之后,如果再调用write就会产生SIGPIPE信号,对于这个信号的处理我们通常忽略即可。
signal(SIGPIPE, SIG_IGN);
结论:对SIGPIPE处理方法:
1)忽略该信号即可signal(SIGPIPE, SIG_IGN);
2)捕捉。改变默认行为
7.close和shutdown的区别
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)
continue;
return -1;
}
else if (nread == 0)
return count - nleft;
bufp += nread;
nleft -= nread;
}
return count;
}
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;
return -1;
}
else if (nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == -1 && errno == EINTR)
continue;
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = buf;
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, bufp, nleft);
if (ret < 0)
return ret;
else if (ret == 0)
return ret;
nread = ret;
int i;
for (i=0; i nleft)
exit(EXIT_FAILURE);
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
exit(EXIT_FAILURE);
bufp += nread;
}
return -1;
}
void echo_srv(int conn)
{
char recvbuf[1024];
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = readline(conn, recvbuf, 1024);
if (ret == -1)
ERR_EXIT("readline");
if (ret == 0)
{
printf("client close\n");
break;
}
if (recvbuf[0] == '2') //注意2 一共2处。。。。
{
//close(conn); //11111111111
//close(conn); //11111111111
shutdown(conn, SHUT_WR);
}
fputs(recvbuf, stdout);
writen(conn, recvbuf, strlen(recvbuf));
}
}
void handle_sigchld(int sig)
{
int mypid = 0;
while ( (mypid=waitpid(-1, NULL, WNOHANG)) >0 )
{
printf("孩子退出,父进程要收尸:%d \n", mypid);
}
}
int main(void)
{
signal(SIGCHLD, handle_sigchld);
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8001);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
pid_t pid;
while (1)
{
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid = fork();
if (pid == -1)
ERR_EXIT("fork");
if (pid == 0)
{
close(listenfd);
echo_srv(conn);
exit(EXIT_SUCCESS);
}
else
{
//close(conn); //注意1
}
}
return 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)
continue;
return -1;
}
else if (nread == 0)
return count - nleft;
bufp += nread;
nleft -= nread;
}
return count;
}
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;
return -1;
}
else if (nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == -1 && errno == EINTR)
continue;
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = buf;
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, bufp, nleft);
if (ret < 0)
return ret;
else if (ret == 0)
return ret;
nread = ret;
int i;
for (i=0; i nleft)
exit(EXIT_FAILURE);
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
exit(EXIT_FAILURE);
bufp += 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));
int ret = readline(sock, recvbuf, sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("readline");
else if (ret == 0)
{
printf("client close\n");
break;
}
fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8001);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect");
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0)
ERR_EXIT("getsockname");
printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
echo_cli(sock);
return 0;
}
8.io模型
8.1 阻塞io模型
- 1.当上层应用app1调用recv系统调用时,如果对等方没有发送数据(缓冲区没有数据),上层应用app1将阻塞(默认行为,被linux内核阻塞);
- 2.当对等方发送了数据,linux内核recv端缓冲区,有数据后,内核会把数据copy给用户空间。然后上层应用app1解除阻塞,执行下一步操作。
8.2 非阻塞io
- 1: 上层应用程序app2将套接字设置成非阻塞模式。
- 2: 上层应用程序app2轮询调用recv函数,接受数据。若缓冲区没有数据,上层程序app2不会阻塞,recv返回值为-1,错误码是EWOULDBLOCK。
- 3:上层应用程序不断轮询有没有数据到来。会造成上层应用忙等待。大量消耗CPU。很少直接用。应用范围小,一般和selectIO复用配合使用
8.3 io复用
- 1: 上层应用程序app3调用select机制(该机制有linux内核支持,避免了app3忙等待。),进行轮询文件描述符的状态变化。
- 2:当select管理的文件描述符没有数据(或者状态没有变化时),上层应用程序app3也会阻塞。
- 3:好处select机制可以管理多个文件描述符
- 4:select可以看成一个管理者,用select来管理多个IO。
一旦检测到的一个I/O或者多个IO,有我们感兴事件,发生,select函数将返回,返回值为检测到的事件个数。进而可以利用select相关api函数,操作具体事件。 - 5:select函数可以设置等待时间,避免了上层应用程序app3,长期僵死。
- 6: 和阻塞IO模型相比,selectI/O复用模型相当于提前阻塞了。等到有数据到来时,再调用recv就不会发生阻塞。
8.4 信号驱动io模型
- 1: 上层应用程序app4建立SIGIO信号处理程序。当缓冲区有数据到来,内核会发送信号告诉上层应用程序app4。
- 2:上层应用程序app4接收到信号后,调用recv函数,因缓冲区有数据,recv函数一般不会阻塞。
- 3:这种用于模型用的比较少,属于典型的“拉模式”。即:上层应用app4,需要调用recv函数把数据拉进来。
- 4:这种方式的缺点是信号可能会有延时,信号处理的延时期间数据可能会发生变化
8.5 异步io
- 1:上层应用程序app5调用aio_read函数,同时提交一个应用层的缓冲区buf;调用完毕后,不会阻塞。上层应用程序app5可以继续其他任务。
- 2:当tcpip协议缓冲区有数据时,linux主动的把内核数据copy到用户空间。然后再给上层应用app5发送信号;告诉app5数据有了,赶快处理吧!
- 3:典型的“推模式”
- 4: 效率最高的一种形式,上层应用程序app5有异步处理的能力(在linux内核的支持下,言外之意:处理其他任务的同时,也可支持IO通讯)。
- 5:异步I/O指的是什么?
上层应用程序app5,在也可以干别的活的时,可以接收数据(接受异步通信事件。===)异步命令来源)。与信号驱动IO模型,上层应用程序app5不需要调用recv函数。
9.select
allow a program to monitor multiple file descriptors, waiting until one or more of
the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file
descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2))
without blocking.
允许程序监控多个文件描述符,直到有一个或者多个文件描述符可用,所谓可用是指,这个文件描述符在执行io操作的时候不会被阻塞
/* According to POSIX.1-2001 */
#include
/* According to earlier standards */
#include
#include
#include
//nfds,最大的文件描述符加1(比如集合里有1,2,7,那么n就是8)
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
•int FD_ISSET(int fd, fd_set *fdset);
–测试某个描述符是否在集合内
•void FD_CLR(int fd, fd_set *fdset);
–从集合内把一个描述符移除
•void FD_SET(int fd, fd_set *fdset);
–把一个描述符加入集合
•void FD_ZERO(fd_set *fdset);
–清空描述符集合。
当那个文件描述符可用的时候,那一位就变成1
10.实现说明
select()实现说明
- 调用select时通过参数告诉内核用户感兴趣的IO描述符
- 关心的IO状态: 输入,输出或错误
- 返回之后内核告诉调用者多个描述符准备好了
- 哪些描述符发生了变化
- 调用返回后对准备好的描述符调用读写操作
- 不关心的描述符集合传NULL
11. select实例:监控输入
int main(void)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
while (1)
{
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 2;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval == -1)
perror("select() -1");
else if (retval)
{
printf("Data is available now.\n");
}
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.\n");
}
return 0;
}
12. 封装
头
void activate_nonblock(int fd);
void deactivate_nonblock(int fd);
int read_timeout(int fd, unsigned int wait_seconds);
int write_timeout(int fd, unsigned int wait_seconds);
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
ssize_t readn(int fd, void *buf, size_t count);
ssize_t writen(int fd, const void *buf, size_t count);
ssize_t recv_peek(int sockfd, void *buf, size_t len);
ssize_t readline(int sockfd, void *buf, size_t maxline);
实现
/**
* read_timeout - 读超时检测函数,不含读操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int read_timeout(int fd, unsigned int wait_seconds)
{
int ret = 0;
if (wait_seconds > 0)
{
fd_set read_fdset;
struct timeval timeout;
FD_ZERO(&read_fdset);
FD_SET(fd, &read_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
//select返回值三态
//1 若timeout时间到(超时),没有检测到读事件 ret返回=0
//2 若ret返回<0 && errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理)
//2-1 若返回-1,select出错
//3 若ret返回值>0 表示有read事件发生,返回事件发生的个数
do
{
ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret == 1)
ret = 0;
}
return ret;
}
/**
* write_timeout - 写超时检测函数,不含写操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int write_timeout(int fd, unsigned int wait_seconds)
{
int ret = 0;
if (wait_seconds > 0)
{
fd_set write_fdset;
struct timeval timeout;
FD_ZERO(&write_fdset);
FD_SET(fd, &write_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret == 1)
ret = 0;
}
return ret;
}
/**
* accept_timeout - 带超时的accept
* @fd: 套接字
* @addr: 输出参数,返回对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
*/
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
if (wait_seconds > 0)
{
fd_set accept_fdset;
struct timeval timeout;
FD_ZERO(&accept_fdset);
FD_SET(fd, &accept_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == -1)
return -1;
else if (ret == 0)
{
errno = ETIMEDOUT;
return -1;
}
}
//一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立
//此时再调用accept将不会堵塞
if (addr != NULL)
ret = accept(fd, (struct sockaddr*)addr, &addrlen); //返回已连接套接字
else
ret = accept(fd, NULL, NULL);
if (ret == -1)
ERR_EXIT("accept");
return ret;
}
/**
* activate_noblock - 设置I/O为非阻塞模式
* @fd: 文件描符符
*/
void activate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
ERR_EXIT("fcntl");
flags |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -1)
ERR_EXIT("fcntl");
}
/**
* deactivate_nonblock - 设置I/O为阻塞模式
* @fd: 文件描符符
*/
void deactivate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
ERR_EXIT("fcntl");
flags &= ~O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -1)
ERR_EXIT("fcntl");
}
/**
* connect_timeout - connect
* @fd: 套接字
* @addr: 要连接的对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
if (wait_seconds > 0)
activate_nonblock(fd);
ret = connect(fd, (struct sockaddr*)addr, addrlen);
if (ret < 0 && errno == EINPROGRESS)
{
//printf("11111111111111111111\n");
fd_set connect_fdset;
struct timeval timeout;
FD_ZERO(&connect_fdset);
FD_SET(fd, &connect_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
// 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中
ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret < 0)
return -1;
else if (ret == 1)
{
//printf("22222222222222222\n");
/* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
/* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
int err;
socklen_t socklen = sizeof(err);
int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
if (sockoptret == -1)
{
return -1;
}
if (err == 0)
{
//printf("3333333333333\n");
ret = 0;
}
else
{
//printf("4444444444444444:%d\n", err);
errno = err;
ret = -1;
}
}
}
if (wait_seconds > 0)
{
deactivate_nonblock(fd);
}
return ret;
}
/**
* readn - 读取固定字节数
* @fd: 文件描述符
* @buf: 接收缓冲区
* @count: 要读取的字节数
* 成功返回count,失败返回-1,读到EOF返回 0)
{
if ((nread = read(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
continue;
return -1;
}
else if (nread == 0)
return count - nleft;
bufp += nread;
nleft -= nread;
}
return count;
}
/**
* writen - 发送固定字节数
* @fd: 文件描述符
* @buf: 发送缓冲区
* @count: 要读取的字节数
* 成功返回count,失败返回-1
*/
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;
return -1;
}
else if (nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
/**
* recv_peek - 仅仅查看套接字缓冲区数据,但不移除数据
* @sockfd: 套接字
* @buf: 接收缓冲区
* @len: 长度
* 成功返回>=0,失败返回-1
*/
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == -1 && errno == EINTR)
continue;
return ret;
}
}
/**
* readline - 按行读取数据
* @sockfd: 套接字
* @buf: 接收缓冲区
* @maxline: 每行最大长度
* 成功返回>=0,失败返回-1
*/
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = buf;
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, bufp, nleft);
if (ret < 0)
return ret;
else if (ret == 0)
return ret;
nread = ret;
int i;
for (i=0; i nleft)
exit(EXIT_FAILURE);
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
exit(EXIT_FAILURE);
bufp += nread;
}
return -1;
}