一、做為 TCP 伺服器需要具備的條件呢?
1.具備一個可以确知的位址( bind() ):相當于我們要明确知道移動客服的号碼,才能給他們電話;
2.讓作業系統知道是一個伺服器,而不是用戶端( listen() ):相當于移動的客服,他們主要的職責是被動接聽使用者電話,而不是主動打電話騷擾使用者;
3.等待連接配接的到來( accept() ):移動客服時刻等待着,來一個客戶接聽一個。
接收端使用 bind() 函數,來完成位址結構與socket 套接字的綁定,這樣 ip、port 就固定了,發送端即可發送資料給有明确位址( ip+port ) 的接收端。
對于 TCP 伺服器程式設計流程,有點類似于接電話過程:
1.找個可以通話的手機(socket() )
2.插上電話卡固定一個号碼( bind() )
3.職責為被動接聽,給手機設定一個鈴聲來監聽是否有來電( listen())
4. 有來電,确定雙方的關系後,才真正接通不挂電話( accept() )
5. 接聽對方的訴說( recv() )
6.适當給些回話( send() )
7.通信結束後,雙方說再見挂電話( close())。
int bind( int sockfd, const struct sockaddr *myaddr,socklen_t addrlen );
功能:
将本地協定位址與 sockfd 綁定,這樣 ip、port 就固定了
參數:
sockfd:socket 套接字myaddr: 指向特定協定的位址結構指針addrlen:該位址結構的長度
傳回值:
成功:傳回 0
失敗:-1
注意:bind隻能綁定自身的位址及端口
使用執行個體:
// 設定本地位址結構體 struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); // 清空,保證最後8位元組為0 my_addr.sin_family = AF_INET; // ipv4 my_addr.sin_port = htons(port); // 端口 my_addr.sin_addr.s_addr = htonl(INADDR_ANY);// ip,INADDR_ANY為通配位址其值為0 // 綁定 int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if( err_log != 0) { perror("binding"); close(sockfd); exit(-1); }
int listen(int sockfd, int backlog);
功能:
将套接字由主動修改為被動,使作業系統為該套接字設定一個連接配接隊列,用來記錄所有連接配接到該套接字的連接配接。更詳細說明,請看《connect()、listen()和accept()三者的關系》。
參數:
sockfd: socket監聽套接字backlog:連接配接隊列的長度
傳回值:
成功:傳回0失敗:其他
int accept( int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen );
功能:
從已連接配接隊列中取出一個已經建立的連接配接,如果沒有任何連接配接可用,則進入睡眠等待(阻塞)。更詳細說明,請看《connect()、listen()和accept()三者的關系》。
參數:
sockfd: socket監聽套接字cliaddr: 用于存放用戶端套接字位址結構addrlen:套接字位址結構體長度的位址
傳回值:
成功:已連接配接套接字。注意:傳回的是一個已連接配接套接字,這個套接字代表目前這個連接配接失敗:< 0
tcp_server代碼:#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { unsigned short port = 8000; // 本地端口 if(argc > 1) { port = atoi(argv[1]); } //1.建立通信端點:套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { perror("socket"); exit(-1); } //設定本地位址結構體 struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); // 清空,保證最後8位元組為0 my_addr.sin_family = AF_INET; // ipv4 my_addr.sin_port = htons(port); // 端口 my_addr.sin_addr.s_addr = htonl(INADDR_ANY);// ip,INADDR_ANY為通配位址其值為0 //2.綁定:将本地ip、端口與套接字socket相關聯起來 int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if( err_log != 0) { perror("binding"); close(sockfd); exit(-1); } //3.監聽,監聽套接字改為被動,建立連接配接隊列 err_log = listen(sockfd, 10); if(err_log != 0) { perror("listen"); close(sockfd); exit(-1); } printf("listen client @port=%d...\n",port); while(1) { struct sockaddr_in client_addr; char cli_ip[INET_ADDRSTRLEN] = ""; socklen_t cliaddr_len = sizeof(client_addr); int connfd = 0; //4.從完成連接配接隊列中提取用戶端連接配接 connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); if(connfd < 0) { perror("accept"); continue; } inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN); printf("----------------------------------------------\n"); printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port)); char recv_buf[512] = ""; while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 ) // 接收資料 { printf("\nrecv data:\n"); printf("%s\n",recv_buf); } close(connfd); //關閉已連接配接套接字 printf("client closed!\n"); } close(sockfd); //關閉監聽套接字 return 0; }
用windows的網絡調試助手作為用戶端,上面代碼為伺服器
運作結果:
關閉連接配接:close()
使用 close() 函數即可關閉套接字,關閉一個代表已連接配接套接字将導緻另一端接收到一個 0 長度的資料包,詳情請看《 TCP 四次揮手》。
做伺服器時
做用戶端時
- 關閉監聽套接字( socket()和listen()之後的套接字 )将導緻伺服器無法接收新的連接配接,但不會影響已經建立的連接配接;
- 關閉 accept()傳回的已連接配接套接字将導緻它所代表的連接配接被關閉,但不會影響伺服器的監聽( socket()和listen()之後的套接字 )。
關閉連接配接就是關閉連接配接,不意味着其他。如果用戶端和伺服器已經連接配接成功的前提下,通常的情況下,先關閉用戶端,再關閉伺服器,如果是先關閉伺服器,立馬啟動伺服器是,伺服器綁定的端口不會立馬釋放(如下圖),要過 1 分鐘左右才會釋放,為什麼會這樣的呢?請看《 TCP 四次揮手》。有沒有方法讓伺服器每次啟動都能立即成功?請看《端口複用》。 源碼下載下傳: