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