linux c 網路程式設計

lm_y發表於2017-09-09

OSI七層網路模型由下至上為1至7層,分別為:

物理層(Physical layer),資料鏈路層(Data link layer),網路層(Network layer),傳輸層(Transport layer),會話層(Session layer),表示層(Presentation layer),應用層(Application layer)
1.1 應用層,很簡單,就是應用程式。這一層負責確定通訊物件,並確保由足夠的資源用於通訊,這些當然都是想要通訊的應用程式乾的事情。 
1.2 表示層,負責資料的編碼、轉化,確保應用層的正常工作。這一層,是將我們看到的介面與二進位制間互相轉化的地方,就是我們的語言與機器語言間的轉化。資料的壓縮、解壓,加密、解密都發生在這一層。這一層根據不同的應用目的將資料處理為不同的格式,表現出來就是我們看到的各種各樣的副檔名。 
1.3 會話層,負責建立、維護、控制會話,區分不同的會話,以及提供單工(Simplex)、半雙工(Half duplex)、全雙工(Full duplex)三種通訊模式的服務。我們平時所知的NFS,RPC,X Windows等都工作在這一層。 
1.4 傳輸層,負責分割、組合資料,實現端到端的邏輯連線。資料在上三層是整體的,到了這一層開始被分割,這一層分割後的資料被稱為段(Segment)。三次握手(Three-way handshake),面向連線(Connection-Oriented)或非面向連線(Connectionless-Oriented)的服務,流控(Flow control)等都發生在這一層。 
1.5 網路層,負責管理網路地址,定位裝置,決定路由。我們所熟知的IP地址和路由器就是工作在這一層。上層的資料段在這一層被分割,封裝後叫做包(Packet),包有兩種,一種叫做使用者資料包(Data packets),是上層傳下來的使用者資料;另一種叫路由更新包(Route update packets),是直接由路由器發出來的,用來和其他路由器進行路由資訊的交換。 
1.6 資料鏈路層,負責準備物理傳輸,CRC校驗,錯誤通知,網路拓撲,流控等。我們所熟知的MAC地址和交換機都工作在這一層。上層傳下來的包在這一層被分割封裝後叫做幀(Frame)。 
1.7 物理層,就是實實在在的物理鏈路,負責將資料以位元流的方式傳送、接收,就不多說了。 


3.2,TCP握手

3.2.1 三次握手建立連線

在TCP/IP協議中,TCP協議提供可靠的連線服務,採用三次握手建立一個連線。
1,客戶端向伺服器傳送一個SYN J
2,伺服器向客戶端響應一個SYN K,並對SYN J進行確認ACK J+1
3,客戶端再想伺服器發一個確認ACK K+1


從圖中可以看出,當客戶端呼叫connect時,觸發了連線請求,向伺服器傳送了SYN J包,這時connect進入阻塞狀態;伺服器監聽到連線請求,即收到SYN J包,呼叫accept函式接收請求向客戶端傳送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到伺服器的SYN K ,ACK J+1之後,這時connect返回,並對SYN K進行確認;伺服器收到ACK K+1時,accept返回,至此三次握手完畢,連線建立。

三次握手的目的是建立雙向的連線,第一次握手是客戶端向伺服器端發出請求 
第二次握手是伺服器端告訴客戶端,第一次握手是成功的,即可以從客戶端傳送到客戶端, 
第三次握手是客戶端告訴伺服器端,第二次握手是成功的,即可以從客戶端到伺服器端 
這樣就保證了客戶端和伺服器端的雙向通訊,

3.2.2 四次握手釋放連線


1,某個應用程式首先呼叫close主動關閉連線,這時TCP傳送一個FIN M;
2,另一端接收到FIN M之後,執行被動關閉,對這個FIN進行確認。它的接收也作為檔案結束符傳遞給應用程式,因為FIN的接收意味著應用程式在相應的連線上再也接收不到額外資料;
3,一段時間之後,接收到檔案結束符的應用程式呼叫close關閉它的socket。這導致它的TCP也傳送一個FIN N;
4,接收到這個FIN的源傳送端TCP對它進行確認。

3.3,socket程式設計

http://www.cnblogs.com/goodcandle/archive/2005/12/10/socket.html

先從伺服器端說起。伺服器端先初始化Socket,然後與埠繫結(bind),對埠進行監聽(listen),呼叫accept阻塞,等待客戶端連線。在這時如果有個客戶端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時客戶端與伺服器端的連線就建立了。客戶端傳送資料請求,伺服器端接收請求並處理請求,然後把迴應資料傳送給客戶端,客戶端讀取資料,最後關閉連線,一次互動結束。

3.3.1 AF_INET TCP傳輸最簡單版本


tcp_client.c

  1. #include <unistd.h>  
  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 <netinet/in.h>  
  9.   
  10. #define PORT 6666  
  11. #define BUFF_SIZE 1024  
  12. #define MAXLEN 4096  
  13.   
  14. main(int argc, char** argv)  
  15. {  
  16.     if(argc!=2){  
  17.         printf("usage: ./client <ipaddress>\n");  
  18.         exit(0);  
  19.     }  
  20.       
  21.     char sendline[4096];  
  22.       
  23.     //socket()建立socket  
  24.     int sockfd = socket(AF_INET,SOCK_STREAM,0);  
  25.   
  26.     //要連線的伺服器地址  
  27.     struct sockaddr_in sliaddr;  
  28.     sliaddr.sin_family = AF_INET;  
  29.     sliaddr.sin_port = htons(PORT);  
  30.     inet_pton(AF_INET, argv[1], &sliaddr.sin_addr);  
  31.   
  32.     //connect()傳送請求(ip=argv[1],protocal=TCP,port=6666)  
  33.     connect(sockfd, (struct sockaddr*)&sliaddr, sizeof(sliaddr));  
  34.   
  35.     //recv sth  
  36.     recv_len = recv(sockfd, buff, sizeof(buff), 0);  
  37.     buff[recv_len] = '\0';  
  38.     printf(" %s ", buff);  
  39.    
  40.     //interactive  
  41.     while (1)  
  42.     {  
  43.         printf("Enter string to send: ");  
  44.         scanf("%s", buff);  
  45.         if (!strcmp(buff, "quit"))  
  46.             break;  
  47.            
  48.         send_len = send(sockfd, buff, strlen(buff), 0);  
  49.         recv_len = recv(sockfd, buff, BUFF_SIZE, 0);  
  50.         buff[recv_len] = '\0';  
  51.         printf("    received: %s \n", buff);  
  52.     }  
  53.   
  54.     //close()關閉連線  
  55.     close(sockfd);  
  56. }  

tcp_server.c

  1. #include <unistd.h>  
  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 <netinet/in.h>  
  9.   
  10. #define PORT 6666  
  11. #define WAIT_QUEUE_LEN 5  
  12. #define BUFF_SIZE 1024  
  13. #define WELCOME "Welcome to my server ^_^!\n"  
  14.   
  15. main()  
  16. {  
  17.     int connfd;  
  18.     char buff[MAXLEN];  
  19.     int len;  
  20.   
  21.     //socket() 建立socket,其中SOCK_STREAM表示tcp連線  
  22.     int sockfd = socket(AF_INET,SOCK_STREAM,0);  
  23.       
  24.     struct sockaddr_in servaddr;  
  25.     servaddr.sin_family = AF_INET;  
  26.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  27.     servaddr.sin_port = htons(PORT);  
  28.       
  29.     //bind()繫結一個socket(ip=all,protocal=TCP,port=6666)  
  30.     bind(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr));  
  31.   
  32.     //listen()監聽  
  33.     listen(sockfd, WAIT_QUEUE_LEN);  
  34.   
  35.     //accept() & close()  
  36.     printf("======waiting for client's request======\n");  
  37.     while(1){  
  38.         c_addrlen = sizeof(struct sockaddr_in);  
  39.         connfd = accept(serverfd, (struct sockaddr *)&caddr, &c_addrlen);  
  40.         printf("client: ip=%s,port=%s\n", cliaddr.sin_addr.s_addr,cliaddr.sin_port);  
  41.           
  42.         //send a welcome  
  43.         send(connfd, WELCOME, strlen(WELCOME), 0);  
  44.        
  45.         //阻塞模式下recv==0表示客戶端已斷開連線  
  46.         while ((len = recv(connfd, buff, BUFF_SIZE, 0)) > 0)  
  47.         {  
  48.             buff[len] = '\0';  
  49.             printf("recv msg is : %s \n", buff);  
  50.             send(connfd, buff, len, 0);  
  51.         }  
  52.           
  53.         close(connfd);  
  54.     }  
  55.   
  56.     //close()關閉連線  
  57.     close(sockfd);  
  58. }  
阻塞與非阻塞recv返回值沒有區分,都是
<0 出錯
=0 連線關閉
>0 接收到資料大小,

makefile

  1. .PHONY : main  
  2. main : server client  
  3. server : server.o  
  4.         gcc -g -o server server.o   
  5. client : client.o  
  6.         gcc -g -o client client.o   
  7. server.o : server.c  
  8.         gcc -g -c server.c  
  9. client.o : client.c  
  10.         gcc -g -c client.c  
  11. clean :   
  12.         rm -rf *.o  
  13. ser :  
  14.         ./server  
  15. cli :  
  16.         ./client  

3.3.2 加入返回值檢查和IP地址

tcp_client.c

  1. #include <unistd.h>  
  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 <netinet/in.h>  
  9.   
  10. #define MAXLEN 4096  
  11.   
  12. main(int argc, char** argv)  
  13. {  
  14.     if(argc!=2){  
  15.         printf("usage: ./client <ipaddress>\n");  
  16.         exit(0);  
  17.     }  
  18.       
  19.     char sendline[4096];  
  20.       
  21.     //socket()建立socket  
  22.     int sockfd;  
  23.     if((sockfd=socket(AF_INET,SOCK_STREAM,0)) ==-1){  
  24.         printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);  
  25.     }  
  26.   
  27.     struct sockaddr_in cliaddr;  
  28.     cliaddr.sin_family = AF_INET;  
  29.     cliaddr.sin_port = htons(6666);  
  30.     if(inet_pton(AF_INET, argv[1], &cliaddr.sin_addr)==-1){  
  31.         printf("inet_pton error for %s\n",argv[1]);  
  32.         exit(0);  
  33.     }  
  34.   
  35.     //connect()傳送請求(ip=argv[1],protocal=TCP,port=6666)  
  36.     if(connect(sockfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr))==-1){  
  37.         printf("connect error: %s(errno: %d)\n",strerror(errno),errno);  
  38.         exit(0);  
  39.     }  
  40.   
  41.     printf("send msg to server: \n");  
  42.     fgets(sendline, 4096, stdin);  
  43.   
  44.     //send()傳送資料  
  45.     if(send(sockfd, sendline, strlen(sendline), 0)==-1){  
  46.         printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);  
  47.         exit(0);  
  48.     }  
  49.   
  50.     //close()關閉連線  
  51.     close(sockfd);  
  52. }  

tcp_server.c
  1. #include <unistd.h>  
  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 <netinet/in.h>  
  9.   
  10. #define MAXLEN 4096  
  11.   
  12. main()  
  13. {  
  14.     int connfd;  
  15.     char buff[MAXLEN];  
  16.     int n;  
  17.   
  18.     //socket()建立socket  
  19.     int sockfd;  
  20.     if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){  
  21.         printf("create socket error: %s(errno:%d)\n",strerror(errno),errno);  
  22.         exit(0);  
  23.     }  
  24.       
  25.     struct sockaddr_in servaddr;  
  26.     servaddr.sin_family = AF_INET;  
  27.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  28.     servaddr.sin_port = htons(6666);  
  29.       
  30.     //bind()繫結一個socket(ip=all,protocal=TCP,port=6666)  
  31.     if(bind(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr))==-1){  
  32.         printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
  33.         exit(0);  
  34.     }  
  35.   
  36.     //listen()監聽  
  37.     if(listen(sockfd,10)==-1){  
  38.         printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);  
  39.         exit(0);  
  40.     }  
  41.   
  42.     //accept() & close()  
  43.     printf("======waiting for client's request======\n");  
  44.     while(1){  
  45.         if((connfd=accept(sockfd, (struct sockaddr *)NULL,NULL))==-1){  
  46.             printf("accept socket error: %s(errno: %d)",strerror(errno),errno);  
  47.             continue;  
  48.         }  
  49.         struct sockaddr_in serv, guest;  
  50.         char serv_ip[20];  
  51.         char guest_ip[20];  
  52.         int serv_len = sizeof(serv);  
  53.         int guest_len = sizeof(guest);  
  54.         getsockname(connfd, (struct sockaddr *)&serv, &serv_len);  
  55.         getpeername(connfd, (struct sockaddr *)&guest, &guest_len);  
  56.         inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip));  
  57.         inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip));  
  58.         printf("host %s:%d guest %s:%d\n", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port));  
  59.         n = recv(connfd, buff, MAXLEN,0);  
  60.         buff[n] = '\0';  
  61.         printf("recv msg from client: %s\n", buff);  
  62.         close(connfd);  
  63.     }  
  64.   
  65.     //close()關閉連線  
  66.     close(sockfd);  
  67. }  

3.3.3 AF_INET UDP傳輸最簡單版本


udp_client.c

  1. /** 
  2. *   @file: udpclient.c 
  3. *   @brief: A simple Udp server 
  4. *   @author: ToakMa <mchgloak1120@163.com> 
  5. *   @date:  2014/10/09 
  6. */  
  7.    
  8. #include <stdio.h>  
  9. #include <stdlib.h>  
  10. #include <string.h>  
  11. #include <strings.h>  
  12. #include <sys/types.h>  
  13. #include <sys/socket.h>  
  14. #include <netinet/in.h>  
  15. #include <arpa/inet.h>  
  16.    
  17. #define BUFF_SIZE 1024  
  18. #define PORT     9988  
  19.    
  20. int main(int argc, char *argv[])  
  21. {  
  22.     int sockfd;  
  23.     struct sockaddr_in remote_addr;  
  24.     int len;  
  25.     char buff[BUFF_SIZE];  
  26.    
  27.     //1. create a socket  
  28.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  29.     if (-1 == sockfd)  
  30.     {  
  31.         perror("udp client socket: ");  
  32.         return -1;  
  33.     }  
  34.        
  35.     //2. prepare ip and port  
  36.     memset(&remote_addr, 0, sizeof(remote_addr));  
  37.     remote_addr.sin_family = AF_INET;  
  38.     remote_addr.sin_port   = htons(PORT);  
  39.     remote_addr.sin_addr.s_addr = inet_addr(argv[1]);  
  40.     bzero(&(remote_addr.sin_zero), 8);  
  41.        
  42.     //3. sendto  
  43.     strcpy(buff, "this a test\n");  
  44.     printf("sending : %s\n", buff);  
  45.     len = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr));  
  46.     if (len < 0)  
  47.     {  
  48.         perror("udp client sendto :");  
  49.         return -1;  
  50.     }  
  51.        
  52.     //4. close  
  53.     close(sockfd);  
  54.    
  55.     return 0;  
  56. }  



udp_server.c

  1. /** 
  2. *   @file: udpserver.c 
  3. *   @brief: A simple Udp server 
  4. *   @author: ToakMa <mchgloak1120@163.com> 
  5. *   @date:  2014/10/09 
  6. */  
  7. #include <stdlib.h>  
  8. #include <stdio.h>  
  9. #include <string.h>  
  10. #include <strings.h>  
  11. #include <sys/types.h>  
  12. #include <sys/socket.h>  
  13. #include <netinet/in.h>  
  14. #include <arpa/inet.h>  
  15.    
  16. #define PORT 9988  
  17. #define BUFF_SIZE 1024  
  18.    
  19. int main(int argc, char *argv[])  
  20. {  
  21.     int sockfd;  
  22.     int sin_len;  
  23.     struct sockaddr_in saddr;  
  24.     struct sockaddr_in remote_addr;  
  25.     char buff[BUFF_SIZE];     
  26.     int res, len;  
  27.    
  28.     //1. create socket  
  29.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  30.     if (-1 == sockfd)  
  31.     {  
  32.         perror("Udp server socket: ");  
  33.         return -1;  
  34.     }  
  35.     printf("Udp server socket create succ!\n");  
  36.    
  37.     //2. prepare IP and port  
  38.     memset(&saddr, 0, sizeof(saddr));  
  39.     saddr.sin_family = AF_INET;  
  40.     saddr.sin_port   = htons(PORT);  
  41.     saddr.sin_addr.s_addr = INADDR_ANY;  
  42.     bzero(saddr.sin_zero, 8);  
  43.    
  44.     //3. bind  
  45.     res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));  
  46.     if (-1 == res)  
  47.     {  
  48.         perror("udp server bind: ");  
  49.         return -1;  
  50.     }  
  51.        
  52.     //4. recvfrom  
  53.     printf("Wait for a packet ...\n");  
  54.     sin_len = sizeof(struct sockaddr_in);  
  55.     len = recvfrom(sockfd, buff, BUFF_SIZE, 0, (struct sockaddr *)&remote_addr, &sin_len);  
  56.     if (-1 == len)  
  57.     {  
  58.         perror("udp server recvform: ");  
  59.         return -1;  
  60.     }  
  61.     buff[len] = '\0';  
  62.    
  63.     printf("Recived packet from %s, contents is: %s \n", \  
  64.         inet_ntoa(remote_addr.sin_addr), buff);  
  65.    
  66.    
  67.     //5. close  
  68.     close(sockfd);  
  69.    
  70.     return 0;  
  71. }  


3.3.4 AF_INET UNIX本地傳輸最簡單版本

unix_client.c

  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <stdio.h>  
  4. #include <sys/un.h>  
  5. #include <unistd.h>  
  6. #include <errno.h>   
  7.   
  8. int main()  
  9. {  
  10.       
  11.     int result;  
  12.     char ch = 'A';  
  13.   
  14.     //建立socket  
  15.     int sockfd;  
  16.     if((sockfd = socket(AF_UNIX,SOCK_STREAM,0) == -1){  
  17.         printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);  
  18.     }  
  19.   
  20.     struct sockaddr_un address;  
  21.     address.sun_family = AF_UNIX;  
  22.     strcpy(address.sun_path,"server_socket");  
  23.     int len = sizeof(address);  
  24.   
  25.     //connect()  
  26.     int result;  
  27.     if((result = connect(socket,(struct sockaddr*)&address,len)==-1){  
  28.         printf("connect error: %s(errno: %d)\n",strerror(errno),errno);    
  29.         exit(0);    
  30.     }  
  31.   
  32.     //read & write  
  33.     write(sockfd,&ch,1);  
  34.     read(sockfd,&ch,1);  
  35.     printf("char from server = %c\n",ch);  
  36.   
  37.     //close()  
  38.     close(sockfd);  
  39.     exit(0);  
  40. }  

unix_server.c
  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <stdio.h>  
  4. #include <sys/un.h>  
  5. #include <unistd.h>  
  6. #include <errno.h>   
  7.   
  8. int main()  
  9. {  
  10.     int server_len,client_len;  
  11.     struct socket_un server_address;  
  12.     struct socket_un client_address;  
  13.   
  14.     //刪除以前套接字  
  15.     unlink("server_socket");  
  16.   
  17.     //socket  
  18.     int server_sockfd = socket(AF_UNIX,SOCK_STREAM,0);  
  19.   
  20.     server_address.sun_family = AF_UNIX;  
  21.     strcpy(server_address.sun_path,"server_socket");  
  22.     server_len = sizeof(server_address);  
  23.   
  24.     //bind()  
  25.     bind(server_sockfd,(struct sockaddr*)&server_address,server_len);  
  26.   
  27.     //listen()  
  28.     listen(server_sockfd,5);  
  29.     while(1){  
  30.         char ch;  
  31.         printf("server waiting\n");  
  32.         client_len = sizeof(client_address);  
  33.         client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address,&client_len);  
  34.   
  35.         //read & write  
  36.         read(client_sockfd,&ch,1);  
  37.         ch++;  
  38.         write(client_sockfd,&ch,1);  
  39.         close(client_sockfd);  
  40.     }  
  41. }  


3.4 網路程式設計函式

Unix/Linux基本哲學之一就是“一切皆檔案”,都可以用“開啟open –> 讀寫write/read –> 關閉close”

  1. #include<sys/types.h>  
  2. #include<sys/socket.h>  

3.4.1 函式socket

建立套接字:socket函式對應於普通檔案的開啟操作。普通檔案的開啟操作返回一個檔案描述字,而socket()用於建立一個socket描述符(socket descriptor),它唯一標識一個socket。
  1. 函式原型:  
  2. int socket(int domain, int type, int protocol);  
  3. 引數說明:  
  4. domain:即協議域。常用的協議族有,AF_INET(ipv4)、AF_INET6(ipv6)、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。  
  5. type:指定socket型別。常用的socket型別有,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。  
  6. protocol:指定協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。  
  7. int:返回值為-1表示出錯  
注意:並不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當protocol為0時,會自動選擇type型別對應的預設協議。

3.4.2 函式bind

命名套接字:bind()函式把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和埠號組合賦給socket。

  1. 函式原型:  
  2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  
  3. 引數說明:  
  4. sockfd:是由socket()呼叫返回的套介面檔案描述符。  
  5. addr:傳入資料結構sockaddr的指標,包括(IP,protocol,port)需要轉換為通用地址型別struct sockaddr*  
  6. addrlen:以設定成sizeof(struct sockaddr)  
  7. int:返回值,-1表示出錯  
  1. ipv4地址結構【AF_INET】:  
  2. struct sockaddr_in {  
  3.     sa_family_t    sin_family; /* address family: AF_INET */  
  4.     in_port_t      sin_port;   /* port in network byte order */  
  5.     struct in_addr sin_addr;   /* internet address */  
  6. };  
  7.   
  8. /* Internet address. */  
  9. struct in_addr {  
  10.     uint32_t       s_addr;     /* address in network byte order */  
  11. };  
ipv6地址結構【AF_INET6】:
  1. struct sockaddr_in6 {   
  2.     sa_family_t     sin6_family;   /* AF_INET6 */   
  3.     in_port_t       sin6_port;     /* port number */   
  4.     uint32_t        sin6_flowinfo; /* IPv6 flow information */   
  5.     struct in6_addr sin6_addr;     /* IPv6 address */   
  6.     uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */   
  7. };  
  8.   
  9. struct in6_addr {   
  10.     unsigned char   s6_addr[16];   /* IPv6 address */   
  11. };  
Unix域地址結構【AF_UNIX】:
  1. #define UNIX_PATH_MAX    108  
  2.   
  3. struct sockaddr_un {   
  4.     sa_family_t sun_family;               /* AF_UNIX */   
  5.     char        sun_path[UNIX_PATH_MAX];  /* pathname */ 使用strcpy(address.sun_path,"server_socket")   
  6. };  
通常伺服器在啟動的時候都會繫結一個眾所周知的地址(如ip地址+埠號),用於提供服務,客戶就可以通過它來接連伺服器;所以伺服器端在listen之前會呼叫bind()。

而客戶端就不用指定,有系統自動分配一個埠號和自身的ip地址組合,在connect()時由系統隨機生成一個。由於客戶端不需要固定的埠號,因此不必呼叫bind(),客戶端的埠號由核心自動分配。注意,客戶端不是不允許呼叫bind(),只是沒有必要呼叫
bind()固定一個埠號,伺服器也不是必須呼叫bind(),但如果伺服器不呼叫bind(),核心會自動給伺服器分配監聽埠,每次啟動伺服器時埠號都不一樣,客戶端要連線伺服器就會遇到麻煩


對server端的addr的初始化如下所示:

  1. memset(&servaddr, 0, sizeof(servaddr));  
  2. servaddr.sin_family = AF_INET;  
  3. servaddr.sin_port = htons(5188);  
  4. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
首先將整個結構體清零(也可以用bzero函式),然後設定地址型別為AF_INET,網路地址為INADDR_ANY,這個巨集表示本地的任意IP地址,因為伺服器可能有多個網路卡,每個網路卡也可能繫結多個IP地址,這樣設定可以在所有的IP地址上監聽,直到與某個客戶端建立了連線時才確定下來到底用哪個IP地址,埠號為5188。

3.4.3 函式listen

建立套接字佇列
  1. 函式原型:  
  2. int listen(int sockfd, int backlog);  
  3. 引數說明:  
  4. sockfd:即為要監聽的socket描述字  
  5. backlog:相應socket可以排隊的最大連線個數  

第二個引數是進入佇列中允許的連線的個數。進入的連線請求在使用系統呼叫accept()應答之前要在進入佇列中等待。這個值是佇列中最多可以擁有的請求的個數。大多數系統的預設設定為20。你可以設定為5或者10。當出錯時,listen()將會返回-1值。

這個函式對於backlog的解釋《unix網路程式設計》P85是說已完成連線佇列(ESTABLISHED)和未完成連線佇列(SYN_RCVD)之和(即等待連線數而非連線數)。伺服器呼叫的accept()返回並接受這個連線,如果有大量的客戶端發起連線而伺服器來不及處理,尚未accept的客戶端就處於連線等待狀態,listen()宣告sockfd處於監聽狀態,並且最多允許有backlog個客戶端處於連線等待狀態,如果接收到更多的連線請求就忽略。在計算機早期由於網路連線的處理速度很慢,幾個併發的請求就可能導致系統處理不過來而引發錯誤,現在這個數字的意義已經不大了,現在軟硬體效能幾乎能保證這個佇列不可能滿。連線的狀態變為ESTABLISHED之後(實際是連線由從半連線佇列轉移到了完成握手的完成佇列)才表示可以被accpet()處理。

3.4.4 函式accept

接受連線
  1. 函式原型:  
  2. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  
  3. 引數說明:  
  4. sockfd:是由socket()呼叫返回的套介面檔案描述符。  
  5. addr:傳出連線客戶的sockaddr指標,包括(IP,protocol,port)  
  6. addrlen:傳入指定客戶結構addr的長度,返回時該值傳出為連線客戶地址addr的實際長度  
  7. int:返回值,-1表示出錯  

呼叫accept()之後,將會返回一個全新的套介面檔案描述符來處理這個單個的連線。這樣,對於同一個連線來說,你就有了兩個檔案描述符。原先的一個檔案描述符正在監聽你指定的埠,新的檔案描述符可以用來呼叫send()和recv()。

可以通過對套接字檔案描述符設定O_NONBLOCK來改變其是否阻塞:

  1. int flags = fcntl(socket, F_GETFL,0);  
  2. fcntl(socket, F_SETFL, O_NONBLOCK| flags);  


3.4.5 函式connect

  1. 函式原型:  
  2. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  
客戶端連線伺服器,addr為傳入引數,表示目的伺服器的(ip,protocal,port)。

3.4.6 函式read與write

read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()

  1. #include <unistd.h>  
  2. ssize_t read(int fd, void *buf, size_t count);  
  3. ssize_t write(int fd, const void *buf, size_t count);  
  4.   
  5. #include <sys/types.h>  
  6. #include <sys/socket.h>  
  7. ssize_t send(int sockfd, const void *buf, size_t len, int flags);  
  8. ssize_t recv(int sockfd, void *buf, size_t len, int flags);  
  9.   
  10. ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,  
  11.                const struct sockaddr *dest_addr, socklen_t addrlen);  
  12. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,  
  13.                struct sockaddr *src_addr, socklen_t *addrlen);  
  14.   
  15. ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);  
  16. ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);  

3.4.7 函式close

  1. 函式原型:  
  2. int close(int fd);  

注意:close操作只是使相應socket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向伺服器傳送終止連線請求。

3.4.8 函式getsockname

函式返回與套介面關聯的本地協議地址。

  1. 函式原型:  
  2. int getsockname(int sockfd, struct sockaddr * localaddr, socken_t * addrlen);  

3.4.9 函式getpeername

函式返回與套介面關聯的遠端協議地址。
  1. 函式原型:  
  2. int getpeername(int sockfd,struct sockaddr* peeraddr,int* addrlen);  
  3. 引數說明:  
  4. sockfd:是由socket()呼叫返回的套介面檔案描述符。  
  5. peeraddr:傳出資料結構sockaddr的指標,包括(IP,protocol,port)  
  6. addrlen:傳出結構大小的指標  
  7. int:返回值,-1表示出錯  


3.5 網路位元組序與主機位元組序轉換

將主機位元組序轉換為網路位元組序(避免大端小端問題)
#include <netinet/in.h>

3.5.1 函式htonl

  1. uint32_t htonl(uint32_t hostlong);  

3.5.2 函式htons

  1. uint16_t htons(uint16_t hostshort);  

3.5.3 函式ntohl

  1. uint32_t ntohl(uint32_t netlong);  

3.5.4 函式ntohs

  1. uint16_t ntohs(uint16_t netshort);  

3.6 IP地址與主機位元組序轉換

3.6.1 函式inet_pton

[將“點分十進位制” -> “整數”]

  1. 函式原型:  
  2. int inet_pton(int af, const char *src, void *dst);  
  3. 引數說明:  
  4. af:地址族,AF_INET為ipv4地址,AF_INET6為ipv6地址  
  5. src:為ip地址(ipv4例如1.1.1.1)  
  6. dst:函式將該地址轉換為in_addr的結構體,並複製在*dst中  
  7. int:返回值:如果函式出錯將返回一個負值,並將errno設定為EAFNOSUPPORT,如果引數af指定的地址族和src格式不對,函式將返回0。  

3.6.2 函式inet_ntop

[將“整數” -> “點分十進位制”]

  1. 函式原型:  
  2. const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);  

3.7 主機資料庫函式

3.7.1 函式gethostname

它返回(本地)計算機的名字,儲存在hostname中,大小為size

  1. 函式原型:  
  2. int gethostname(char*hostname,size_tsize);  
  3. 引數說明:  
  4. int:返回值gethostname將返回0。如果失敗,它將返回-1。   

3.7.2 函式gethostbyname

根據域名或者主機名獲取資訊

  1. 函式原型:  
  2. struct hostent *gethostbyname(const char *name);  
  3. 引數說明:  
  4. name:主機名,如"www.baidu.com"  
  5. hostend:返回值。如果函式呼叫失敗,將返回NULL。  

hostend的結構如下:

  1. struct hostent   
  2. {  
  3.   char  *h_name;            //表示的是主機的規範名,例如www.google.com的規範名其實是www.l.google.com  
  4.   char  **h_aliases;        //表示的是主機的別名  
  5.   int   h_addrtype;         //IP地址的型別  
  6.   int   h_length;           //IP地址的長度  
  7.   char  **h_addr_list;      //主機的ip地址,注意這是以網路位元組順序儲存的一個值,用inet_ntop恢復  
  8. };  

示例程式碼:

  1. #include <netdb.h>  
  2. #include <sys/socket.h>  
  3. #include <stdio.h>  
  4. #include <unistd.h>  
  5.   
  6. int main(int argc, char **argv)  
  7. {  
  8.     char *host,**names;  
  9.     struct hostent *hostinfo;  
  10.     cha str[32];  
  11.     /* 取得命令後第一個引數,即要解析的域名或主機名 */  
  12.     if(argc==1){  
  13.         char myname[256];  
  14.         gethostname(myname,255);  
  15.         host=myname;  
  16.     }  
  17.     else{  
  18.         host=argv[1];  
  19.     }  
  20.     /* 呼叫gethostbyname()。呼叫結果都存在hostinfo中 */  
  21.     if( (hostinfo = gethostbyname(host) ) == NULL )  
  22.     {  
  23.         printf("gethostbyname error for host:%s/n", host);  
  24.         exit(1); /* 如果呼叫gethostbyname發生錯誤,返回1 */  
  25.     }  
  26.     /* 將主機的規範名打出來 */  
  27.     printf("official hostname:%s/n",hostinfo->h_name);  
  28.     /* 主機可能有多個別名,將所有別名分別打出來 */  
  29.     for(names = hostinfo->h_aliases; *names != NULL; names++)  
  30.         printf(" alias:%s/n",*names);  
  31.     /* 根據地址型別,將地址打出來 */  
  32.     switch(hostinfo->h_addrtype)  
  33.     {  
  34.     case AF_INET:  
  35.     case AF_INET6:  
  36.         names=hostinfo->h_addr_list;  
  37.         /* 將剛才得到的所有地址都打出來。其中呼叫了inet_ntop()函式 */  
  38.         for(;*names!=NULL;names++)  
  39.             printf(" address:%s/n", inet_ntop(hostinfo->h_addrtype, *names, str, sizeof(str)));  
  40.         break;  
  41.     default:  
  42.         printf("unknown address type/n");  
  43.         break;  
  44.     }  
  45.     exit(0);  
  46. }   

3.7.3 函式gethostbyaddr

根據ip地址(網路位元組序)獲取資訊

  1. 函式原型:  
  2. struct hostent *gethostbyaddr(const char *name,int len,int type)  
  3. 引數說明:  
  4. name:ip地址,例如: inet_addr("192.168.4.111")  
  5. len:  
  6. type:  
  7. hostend:返回值。如果函式呼叫失敗,將返回NULL。  

3.7.4 函式getservbyname

用於根據給定的名字來查詢相應的伺服器,返回對應於給定服務名和協議名的相關服務資訊

  1. 函式原型:  
  2. struct servernt *gerservbyname(const char *servname,const char *protoname)  
  3. 引數說明:  
  4. servname:例如smtp  
  5. protoname:例如tcp  

servent結構如下:

  1. struct servent{  
  2.     char *s_name;    /*服務的正規名字*/  
  3.     char **s_aliases;/*別名列表*/  
  4.     int s_port;      /*服務的埠號*/  
  5.     char *s_proto;   /*服務的協議,如tcp或udp*/  
  6. }  

3.7.5 函式getservbyport

返回與給定服務名對應的包含名字和服務號資訊的servent結構指標(注意引數port的值必須是網路位元組序型別的,所以在使用的時候需要使用函式htons(port)來進行轉換)。

  1. 函式原型:  
  2. struct servent *getservbyport(int port,const char *proroname);  
  3. 函式示例:  
  4. sptr=getservbyport(htons(53),"udp");  

示例程式碼:

  1. #獲取任意已知主機的日期和時間  
  2. #include <netdb.h>  
  3. #include <sys/socket.h>  
  4. #include <netinet/in.h>  
  5. #include <stdio.h>  
  6. #include <unistd.h>  
  7.   
  8. int main(int argc, char **argv)  
  9. {  
  10.     char *host;  
  11.     struct hostent * hostinfo;  
  12.     struct servent * servinfo;  
  13.     /* 取得命令後第一個引數,即要解析的域名或主機名 */  
  14.     if(argc==1){  
  15.         host="localhost";  
  16.     }  
  17.     else{  
  18.         host=argv[1];  
  19.     }  
  20.     /* 呼叫gethostbyname()。呼叫結果都存在hostinfo中 */  
  21.     if( (hostinfo = gethostbyname(host) ) == NULL )  
  22.     {  
  23.         printf("gethostbyname error for host:%s/n", host);  
  24.         exit(1); /* 如果呼叫gethostbyname發生錯誤,返回1 */  
  25.     }  
  26.     if ( (servinfo = getservbyname("daytime","tcp")) );  
  27.     {  
  28.         printf("no daytime service/n");  
  29.         exit(1); /* 如果呼叫getservbyname發生錯誤,返回1 */  
  30.     }  
  31.     /* 將daytime的埠列印出來*/  
  32.     printf("daytime port is %d/n",ntohs(servinfo -> s_port));  
  33.   
  34.     int sockfd = socket(AF_INET, SOCK_STREAM,0);  
  35.     struct sockaddr_in address;  
  36.     address.sin_family = AF_INET;  
  37.     address.sin_port = servinfo -> s_port;  
  38.     address.sin_addr= *(struct in_addr*)*hostinfo->h_addr_list;  
  39.     int len = sizeof(address);  
  40.   
  41.     int result;  
  42.     if((result = connect(socket,(struct sockaddr*)&address,len))==-1){  
  43.         printf("connect error: %s(errno: %d)\n",strerror(errno),errno);    
  44.         exit(0);   
  45.     }  
  46.   
  47.     char buffer[128];  
  48.     result = read(sockfd,buffer,sizeof(buffer));  
  49.     buffer[result]='\0';  
  50.     printf("read %d bytes: %s",result,buffer);  
  51.   
  52.     close(sockfd);  
  53.     exit(0);  
  54. }   


3.5 多客戶程式設計

http://blog.csdn.net/simba888888/article/details/9034407

3.5.1 阻塞與非阻塞

(1)阻塞block
    所謂阻塞方式block,顧名思義,就是程式或是執行緒執行到這些函式時必須等待某個事件的發生,如果事件沒有發生,程式或執行緒就被阻塞(程式Sleep),函式不能立即返回。
    例如socket程式設計中connect、accept、recv、recvfrom這樣的阻塞程式。
    再如絕大多數的函式呼叫、語句執行,嚴格來說,他們都是以阻塞方式執行的。
(2)非阻塞non-block
    所謂非阻塞方式non-block,就是程式或執行緒執行此函式時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函式的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個程式碼來告知事件未發生,而程式或執行緒繼續執行,所以效率較高。
   非阻塞I/O有一個缺點,如果所有裝置都一直沒有資料到達,呼叫者需要反覆查詢做無用功,如果阻塞在那裡,作業系統可以排程別的程式執行,就不會做無用功了。?????

3.5.2 函式select

  1. 函式原型:  
  2. int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);    
  3. 引數說明:  
  4. nfds:為檔案描述符集合中的最大值+1,避免函式檢查fd_set的所有1024位  
  5. readfds:被監控是否可以讀   
  6. writefds:被監控是否可以寫   
  7. exceptfds:被監控是否發生異常   
  8. timeout:超時時間,Linux返回時會被修改成剩餘的時間。  
  9. int:返回值,返回狀態發生變化的描述符的總數,錯誤返回-1,超時返回0。  

關於timeout:

  1. struct timeval結構如下:  
  2. struct timeval  
  3. {  
  4.        long tv_sec;  //seconds  
  5.        long tv_usec; //microseconds  
  6. };  
1,若timeout設定為t>0,表示等待固定時間,有一個fd位被置為1或者時間耗盡,函式均返回。
2,若timeout設定為t=0,表示非阻塞,函式檢查完每個fd後立即返回。
3,若timeout設定為t="NULL",表示阻塞,直到有一個fd位被置為1函式才返回。
  1. 相關操作:    
  2. FD_ZERO(fd_set *set);         //fd_set很多系統實現為bit arrays,將所有的檔案描述符從fd_set中清空    
  3. FD_CLR(int fd, fd_set *set);  //從set中清除fd      
  4. FD_SET(int fd, fd_set *set);  //將fd新增到set中     
  5. FD_ISSET(int fd, fd_set *set);//判斷描述符fd是否在給定的描述符集set中,通常配合select函式使用,由於select函式成功返回時會將未準備好的描述符位清零。通常我們使用FD_ISSET是為了檢查在select函式返回後,某個描述符是否準備好,以便進行接下來的處理操作。       

常用程式碼:

  1. fd_set  rdfds;  
  2. struct timeval tv;  
  3. tv.tv_sec = 1;  
  4. tv.tv_uses = 500;  
  5. int ret;  
  6. FD_ZERO(&rdfds);  
  7. FD_SET(socket, &rdfds);  
  8. ret = select (socket + 1, %rdfds, NULL, NULL, &tv);  
  9. if(ret < 0)   
  10.  perror ("select");  
  11. else if (ret == 0)   
  12.  printf("time out");  
  13. else {  
  14.        printf(“ret = %d/n”,ret);  
  15.        if(FD_ISSET(socket, &rdfds)){  
  16.        /* 讀取socket控制程式碼裡的資料 */  
  17.        recv( );  
  18.        }  
  19. }  

3.5.3 函式pselect

  1. 函式原型:  
  2. #include <sys/select.h>  
  3. int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, const struct timespec *restrict tsptr,const sigset_t *restrict sigmask);  
  4. 引數說明  
  5. 返回值:Returns: count of ready descriptors, 0 on timeout, -1 on error  
它與select的區別在於:
1,pselect使用timespec結構指定超時值。timespec結構以秒和納秒錶示時間,而非秒和微秒。
2,pselect的超時值被宣告為const,這保證了呼叫pselect不會改變timespec結構。
3,pselect可使用一個可選擇的訊號遮蔽字。在呼叫pselect時,以原子操作的方式安裝該訊號遮蔽字,在返回時恢復以前的訊號遮蔽字。

3.5.4 函式poll

  1. 函式原型:  
  2. #include <sys/poll.h>  
  3. int poll (struct pollfd *fds, unsigned int nfds, int timeout);  
  4. 引數說明:  
  5. timeout:【值=INFTIM】表示永遠等待【值=0】表示立即返回,不阻塞程式【值>0】等待指定數目的毫秒數。  

pollfd結構說明:

  1. struct pollfd {  
  2.      int fd; /* 檔案描述符 */  
  3.      short events; /* 等待的事件 */  
  4.      short revents; /* 發生的事件 */  
  5. };  
  6. poll函式可用的測試值:  
  7. 常量  說明  
  8. POLLIN  普通或優先順序帶資料可讀  
  9. POLLRDNORM  普通資料可讀  
  10. POLLRDBAND  優先順序帶資料可讀  
  11. POLLPRI 高優先順序資料可讀  
  12. POLLOUT 普通資料可寫  
  13. POLLWRNORM  普通資料可寫  
  14. POLLWRBAND  優先順序帶資料可寫  
  15. POLLERR 發生錯誤  
  16. POLLHUP 發生掛起  
  17. POLLNVAL    描述字不是一個開啟的檔案  
  18. 注意:後三個只能作為描述字的返回結果儲存在revents中,而不能作為測試條件用於events中。  

3.5.5 函式epoll

一共有三個函式:

  1. int epoll_create(int size);  
建立一個epoll的控制程式碼,size用來告訴核心這個監聽的數目一共有多大。這個引數不同於select()中的第一個引數,給出最大監聽的fd+1的值。需要注意的是,當建立好epoll控制程式碼後,它就是會佔用一個fd值,在linux下如果檢視/proc/程式id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須呼叫close()關閉,否則可能導致fd被耗盡。
  1. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
epoll的事件註冊函式,它不同與select()是在監聽事件時告訴核心要監聽什麼型別的事件,而是在這裡先註冊要監聽的事件型別。第一個引數是epoll_create()的返回值,第二個參數列示動作,用三個巨集來表示:
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個引數是需要監聽的fd,第四個引數是告訴核心需要監聽什麼事。
struct epoll_event結構如下:
  1. struct epoll_event {  
  2.   __uint32_t events;  /* Epoll events */  
  3.   epoll_data_t data;  /* User data variable */  
  4. };  
events可以是以下幾個巨集的集合:
EPOLLIN :表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的檔案描述符可以寫;
EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);
EPOLLERR:表示對應的檔案描述符發生錯誤;
EPOLLHUP:表示對應的檔案描述符被結束通話;
EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL佇列裡
  1. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);  
等待事件的產生,類似於select()呼叫。引數events用來從核心得到事件的集合,maxevents告之核心這個events有多大,這個 maxevents的值不能大於建立epoll_create()時的size,引數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有 說法說是永久阻塞)。該函式返回需要處理的事件數目,如返回0表示已超時。



在前面講過的客戶/伺服器程式中,伺服器只能處理一個客戶端的請求,如何同時服務多個客戶端呢?在未講到select/poll/epoll等高階IO之前,比較老土的辦法是使用fork來實現。網路伺服器通常用fork來同時服務多個客戶端,父程式專門負責監聽埠,每次accept一個新的客戶端連線就fork出一個子程式專門服務這個客戶端。但是子程式退出時會產生殭屍程式,父程式要注意處理SIGCHLD訊號和呼叫wait清理殭屍程式,最簡單的辦法就是直接忽略SIGCHLD訊號。

1,這裡為什麼會有殭屍程式?

2,實現P2P

3,signal(SIGCHLD, SIG_IGN);



相關文章