Socket編程回顧,一個最簡單服務器程序


第一次接觸服務器是快畢業的時候,是不是有點晚(# ̄ω ̄),這也導致工作方向一直沒考慮網絡編程這塊,做了好多其他沒啥“意思”的技術。

之前看到一篇博文提到程序猿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);

創建一個套接字,並且設置該套接字協議類型。

image

 

int bind(SOCKET socket, const struct sockaddr* address, socklen_t address_len);

為套接字綁定IP和端口。

image

 

int listen(int socket, int backlog);

listen通過socket套接字和該套接字綁定的IP信息在內核開啟監聽,並且返回監聽描述符。

此處監聽工作交給內核處理,代碼本身不阻塞,但內核對應端口一直在做監聽工作。同時維護兩個連接隊列,大小由backlog決定。

 
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

發送連接請求,代碼默認阻塞。

image

 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

接受連接請求,代碼默認阻塞。

accept實際上是在從內核listen維護的就緒隊列中取描述符。

 

 

int close(int fd);

關閉描述符。listen描述符和accept描述符是完全獨立的,關閉其中一個互不影響。

image

 

這里再詳細闡述listen()調用后,內核是如何維護兩個連接隊列的。其實維護的就是熟悉的三次握手過程。

image

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:

image

server:

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM