1、概述
下面2个问题你会怎么回答呢?
1、bind如果绑定0号端口,可以工作么,如果能正常工作,绑定的什么端口
2、客户端可以调用bind么
2、解析
2.1、bind如果绑定0号端口,可以工作么,如果能正常工作,绑定的什么端口
是可以工作的;当使用bind绑定0号端口时,系统会自动选择一个未被占用的临时端口,通常从32768~60999范围内选择。如果程序显示指定非0端口进行绑定,则使用指定端口监听。
例如:select_server程序绑定0号端口,俩次运行分别被分配了34359、43959端口
2.2、客户端可以调用bind么
可以调用bind,并没有规定只有服务端使用bind。
客户端连接服务器,若未显示调用bind,系统会自动为其分配一个临时端口。
客户端连接服务器,若显示调用bind,会使用指定端口和服务端通信。
例如:
1、用nc命令模仿客户端,没有调用bind绑定端口,连接select_server
2、用nc命令模仿客户端,显示调用bind绑定端口,连接select_server
2.3、原理
socket由2部分组成
1、文件描述符fd(int型)
2、内核对象:tcb(包含源ip、源端口号、目标ip、目标端口、协议等信息)
文件描述符fd 和 tcb一一对应
bind作用:通过fd找到内核对象tcb,设置ip、port
2.4、select_server代码
有需要可以下载代码,自己本地编译试试
编译:gcc -o select_server select_main.c
#define _GNU_SOURCE#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <arpa/inet.h>#define INVALID_HANDLE_VALUE (-1)
#define LISTEN_BACKLOG (1024)int main()
{int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);if(sockfd == INVALID_HANDLE_VALUE){perror("socket creation failed");return -1;}int opt = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 绑定struct sockaddr_in servAddr;servAddr.sin_family = AF_INET;// servAddr.sin_addr.s_addr = inet_addr("192.168.202.223");servAddr.sin_addr.s_addr = htonl(INADDR_ANY);servAddr.sin_port = htons(0);if(-1 == bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr))){perror("bind error");close(sockfd);return -1;}// 监听if(-1 == listen(sockfd, LISTEN_BACKLOG)){perror("listen error");close(sockfd);return -1;}// 获取监听端口struct sockaddr_in bound_addr;socklen_t len = sizeof(bound_addr);if(getsockname(sockfd, (struct sockaddr*)&bound_addr, &len) == 0){printf("server bound port:%d\n", ntohs(bound_addr.sin_port));}else{printf("getsockname failed\n");}// select模型int maxfd = sockfd;fd_set rfds, temp_fds;FD_ZERO(&rfds);FD_SET(sockfd, &rfds);while(1){temp_fds = rfds;int nready = select(maxfd + 1, &temp_fds, NULL, NULL, NULL);if(nready < 0 && errno != EINTR){break;}// 检查新连接if(FD_ISSET(sockfd, &temp_fds)){struct sockaddr_in client_addr;socklen_t addrlen = sizeof(client_addr);int client_fd = accept4(sockfd, (struct sockaddr*)&client_addr, &addrlen, SOCK_NONBLOCK);if(client_fd < 0){if(errno == EAGAIN || errno == EWOULDBLOCK){printf("Error %s\n", strerror(errno));}else{printf("accept4 failed %s\n", strerror(errno));}}else{if(client_fd > maxfd){maxfd = client_fd;}FD_SET(client_fd, &rfds); // 打印客户端信息printf("client fd:%d ip:%s port:%d\n", client_fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));}}// 检查已连接的客户端是否有数据for(int sock_client = sockfd + 1; sock_client <= maxfd; ++sock_client){if(FD_ISSET(sock_client, &temp_fds)){char buf[1024] = {0};int n = recv(sock_client, buf, sizeof(buf), 0);if( n < 0){// 发生错误,判断errno是否为EAGAIN EWOULDBLOCKif(errno != EAGAIN && errno != EWOULDBLOCK){printf("Client on fd %d disconnected\n", sock_client);FD_CLR(sock_client, &rfds);close(sock_client);}}else if (n == 0){// 对端关闭FD_CLR(sock_client, &rfds);close(sock_client);continue;}else{// 收到数据buf[n] = '\0';printf("recv data:%s\n", buf);send(sock_client, buf, n, 0);}}}}// 关闭套接字for(int i = sockfd; i <= maxfd; ++i){close(i);}return 0;
}
学习链接:https://github.com/0voice