socket 问题: 连续bind/listen 同一个socket fd
作者:互联网
如果我们想尝试哪个端口号可用,然后监听该端口,该如何处理?比如FTP协议里面有这样一个需求,PASV模式下,Server需要监听本地数据端口,通常是找一个随机端口号进行监听。而且每收到一个客户PASV命令后,就需要提供一个不同的数据端口,这也就是说,不能两个连接同时使用同一个数据端口。那么,我们如何确定一个可用的本地端口号呢?
我们知道,正常的监听本地端口流程是:socket(), bind(), listen()。如果从socket到listen,监听一个端口号,都调用成功,说明该端口号可用。因此,我们可以用这种方法创建一个可用的socket fd。
#include <unistd.h>
#include <errno.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
using namespace std;
typedef sockaddr SA;
int socket_create(const char* ip, const int port)
{
if (ip == NULL || port < 0) {
return -1;
}
/* create socket */
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket error");
return -1;
}
/* set sock option SO_REUSEADDR for re-bind when calling socket failed */
int op = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(static_cast<uint16_t>(port));
local.sin_addr.s_addr = inet_addr(ip);
if (bind(sock, reinterpret_cast<SA*>(&local), sizeof(local)) < 0) {
perror("bind error");
return -1;
}
if (listen(sock, 10) < 0) {
perror("listen error");
return -1;
}
return sock;
}
但如果失败了,怎么办?
我们不用关心是哪一步失败,而是直接跳过该端口,尝试监听下一个端口,直到成功为止。
注意:知名端口号范围01023;应用程序能用的动态端口号范围102465535。如果非专用应用程序,通常只能用动态端口号,而且在Linux下使用知名端口号,需要root权限。
/**
* 打开数据连接,尝试在指定端口范围[start_port, end_port]中,找一个可用端口号并监听之
*/
int ftp_create_datasocket(const char *ip, int start_port, int end_port)
{
int listenfd = -1;
int i;
if (start_port < 1024) start_port = 1024;
if (end_port >= 65535) end_port = 65535;
if (start_port > end_port) return -1;
for (i = start_port; i <= end_port; ++i) {
if ((listenfd = socket_create(ip, i)) != -1) /* success */
break;
}
return listenfd;
}
到这里,我们最开始的问题解决了。但由此让人不得不思考另外几个问题:如果本地端口已经被同一个进程占用,到底是哪一步会失败?或者说,我们有一个sockfd,如果连续多次bind、listen,到底是哪一步会失败?
下面用几组实验来验证。
首先,是对通过一个socket fd调用bind 2次,看是否报错,以及报错信息。
int test_bindTwoTimes(const char* ip, const int port)
{
if (ip == NULL || port < 0) {
return -1;
}
/* create socket */
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket error");
return -1;
}
/* set sock option SO_REUSEADDR for re-bind when calling socket failed */
int op = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(static_cast<uint16_t>(port));
local.sin_addr.s_addr = inet_addr(ip);
if (bind(sock, reinterpret_cast<SA*>(&local), sizeof(local)) < 0) { /* first time */
perror("bind1 error");
return -1;
}
if (bind(sock, reinterpret_cast<SA*>(&local), sizeof(local)) < 0) { /* second time */
perror("bind2 error");
return -1;
}
if (listen(sock, 10) < 0) {
perror("bind error");
return -1;
}
return sock;
}
接着,对同一个socket fd调用listen 2次,看是否报错及报错信息。
int test_listenTwoTimes(const char* ip, const int port)
{
if (ip == NULL || port < 0) {
return -1;
}
/* create socket */
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket error");
return -1;
}
/* set sock option SO_REUSEADDR for re-bind when calling socket failed */
int op = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(static_cast<uint16_t>(port));
local.sin_addr.s_addr = inet_addr(ip);
if (bind(sock, reinterpret_cast<SA*>(&local), sizeof(local)) < 0) {
perror("bind error");
return -1;
}
if (listen(sock, 10) < 0) {
perror("listen1 error");
return -1;
}
if (listen(sock, 10) < 0) {
perror("listen2 error");
return -1;
}
return sock;
}
测试用例:
分为参照组,实验组:连续bind 2次、连续listen 2次。
为每个实验组设置2个测试用例,是为了观察不同端口号是否有影响。
int main()
{
printf("Test on different socket fd\n");
{// 参照组
printf("Test Case: background\n");
const char* ip = "0.0.0.0";
int port = 1025; /* 使用well-known端口号(0~1023)需要root权限*/
int sockfd = socket_create(ip, port);
if (sockfd >= 0) {
printf("Success to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
else {
printf("Fail to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
close(sockfd);
}
{// 连续bind 2次
printf("Test Case1: bind two times on same sockfd\n");
const char* ip = "0.0.0.0";
int port = 1025;
int sockfd = test_bindTwoTimes(ip, port);
if (sockfd >= 0) {
printf("Success to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
else {
printf("Fail to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
close(sockfd);
}
{// 连续bind 2次
printf("Test Case2: bind two times on same sockfd\n");
const char* ip = "0.0.0.0";
int port = 65536;
int sockfd = test_bindTwoTimes(ip, port);
if (sockfd >= 0) {
printf("Success to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
else {
printf("Fail to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
close(sockfd);
}
{// 连续listen 2次
printf("Test Case1: listen two times on same sockfd\n");
const char* ip = "0.0.0.0";
int port = 1025;
int sockfd = test_listenTwoTimes(ip, port);
if (sockfd >= 0) {
printf("Success to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
else {
printf("Fail to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
close(sockfd);
}
{// 连续listen 2次
printf("Test Case2: listen two times on same sockfd\n");
const char* ip = "0.0.0.0";
int port = 65536;
int sockfd = test_listenTwoTimes(ip, port);
if (sockfd >= 0) {
printf("Success to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
else {
printf("Fail to create sockfd %d on %s:%d\n", sockfd, ip, port);
}
close(sockfd);
}
return 0;
}
实验结果:
Test on different socket fd
Test Case: background
Success to create sockfd 3 on 0.0.0.0:1025
Test Case1: bind two times on same sockfd
bind2 error: Invalid argument
Fail to create sockfd -1 on 0.0.0.0:1025
Test Case2: bind two times on same sockfd
bind2 error: Invalid argument
Fail to create sockfd -1 on 0.0.0.0:65536
Test Case1: listen two times on same sockfd
Success to create sockfd 5 on 0.0.0.0:1025
Test Case2: listen two times on same sockfd
Success to create sockfd 5 on 0.0.0.0:65536
从实验结果,我们可以看到,连续bind同一个端口号,失败的是第二次bind;而listen不受此影响,两次都成功。
标签:socket,int,bind,fd,ip,sockfd,port 来源: https://www.cnblogs.com/fortunely/p/16367542.html