第一次接觸服務器是快畢業的時候,是不是有點晚(# ̄ω ̄),這也導致工作方向一直沒考慮網絡編程這塊,做了好多其他沒啥“意思”的技術。
之前看到一篇博文提到程序猿80%都是庸才,10%是人才,10%是天才,深有感觸。仔細想想自己是不是也是還在那80%里面掙扎?一個抱怨這抱怨那的trouble maker,寫着爛的掉渣的代碼,永遠在別人身后不思進取,給剩下的20%的同事埋雷。
扯遠了,重新回顧Socket,溫習下Linux內核是怎么處理Socket的吧。
文件描述符,在網絡編程中經常提及這個詞,當時初學時一直就這么叫着,現在回頭看。不過對Linux內核分配的IO的稱謂而已,套接字(Socket)本質上就是文件描述符,為何加上文件兩個字?因為Linux萬物皆文件啊!。在TCP整個通訊過程,有多個文件描述符需要處理。
Listenfd:監聽描述符
Connectfd:請求連接描述符
Accept:接受連接描述符
Read/Write/Recv/Send…:IO描述符(本文不詳細闡述)
服務器建立連接的流程和涉及到的函數:socket()、bind()、listen()、accept()、connect()、close()。
結構體struct sockaddr_in :網絡通訊五元組,本端IP,本端端口、對端IP、對端端口、協議類型。
int socket(int domain, int type, int protocol);創建一個套接字,並且設置該套接字協議類型。
int bind(SOCKET socket, const struct sockaddr* address, socklen_t address_len);為套接字綁定IP和端口。
int listen(int socket, int backlog);listen通過socket套接字和該套接字綁定的IP信息在內核開啟監聽,並且返回監聽描述符。
此處監聽工作交給內核處理,代碼本身不阻塞,但內核對應端口一直在做監聽工作。同時維護兩個連接隊列,大小由backlog決定。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);發送連接請求,代碼默認阻塞。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);接受連接請求,代碼默認阻塞。
accept實際上是在從內核listen維護的就緒隊列中取描述符。
int close(int fd);關閉描述符。listen描述符和accept描述符是完全獨立的,關閉其中一個互不影響。
這里再詳細闡述listen()調用后,內核是如何維護兩個連接隊列的。其實維護的就是熟悉的三次握手過程。
Client請求時間是不確定的,當多個請求到Server時,處於請求隊列,等待listen的端口逐個處理至就緒隊列。
connect處於阻塞態等待請求從listen的就緒隊列被accept調度返回具體用於數據傳輸的accept_fd描述符。
accept處於阻塞態,當請求隊列為空或處理完畢時。
由此可知,三次握手由connet發起,accept結束,途中經歷listen的隊列維護。
下面附上整個流程代碼。
1 /* File Name: server.c */ 2 #include<stdio.h> 3 #include<stdlib.h> 4 #include<string.h> 5 #include<errno.h> 6 #include<sys/types.h> 7 #include<sys/socket.h> 8 #include<sys/unistd.h> 9 #include<netinet/in.h> 10 const int DEFAULT_PORT = 2500; 11 const int BUFFSIZE = 1024; 12 const int MAXLINK = 10; 13 14 int main(int argc, char** argv) 15 { 16 int socket_fd, connect_fd; 17 struct sockaddr_in servaddr; 18 char buff[BUFFSIZE]; 19 20 if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 21 { 22 printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); 23 exit(0); 24 } 25 26 memset(&servaddr, 0, sizeof(servaddr)); 27 servaddr.sin_family = AF_INET; 28 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 29 servaddr.sin_port = htons(DEFAULT_PORT); 30 31 if (bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) 32 { 33 printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); 34 exit(0); 35 } 36 37 if (listen(socket_fd, MAXLINK) == -1) 38 { 39 exit(0); 40 } 41 42 if ((connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1) 43 { 44 exit(0); 45 } 46 47 while(1) 48 { 49 memset(&buff,'\0', sizeof(buff)); 50 51 recv(connect_fd, buff, BUFFSIZE - 1, 0); 52 send(connect_fd, buff, BUFFSIZE - 1, 0); 53 54 printf("recv msg from client: %s\n", buff); 55 } 56 close(connect_fd); 57 close(socket_fd); 58 }
測試結果:
client:
server: