1.TCP状态转换
关于TCP状态转换,自行查阅《UNIX网络编程_卷1_套接字联网API_第3版》第二章第六节。
2.TIME_WAIT状态
关于TIME_WAIT状态,自行查阅《UNIX网络编程_卷1_套接字联网API_第3版》第二章第七节。
server.c:
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SERV_PORT 9000
int main(int argc, char* const* argv)
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int result;
result = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result == -1)
{
char* perrorinfo = strerror(errno);
printf("bind返回的值为%d,错误码为:%d,错误信息为:%s;\n", result, errno, perrorinfo);
return -1;
}
result = listen(listenfd, 32);
if (result == -1)
{
char* perrorinfo = strerror(errno);
printf("listen返回的值为%d,错误码为:%d,错误信息为:%s;\n", result, errno, perrorinfo);
return -1;
}
int connfd;
const char* pcontent = "I sent sth to client!\n";
for (;;)
{
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
write(connfd, pcontent, strlen(pcontent));
printf("本服务器给客户端发送了一串字符~~~~~~~~~~~!\n");
close(connfd);
}
close(listenfd);
return 0;
}
运行服务器程序,用 netstat -anp | grep -E 'State|9000' 命令观察到监听端口一直处在 LISTEN 状态,我们用两个客户端连接到服务器,服务器给每个客户端发送一串字符 “I sent sth to client!\n” 并关闭客户端,虽然这两个连接被 close 掉了,但是产生了两条 TIME_WAIT 状态的信息。
此时,我们杀掉服务器程序再重新启动,就会启动失败,bind() 函数返回失败。
3.SO_REUSEADDR选项
server.c:
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SERV_PORT 9000
int main(int argc, char* const* argv)
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int reuseaddr = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuseaddr, sizeof(reuseaddr)) == -1)
{
char* perrorinfo = strerror(errno);
printf("setsockopt(SO_REUSEADDR)返回值为%d,错误码为:%d,错误信息为:%s;\n", -1, errno, perrorinfo);
}
int result;
result = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (result == -1)
{
char* perrorinfo = strerror(errno);
printf("bind返回的值为%d,错误码为:%d,错误信息为:%s;\n", result, errno, perrorinfo);
return -1;
}
result = listen(listenfd, 32);
if (result == -1)
{
char* perrorinfo = strerror(errno);
printf("listen返回的值为%d,错误码为:%d,错误信息为:%s;\n", result, errno, perrorinfo);
return -1;
}
int connfd;
const char* pcontent = "I sent sth to client!\n";
for (;;)
{
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
write(connfd, pcontent, strlen(pcontent));
printf("本服务器给客户端发送了一串字符~~~~~~~~~~~!\n");
close(connfd);
}
close(listenfd);
return 0;
}
setsockopt(SO_REUSEADDR):用在服务器端,socket() 创建之后,bind() 之前。
SO_REUSEADDR 的能力:
SO_REUSEADDR 允许启动一个监听服务器并捆绑其端口,即使以前建立的将端口用作它们的本地端口的连接仍旧存在。也就是说,即便 TIME_WAIT 状态存在,服务器 bind() 也能成功。SO_REUSEADDR 允许同一个端口上启动同一个服务器的多个实例,只要每个实例捆绑一个不同的本地 IP 地址即可。SO_REUSEADDR 允许单个进程捆绑同一个端口到多个套接字,只要每次捆绑指定不同的本地 IP 地址即可。SO_REUSEADDR 允许完全重复的绑定:当一个 IP 地址和端口已经绑定到某个套接字上时,如果传输协议支持,同样的 IP 地址和端口还可以绑定到另一个套接字上,一般来说本特性仅支持 UDP 套接字。- 所有
TCP 服务器都应该指定本套接字选项,以防止当套接字处于 TIME_WAIT 时 bind() 失败的情形出现。
|