7.2 TCP IP的11种状态
先看TCP IP的10种状态,如下所示:
三次握手:
客户端A端发送SYN,然后进入SYN_SENT状态,服务器B端接收到SYN后,返回一个响应ACK,同时也发送一个SYN,然后B端进入SYN_RCVD状态,A端收到ACK后进入ESTABLISHED状态,然后发送一个ACK,服务器B端收到ACK后进入ESTABLISHED状态。
四次分手:
先关闭的一端A端发送FIN然后进入FIN_WAIT_1状态,另一端B端会返回一个响应,然后A端进入FIN_WAIT_2状态。当服务器端B端检测到对端已经关闭(read到0)时,也会调用close,发送FIN给A端,然后B端进入LAST_ACK状态,A端收到FIN后回复一个响应然后进入TIME_WAIT状态,服务器B端接收到响应后进入CLOSED状态。
三次握手双方都进入ESTABLISHED状态后,表示双方可以通信了。查看TCP的连接状态可以使用netstat -na。
TCP IP协议为什么要做成三次握手和四次断开?
因为TCP IP是全双工协议,需要互相确认对方的身份,A要确认B收到自己发的包了,B也要确认A收到自己发的包了。
两个银行之间的连接和上述握手过程类似,A银行先发送加密包,B端收到后解密,然后B发送一个加密包,A端收到后解密,在这个过程中,协商出一个秘钥,从此这个秘钥就用来在两个会话之间进行加密。
四次分手中,任何一端都可以先关闭连接,先调用close的那一端,最终状态要推进到TIME_WAIT,TIME_WAIT状态的含义就是等一会再最终关闭。套接字处于TIME_WAIT状态时,再次在这个套接字上启动服务器是起不来的,除非使用套接字的IO复用技术(SO_REUSEADDR)。
调用close相当于发送了一个'\0',另一端会读取到0,读取到0就会知道对方已经关闭了(至少对方的写数据断了)。当A端关闭了socket,不代表B端不能往自己的socket中写数据,最多是写失败,毕竟socket是存在缓冲区的。B端得知A端已关闭后,可以根据自己的业务需要,来关闭自己的socket,也可以不关闭。TCP IP是全双工的,两端都执行关闭,才能将这条通路关闭,如果只有一端关闭,那么这条通路是没有彻底关闭的,只是关闭了一个方向的数据流。
主动关闭的一方进入FIN_WAIT_2就是半连接状态,只要对等方不调用close,这个连接就一直处于半连接状态。服务器端接收到FIN后,是TCP IP内核回复的ACK,对应用透明。
我们用一个实验查看半连接状态。
服务器程序如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */ void handler(int num)
{
int mypid = ;
while((mypid = waitpid(-, NULL, WNOHANG)) > )
{
printf("child %d die\n", mypid);
}
} int main()
{
int sockfd = ;
signal(SIGCHLD, handler);
sockfd = socket(AF_INET, SOCK_STREAM, ); if(sockfd == -)
{
perror("socket error");
exit();
} struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
inet_aton("192.168.31.128", &addr.sin_addr);
//addr.sin_addr.s_addr = inet_addr("192.168.6.249");
//addr.sin_addr.s_addr = INADDR_ANY; int optval = ;
if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < )
{
perror("setsockopt error");
exit();
} if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < )
{
perror("bind error");
exit();
} if(listen(sockfd, SOMAXCONN) < )
{
perror("listen error");
exit();
} struct sockaddr_in peeraddr;
socklen_t peerlen; int conn = ; while()
{
conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
if(conn == -)
{
perror("accept error");
exit();
} char *p = NULL;
int peerport = ;
p = inet_ntoa(peeraddr.sin_addr);
peerport = ntohs(peeraddr.sin_port);
printf("peeraddr = %s\n peerport = %d\n", p, peerport); pid_t pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
char recvbuf[] = {};
int ret = ;
while()
{
ret = read(conn, recvbuf, sizeof(recvbuf)); if(ret == )
{
printf("peer closed \n");
exit();
}
else if(ret < )
{
perror("read error");
exit();
} fputs(recvbuf, stdout); write(conn, recvbuf, ret);
}
}
} close(conn);
close(sockfd); return ;
}
客户端程序如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */ int main()
{
int sockfd[]; int i = ; for(i = ; i < ; i++)
{
sockfd[i] = socket(AF_INET, SOCK_STREAM, ); struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
inet_aton("192.168.31.128", &addr.sin_addr);
//addr.sin_addr.s_addr = inet_addr("192.168.31.128"); if( connect(sockfd[i], (struct sockaddr *)&addr, sizeof(addr)) == - )
{
perror("connect error");
exit();
} struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if(getsockname(sockfd[i], (struct sockaddr*)&localaddr, &addrlen) < )
{
perror("getsockname error");
exit();
} printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); } char recvbuf[] = {};
char sendbuf[] = {};
int ret = ; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sockfd[], sendbuf, strlen(sendbuf)); ret = read(sockfd[], recvbuf, sizeof(recvbuf)); fputs(recvbuf, stdout);
memset(recvbuf, , sizeof(recvbuf));
memset(sendbuf, , sizeof(sendbuf)); } return ;
}
我们首先启动服务器,然后启动客户端,然后关掉服务器(执行主动关闭),并查看套接字状态,如下所示:
服务器执行主动关闭,由程序可以得知,服务器关闭后会发送FIN给客户端,客户端的TCP IP协议栈接收到FIN后恢复ACK给服务器,这时候服务器进入了FIN_WAIT2状态,但是客户端一直阻塞在fgets函数那里,所以没有执行close函数,因此,客户端也就不会主动发送FIN,因此,服务器处于半连接状态(由于我们将服务器进程关闭了,所以这个半连接状态过一段时间也会消失,如果不关闭服务器,则会一直处于半连接状态)。客户端套接字一直处于CLOSE_WAIT状态。
首先关闭的一端存在TIME_WAIT是因为要确保最后一个确认包能发送到对端,保证对端能正常关闭,具体原因见另一篇博客。
以上我们只说了10种状态,现在来看第11种状态,如下所示:
当通信双方同时close时,会出现第11种状态,叫CLOSIING。
发送FIN后进入FIN_WAIT_1状态,收到对端的FIN后,回复一个ACK并进入CLOSIING状态,收到对端的ACK后进入TIME_WAIT状态。
细节知识:
如果发送端向缓冲区写入数据,然后调用close,这样接收端还能收到数据吗?例如调用如下的程序片段:
send(fd, "abcd");
close(fd);
接收端是可以收到数据的,而且可以可靠的收到。发送端会先将abcd顺序写入缓冲区,调用close时,会将'\0'也写入缓冲区,然后开始像流水一样顺序发送出去。接收端的TCP IP协议栈会逐个分析,当发现FIN时就知道发送端关闭了。
服务器向客户端发送了FIN,但是客户端的应用程序没有处理读到的0,而是继续向socket套接字写数据,向服务器发送报文。因为TCP IP是双工的,服务器关闭socket,不等于客户端不能写数据,在这种场景下,如果客户端向服务器发送数据,会引起TCP IP协议RST段重置,会使客户端接收到一个SIGPIPE信号,如果客户端不处理,则默认动作是让进程退出。
管道破裂示例程序如下:
服务器:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */ void handler(int num)
{
int mypid = ;
while((mypid = waitpid(-, NULL, WNOHANG)) > )
{
printf("child %d die\n", mypid);
}
} int main()
{
int sockfd = ;
signal(SIGCHLD, handler);
sockfd = socket(AF_INET, SOCK_STREAM, ); if(sockfd == -)
{
perror("socket error");
exit();
} struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
inet_aton("192.168.31.128", &addr.sin_addr);
//addr.sin_addr.s_addr = inet_addr("192.168.6.249");
//addr.sin_addr.s_addr = INADDR_ANY; int optval = ;
if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < )
{
perror("setsockopt error");
exit();
} if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < )
{
perror("bind error");
exit();
} if(listen(sockfd, SOMAXCONN) < )
{
perror("listen error");
exit();
} struct sockaddr_in peeraddr;
socklen_t peerlen; int conn = ; while()
{
conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
if(conn == -)
{
perror("accept error");
exit();
} char *p = NULL;
int peerport = ;
p = inet_ntoa(peeraddr.sin_addr);
peerport = ntohs(peeraddr.sin_port);
printf("peeraddr = %s\n peerport = %d\n", p, peerport); pid_t pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
printf("child pid=%d\n", getpid());
char recvbuf[] = {};
int ret = ;
while()
{
ret = read(conn, recvbuf, sizeof(recvbuf)); if(ret == )
{
printf("peer closed \n");
exit();
}
else if(ret < )
{
perror("read error");
exit();
} fputs(recvbuf, stdout); write(conn, recvbuf, ret);
}
} close(conn);
} close(conn);
close(sockfd); return ;
}
客户端:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */ int main()
{
int sockfd[]; int i = ; for(i = ; i < ; i++)
{
sockfd[i] = socket(AF_INET, SOCK_STREAM, ); struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
inet_aton("192.168.31.128", &addr.sin_addr);
//addr.sin_addr.s_addr = inet_addr("192.168.31.128"); if( connect(sockfd[i], (struct sockaddr *)&addr, sizeof(addr)) == - )
{
perror("connect error");
exit();
} struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if(getsockname(sockfd[i], (struct sockaddr*)&localaddr, &addrlen) < )
{
perror("getsockname error");
exit();
} printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); } char recvbuf[] = {};
char sendbuf[] = {};
int ret = ; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
//write(sockfd[0], sendbuf, strlen(sendbuf));
write(sockfd[], "aaaaaaaaaa", );
write(sockfd[], "aaaaaaaaaa", );
write(sockfd[], "aaaaaaaaaa", );
write(sockfd[], "aaaaaaaaaa", );
//ret = read(sockfd[0], recvbuf, sizeof(recvbuf)); //fputs(recvbuf, stdout);
memset(recvbuf, , sizeof(recvbuf));
memset(sendbuf, , sizeof(sendbuf)); } return ;
}
我们的实验步骤是这样的,启动服务器,然后启动客户端,使用kill命令杀死服务器中的业务进程(子进程),然后查看套接字状态,这时可以看到客户端进程处于CLOSE_WAIT状态,因为客户端没有调用close,而是阻塞在fgets函数了。这时,我们在终端上随便输入几个字符,让客户端往套接字写数据。这时客户端会退出(这就是管道破裂了),ps -u已经看不到客户端进程了。
结果如下:
网络服务程序中有些进程莫名退出可能就是管道破裂导致的。
当管道破裂时,我们不想让进程退出,而是捕捉信号做其他处理,因此,需要注册信号处理函数,修改后的客户端程序如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */ void handler(int num)
{
printf("signal num : %d\n", num);
} int main()
{
int sockfd[]; signal(SIGPIPE, handler); int i = ; for(i = ; i < ; i++)
{
sockfd[i] = socket(AF_INET, SOCK_STREAM, ); struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
inet_aton("192.168.31.128", &addr.sin_addr);
//addr.sin_addr.s_addr = inet_addr("192.168.31.128"); if( connect(sockfd[i], (struct sockaddr *)&addr, sizeof(addr)) == - )
{
perror("connect error");
exit();
} struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if(getsockname(sockfd[i], (struct sockaddr*)&localaddr, &addrlen) < )
{
perror("getsockname error");
exit();
} printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); } char recvbuf[] = {};
char sendbuf[] = {};
int ret = ; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
//write(sockfd[0], sendbuf, strlen(sendbuf));
write(sockfd[], "aaaaaaaaaa", );
write(sockfd[], "aaaaaaaaaa", );
write(sockfd[], "aaaaaaaaaa", );
write(sockfd[], "aaaaaaaaaa", );
//ret = read(sockfd[0], recvbuf, sizeof(recvbuf)); //fputs(recvbuf, stdout);
memset(recvbuf, , sizeof(recvbuf));
memset(sendbuf, , sizeof(sendbuf)); } return ;
}
管道破裂时,进程不会退出了,执行结果如下:
实际应用中,我们注册SIGPIPE的信号处理函数,然后对write的返回值做异常处理就好了。
调用close相当于将读和写全部关闭,shutdown函数可以有选择的终止某个方向的数据的传送或者终止数据传送的两个方向。当我们只想关闭某一个方向的写,不想关闭收时可以调用这个函数。比如我们发送ABC,然后调用shutdown(发送FIN),则关闭了写,ABC和FIN会传送到对端,我们还可以在这一端读取对端的回信。对端可能会回复DEF,然后关闭对端的套接字。
shutdown how=1就可以保证对等方接收到一个EOF(\0)字符,而不管其他进程是否已经打开了套接字。而close不能保证,直到套接字引用计数减为0时才发送FIN。也就是说直到所有的进程都关闭了套接字。不管文件描述符的引用计数为2,3,5或者其他,但是我们还想关闭这个文件描述符,这时就可以用shutdown了。
how参数可以选择关闭读或者写,下面我们进行实验。
服务器端程序:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */ void handler(int num)
{
int mypid = ;
while((mypid = waitpid(-, NULL, WNOHANG)) > )
{
printf("child %d die\n", mypid);
}
} int main()
{
int sockfd = ;
signal(SIGCHLD, handler);
sockfd = socket(AF_INET, SOCK_STREAM, ); if(sockfd == -)
{
perror("socket error");
exit();
} struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
inet_aton("192.168.31.128", &addr.sin_addr);
//addr.sin_addr.s_addr = inet_addr("192.168.6.249");
//addr.sin_addr.s_addr = INADDR_ANY; int optval = ;
if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < )
{
perror("setsockopt error");
exit();
} if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < )
{
perror("bind error");
exit();
} if(listen(sockfd, SOMAXCONN) < )
{
perror("listen error");
exit();
} struct sockaddr_in peeraddr;
socklen_t peerlen; int conn = ; while()
{
conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
if(conn == -)
{
perror("accept error");
exit();
} char *p = NULL;
int peerport = ;
p = inet_ntoa(peeraddr.sin_addr);
peerport = ntohs(peeraddr.sin_port);
printf("peeraddr = %s\n peerport = %d\n", p, peerport); pid_t pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
close(sockfd);
printf("child pid=%d\n", getpid());
char recvbuf[] = {};
int ret = ;
while()
{
ret = read(conn, recvbuf, sizeof(recvbuf)); if(ret == )
{
printf("peer closed \n");
exit();
}
else if(ret < )
{
perror("read error");
exit();
} fputs(recvbuf, stdout);
write(conn, recvbuf, ret); if(recvbuf[] == '')
{
close(conn);
//shutdown(conn, SHUT_WR);
} }
} //close(conn);
} close(conn);
close(sockfd); return ;
}
客户端程序如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */ void handler(int num)
{
printf("signal num : %d\n", num);
} int main()
{
int sockfd[]; signal(SIGPIPE, handler); int i = ; for(i = ; i < ; i++)
{
sockfd[i] = socket(AF_INET, SOCK_STREAM, ); struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
inet_aton("192.168.31.128", &addr.sin_addr);
//addr.sin_addr.s_addr = inet_addr("192.168.31.128"); if( connect(sockfd[i], (struct sockaddr *)&addr, sizeof(addr)) == - )
{
perror("connect error");
exit();
} struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if(getsockname(sockfd[i], (struct sockaddr*)&localaddr, &addrlen) < )
{
perror("getsockname error");
exit();
} printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); } char recvbuf[] = {};
char sendbuf[] = {};
int ret = ; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sockfd[], sendbuf, strlen(sendbuf)); ret = read(sockfd[], recvbuf, sizeof(recvbuf)); if(ret == )
{
perror("server closed");
exit();
} fputs(recvbuf, stdout);
memset(recvbuf, , sizeof(recvbuf));
memset(sendbuf, , sizeof(sendbuf)); } return ;
}
在服务器端程序中,我们将122行的close(conn)注释掉,这时候conn的引用计数为2,第113-117行中,当服务器接收到第一个字符时'2'时,子进程关闭conn,但是这时候conn
的引用计数是1,不会发送FIN,因为调用close时,只有引用计数变为0时才会发送FIN。当客户端接收到FIN时,会进入到67行打印server closed,但是执行时却没有打印,也就是说客户端没有接收到FIN,执行结果如下所示:
下面我们将服务器程序的第115行注释掉,换成116行的shutdown函数,当服务器接收到第一个字符是‘2’时,会执行shutdown,这时即使conn的引用计数是2,也还是会发送FIN给客户端,客户端接收到FIN时,会打印出server closed,执行结果如下:
7.2 TCP IP的11种状态的更多相关文章
- TCP/IP协议11种状态
1.l SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发 ...
- (转)TCP连接的11种状态变迁
自:http://blog.csdn.net/engrossment/article/details/8104482 http://blog.csdn.net/xiaofei0859/article/ ...
- [linux] C语言Linux系统编程-TCP通信的11种状态
三次握手由client主动发出SYN请求, 此时client处于SYN_SENT状态(第一次握手)当server收到之后会由LISTEN转变为SYN_REVD状态, 并回复client, client ...
- TCP协议的11种状态及其变化过程?传输的内容又是什么?
在TCP的11种状态变迁中,我们需要用到TCP头部的三个标志位: 1.SYN,SYN=1表示这是一个连接请求报文或者连接接受报文 2.ACK,ACK=1,表示确认号生效 3.FIN,FIN=1表示发送 ...
- TCP连接的11种状态
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的.可靠的.基于字节流的传输层通信协议.TCP协议主要针对三次握手建立连接和四次挥手断开连接,其中包括了 ...
- TCP连接的11种状态,三次握手四次挥手原因
1).LISTEN:首先服务端需要打开一个socket进行监听,状态为LISTEN. /* The socket is listening for incoming connections. 侦听来自 ...
- 【Linux网络基础】TCP/IP协议簇的详细介绍(三次握手四次断开,11种状态)
一.TCP/IP协议簇(DoD参考模型) 用于简化OSI层次,以及相关的标准. 传输控制协议(tcp/ip)簇是相关国防部DoD所创建的,主要用来确保数据的完整性以及在毁灭性战争中维持通信 是由一组不 ...
- python TCP协议详解 三次握手四次挥手和11种状态
11种状态解析 LISTEN -------------------- 等待从任何远端TCP 和端口的连接请求. SYN_SENT --------------- 发送完一个连接请求后等待一个 ...
- TCP之11种状态变迁
1. TCP 之11种状态变迁 TCP 为一个连接定义了 11 种状态,并且 TCP 规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态.如,当某个应用进程在 CLOSED 状 ...
随机推荐
- ant的原理
ANT批量完成项目代码的重新编译.打包.测试.java语言编写与平台无关的. Ant工具 Ant是一种基于Java的build工具.理论上来说,它有些类似于(Unix)C中的make ,但没有make ...
- 文件上传allowedTypes和文件下载contentType(mimeType)
我们在做文件上传和下载时,常常要用到以下mimeType,下面列出来供大家参考参考!希望多顶顶 '.a' : 'application/octet-stream', '.ai' ...
- Python - configParser模块学习
configParser 模块用于操作配置文件 注:Parser汉译为“解析”之意. 配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键= ...
- thinkphp5开发的网站出现”No input file specified”(php版本5.6.27)
thinkphp5开发的网站出现”No input file specified”(php版本5.6.27) 一.总结 一句话总结:搜索引擎一定要用google,比百度节约时间一万倍,google啊, ...
- Xshell Xftp 免费版 (xshell6 评估期已过 解决办法)
xshell6 评估期已过,因为下载的版本是evaluation版本,是有期限的. 大家可以修改为Home and school use 的版本,这样就不会出现这个提示了. 具体的操作步骤如下: 1. ...
- php date()
PHP Date() 函数把时间戳格式化为更易读的日期和时间. date(format,timestamp) format:显示时间的格式. timestamp:可选.规定时间戳.默认是当前时间和日 ...
- 3.3 建立松耦合组件(MVC 模式最重要的特性之一是它支持、关注“分离”)《精通 ASP.NET MVC 5》 推荐指数:8 星半
笔者通常希望应用程序中的组件尽可能独立,而只有很少几个可控的依赖项.—— 在理想情况下,每个组件都不了解其他组件,而只是通过抽象接口来处理应用程序的其他区域.这称为松耦合 .—— 它能够使应用程序更易 ...
- 附录A——面向对象基础
在学习设计模式之前,C#语言中一些基本的面向对象的知识还是应该具备的,比如像继承.多态,接口.抽象类,集合.泛型等. A.2 类与实例 什么是对象? 一切事物(事和物)都是对象,对象就是可以看到.感觉 ...
- Error: [ng:areq] Argument 'LoginCtrl' is not a function, got undefined
- HttpServletRequest解决中文乱码的问题
HTTP请求有get和post,这两中方式解决中文乱码的方式如下: 1.Post方式请求 //这句话是设置post请求体的编码为utf-8 request.setCharacterEncoding(& ...