基於Linux的socket編程模板

    在網絡編程編程中,咱們常常會遇到這樣一種C/S架構,服務器端(Server)監聽客戶端(Client)發送過來的命令,而後解析該命令,並作對應的處理,最後返回處理結果(例如成功或者失敗及緣由)給客戶端。linux

    最近,在Linux下作網絡編程,涉及的就是上面的這種需求,簡單地整理了下本身的代碼,分享在這裏吧,供初學者參考。編程

    首先說一下編程思路吧。服務器

    在這種狀況客戶端必須實現的的接口有:鏈接服務器、發送、斷開鏈接。網絡

    服務器端,有一個主線程,用於監聽客戶端的鏈接請求,一旦有新客戶端鏈接,則建立一個新的socket及線程專門服務這個客戶端。這個服務線程專門監聽該客戶端的命令,而且解析命令進行服務器,直到客戶端斷開鏈接或者發送關閉鏈接的命令。session

    另外,須要涉及一個通訊協議,約定命令的包頭、命令的識別碼、命令的參數。架構

    思路就說到這兒了,下面的相關代碼,附件中有完整的代碼,包含了Makefile文件。socket

1、通訊協議設計tcp

  
  
           
  
  
  1. //////////////////////////////////////////////////////////////////////////  
  2. //  COPYRIGHT NOTICE  
  3. //  Copyright (c) 2011, 華中科技大學 ticktick(版權聲明)  
  4. //  All rights reserved.  
  5. //   
  6. /// @file    Command.h    
  7. /// @brief   命令包聲明文件  
  8. ///  
  9. /// 定義各類TCP命令包以及相關結構體  
  10. ///  
  11. /// @version 1.0     
  12. /// @author  lujun   
  13. /// @E-mail  lujun.hust@gmail.com  
  14. /// @date    2011/08/21  
  15. //  
  16. //  
  17. //  修訂說明:  
  18. //////////////////////////////////////////////////////////////////////////  
  19.  
  20. #ifndef COMMAND_H_  
  21. #define COMMAND_H_  
  22.  
  23. typedef unsigned char uint8_t;  
  24.  
  25. // TCP CMD header len  
  26. #define TCP_CMD_HEADER_LEN 8  
  27.  
  28. // CMD header  
  29. static uint8_t TCP_CMD_HEADER_STR[TCP_CMD_HEADER_LEN] = { 0xAA,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xFF };  
  30.  
  31. // max user name len   
  32. #define MAX_USER_NAME_LEN 20  
  33.  
  34. // server cmd struct  
  35. typedef enum _ServerCMD  
  36. {  
  37.     CMD_SAVE_USER_NAME,         // save user name  
  38.     CMD_SAVE_USER_AGE,          // save user age  
  39.  
  40. }ServerCMD;  
  41.  
  42. // return cmd   
  43. typedef enum _ReturnCMD  
  44. {  
  45.     DVS_RETURN_SUCCESS = 0,        
  46.     DVS_RETURN_FAIL,               
  47.     DVS_RETURN_TIMEOUT,            
  48.     DVS_RETURN_INVLID_HEADER,      
  49.     DVS_RETURN_INVLID_CMD,         
  50.     DVS_RETURN_INVLID_PRM,         
  51.  
  52. }ReturnCMD;  
  53.  
  54. // 1bytes aligning  
  55. #pragma pack( push, 1 )  
  56.  
  57. // server pack from client  
  58. typedef struct _ServerPack   
  59. {  
  60.     // cmd header  
  61.     uint8_t cmdHeader[TCP_CMD_HEADER_LEN];  
  62.  
  63.     // command id  
  64.     ServerCMD serverCMD;  
  65.  
  66.     // cmd param  
  67.     union 
  68.     {  
  69.         // save user name  
  70.           struct 
  71.           {  
  72.               // user name  
  73.             char username[MAX_USER_NAME_LEN];  
  74.  
  75.           }UserName;  
  76.  
  77.           // save user age  
  78.           struct 
  79.           {  
  80.               // user age  
  81.             int userage;  
  82.  
  83.           }UserAge;  
  84.  
  85.     }Parameters;  
  86.  
  87. }ServerPack;  
  88.  
  89. // return pack from server  
  90. typedef struct _ReturnPack  
  91. {  
  92.     // cmd header  
  93.     uint8_t cmdHeader[TCP_CMD_HEADER_LEN];  
  94.  
  95.     // return cmd   
  96.     ReturnCMD returnCMD;  
  97.  
  98. }ReturnPack;  
  99.  
  100. #pragma pack( pop )  
  101.  
  102. #define SERVER_PACK_LEN sizeof(ServerPack)  
  103. #define RETURN_PACK_LEN sizeof(ReturnPack)  
  104.  
  105. #endif // COMMAND_H_  

2、客戶端代碼ide

  
  
           
  
  
  1. //////////////////////////////////////////////////////////////////////////  
  2. //  COPYRIGHT NOTICE  
  3. //  Copyright (c) 2011, 華中科技大學 ticktick(版權聲明)  
  4. //  All rights reserved.  
  5. //   
  6. /// @file    client.c    
  7. /// @brief   tcp客戶端代碼  
  8. ///  
  9. /// 實現tcp客戶端的相關接口  
  10. ///  
  11. /// @version 1.0     
  12. /// @author  lujun   
  13. /// @E-mail  lujun.hust@gmail.com  
  14. /// @date    2011/08/21  
  15. //  
  16. //  
  17. //  修訂說明:  
  18. //////////////////////////////////////////////////////////////////////////  
  19.  
  20. #include <stdio.h>  
  21. #include <sys/types.h>  
  22. #include <sys/socket.h>  
  23. #include <netinet/in.h>  
  24. #include <unistd.h>  
  25. #include <errno.h>  
  26. #include <string.h>  
  27. #include "client.h"  
  28.  
  29. // socket handle  
  30. int g_hSocket;  
  31.  
  32. int connect_server( char *destIp, int destPort )  
  33. {  
  34.     int result;  
  35.     struct sockaddr_in address;  
  36.  
  37.     // create a socket  
  38.     g_hSocket = socket(AF_INET,SOCK_STREAM,0);  
  39.  
  40.     // set server addr  
  41.     address.sin_family = AF_INET;  
  42.  
  43.     // use server ip and listen port to connect  
  44.     address.sin_addr.s_addr = inet_addr( destIp );  
  45.     address.sin_port        = htons(destPort);  
  46.       
  47.     // connect tcp server  
  48.     result = connect(g_hSocket,(struct sockaddr *)&address,sizeof(address) );  
  49.     if( result == -1 )  
  50.     {  
  51.         printf("[tcp client] can't connect server !\n");  
  52.           return -1;  
  53.     }  
  54.       
  55.     return 0;  
  56. }  
  57.  
  58. int close_connect()  
  59. {  
  60.     printf("close connect with server !\n ");  
  61.  
  62.     close(g_hSocket);  
  63.  
  64.     return 0;  
  65. }  
  66.  
  67. int send_cmd( ServerPack sPack )  
  68. {  
  69.     int recvBytes = 0;  
  70.     int sendBytes = 0;  
  71.     ReturnPack rPack;  
  72.  
  73.     // add cmd header  
  74.     memcpy(sPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN);  
  75.  
  76.     // send cmd  
  77.     while(1)  
  78.     {  
  79.         sendBytes = send(g_hSocket,(uint8_t *)&sPack,SERVER_PACK_LEN,0);  
  80.           
  81.         if( sendBytes == SERVER_PACK_LEN )  
  82.         {  
  83.             printf("successfully send bytes %d\n",SERVER_PACK_LEN);  
  84.             break;  
  85.         }  
  86.         else if( sendBytes <= 0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)  
  87.         {  
  88.             printf("disconnected or other errors!\n");  
  89.               return -1;  
  90.         }  
  91.         else 
  92.         {  
  93.             continue;  
  94.         }  
  95.     }  
  96.  
  97.     // recv process result from server  
  98.     while(1)  
  99.     {  
  100.         recvBytes = recv(g_hSocket,(uint8_t *)&rPack,RETURN_PACK_LEN,0);  
  101.  
  102.           if( recvBytes == RETURN_PACK_LEN )  
  103.           {  
  104.               break;  
  105.           }  
  106.           else if( recvBytes <=0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN )  
  107.           {  
  108.               printf("disconnected or error occur!\n close the socket!\n");  
  109.               return -1;  
  110.           }  
  111.           else 
  112.           {   
  113.               continue;  
  114.           }  
  115.     }  
  116.  
  117.     // check header  
  118.     if ( memcmp( rPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEADER_LEN ) != 0 )  
  119.     {  
  120.         printf("return pack header errror!\n");  
  121.         return -2;  
  122.     }  
  123.  
  124.     // get return status  
  125.     if( rPack.returnCMD != DVS_RETURN_SUCCESS )  
  126.     {  
  127.         printf("return status : fail!\n");  
  128.         return -3;  
  129.     }  
  130.       
  131.     return 0;      
  132. }  
  133.  
  134.  

3、服務器主線程代碼函數

  
  
           
  
  
  1. //////////////////////////////////////////////////////////////////////////  
  2. //  COPYRIGHT NOTICE  
  3. //  Copyright (c) 2011, 華中科技大學 ticktick(版權聲明)  
  4. //  All rights reserved.  
  5. //   
  6. /// @file    server.c   
  7. /// @brief   tcp服務器主線程代碼  
  8. ///  
  9. /// 實現tcp服務端監聽線程相關函數  
  10. ///  
  11. /// @version 1.0     
  12. /// @author  lujun   
  13. /// @E-mail  lujun.hust@gmail.com  
  14. /// @date    2011/08/21  
  15. //  
  16. //  
  17. //  修訂說明:  
  18. //////////////////////////////////////////////////////////////////////////  
  19.  
  20. #include <sys/types.h>  
  21. #include <sys/socket.h>  
  22. #include <stdio.h>  
  23.  
  24. #include <netinet/in.h>  
  25. #include <unistd.h>  
  26.  
  27. #include "serverIf.h"  
  28.  
  29. int g_hServerSocket;  
  30.  
  31. int open_port( int localport )  
  32. {  
  33.     int result;  
  34.     int clientSocket,client_len;  
  35.  
  36.     struct sockaddr_in server_addr;  
  37.     struct sockaddr_in client_addr;  
  38.  
  39.     // create a socket obj for server  
  40.     g_hServerSocket = socket(AF_INET,SOCK_STREAM,0);  
  41.  
  42.     // bind tcp port  
  43.     server_addr.sin_family = AF_INET;  
  44.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  45.     server_addr.sin_port = htons(localport);  
  46.  
  47.     result = bind(g_hServerSocket,(struct sockaddr *)&server_addr,sizeof(server_addr) );  
  48.     if( result != 0 )   
  49.     {  
  50.            printf("[tcp server] bind error!\n ");      
  51.            return -1;  
  52.     }  
  53.  
  54.     // begin to listen  
  55.     result = listen(g_hServerSocket,5);  
  56.     if( result != 0 )  
  57.     {  
  58.         printf("[tcp server] listen error!\n ");  
  59.           return -1;  
  60.     }  
  61.  
  62. // 注: ServerEnv用於給服務線程傳參,定義於serverIf.h中
  63.     ServerEnv env;  
  64.     while(1)  
  65.     {  
  66.           client_len = sizeof(client_addr);  
  67.           clientSocket = accept(g_hServerSocket,(struct sockaddr *)&client_addr,&client_len );  
  68.  
  69.           if( clientSocket < 0 )  
  70.           {  
  71.               printf("[tcp server] accept error!\n" );  
  72.               return -1;  
  73.           }  
  74.              
  75.           env.m_hSocket = clientSocket;  
  76.             
  77.           // add new tcp server thread  
  78.           add_new_tcp_process_thr(&env);  
  79.     }  
  80.  
  81.     return 0;  
  82. }  
  83.  
  84. int close_port()  
  85. {  
  86.     printf("close server port, stop listen!\n");  
  87.  
  88.     close(g_hServerSocket);  
  89.  
  90.     return 0;  
  91. }  

4、服務器端服務線程代碼

  
  
           
  
  
  1. //////////////////////////////////////////////////////////////////////////  
  2. //  COPYRIGHT NOTICE  
  3. //  Copyright (c) 2011, 華中科技大學 ticktick(版權聲明)  
  4. //  All rights reserved.  
  5. //   
  6. /// @file    serverIf.c    
  7. /// @brief   tcp服務線程代碼  
  8. ///  
  9. /// 實現tcp服務線程相關接口  
  10. ///  
  11. /// @version 1.0     
  12. /// @author  lujun   
  13. /// @E-mail  lujun.hust@gmail.com  
  14. /// @date    2011/08/21  
  15. //  
  16. //  
  17. //  修訂說明:  
  18. //////////////////////////////////////////////////////////////////////////  
  19.  
  20. #include "serverIf.h"  
  21. #include "../include/Command.h"  
  22. #include <sys/socket.h>  
  23. #include <stdio.h>  
  24. #include <string.h>  
  25. #include <errno.h>  
  26.  
  27. int add_new_tcp_process_thr( ServerEnv *envp )  
  28. {  
  29.     pthread_t tcpThr;  
  30.  
  31.     if( pthread_create( &tcpThr,NULL,tcpServerThrFxn,envp ) )  
  32.     {  
  33.         printf("tcp thread create fail!\n");  
  34.         return -1;  
  35.     }  
  36.  
  37.     printf("tcp thread has been created!\n");  
  38.  
  39.     return 0;  
  40. }  
  41.  
  42. int save_user_name( char * pUsername )  
  43. {  
  44.     printf("ok,user name saved,username=%s\n",pUsername);  
  45.  
  46.     return 0;  
  47. }  
  48.  
  49. int save_user_age( int age )  
  50. {  
  51.     printf("ok,user age saved,userage=%d\n",age);  
  52.  
  53.     return 0;  
  54. }  
  55.  
  56. void * tcpServerThrFxn( void * arg )  
  57. {  
  58.     ServerEnv * envp = (ServerEnv *)arg;  
  59.     int socketfd = envp->m_hSocket;  
  60.     int returnBytes;  
  61.  
  62.     ServerPack sPack;  
  63.     ReturnPack rPack;  
  64.     memcpy(rPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN);  
  65.  
  66.     while(1)  
  67.     {  
  68.           // read cmd from client  
  69.           returnBytes = recv(socketfd,(uint8_t *)&sPack,SERVER_PACK_LEN,0);  
  70.          if( returnBytes == SERVER_PACK_LEN )  
  71.           {  
  72.               //printf("ok,recv %d bytes! \n",SERVER_PACK_LEN);  
  73.           }  
  74.           else if( returnBytes <= 0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN )  
  75.           {  
  76.               printf("disconnected or error occur! errno=%d\n ",errno);  
  77.               break;  
  78.           }  
  79.           else 
  80.           {  
  81.               continue;  
  82.           }  
  83.  
  84.          // check the header  
  85.           if ( memcmp( sPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEADER_LEN ) != 0 )  
  86.           {  
  87.               rPack.returnCMD = DVS_RETURN_INVLID_HEADER;  
  88.  
  89.               // return error info to client  
  90.               returnBytes = send(socketfd,(uint8_t *)&rPack,RETURN_PACK_LEN,0 ) ;  
  91.               if( returnBytes < RETURN_PACK_LEN)  
  92.               {  
  93.                   printf("send error!\n");  
  94.                     continue;  
  95.               }  
  96.           }  
  97.  
  98.           // analyse cmd  
  99.           rPack.returnCMD = DVS_RETURN_SUCCESS;  
  100.          switch( sPack.serverCMD )  
  101.           {  
  102.           case CMD_SAVE_USER_NAME:  
  103.                {    
  104.                      if( save_user_name(sPack.Parameters.UserName.username) != 0)  
  105.                      {  
  106.                          rPack.returnCMD = DVS_RETURN_FAIL;  
  107.                      }  
  108.                }  
  109.           break;  
  110.           case CMD_SAVE_USER_AGE:  
  111.               {  
  112.                    if( save_user_age(sPack.Parameters.UserAge.userage) != 0)  
  113.                      {   
  114.                          rPack.returnCMD = DVS_RETURN_FAIL;  
  115.                      }  
  116.               }  
  117.           break;  
  118.           default:  
  119.              {    
  120.                    rPack.returnCMD = DVS_RETURN_INVLID_CMD;  
  121.              }  
  122.           break;  
  123.           }   
  124.       
  125.           // return result info to client  
  126.           returnBytes = send(socketfd,(uint8_t *)&rPack,RETURN_PACK_LEN,0 );  
  127.           if( returnBytes < RETURN_PACK_LEN )  
  128.           {  
  129.             printf("send error!\n");  
  130.             continue;         
  131.           }  
  132.     }  
  133.  
  134.     printf("close session socket!");  
  135.  
  136.     // close socket  
  137.     close(socketfd);  
  138.  
  139.     return (void*)0;  
  140. }  

5、客戶端測試代碼

  
  
           
  
  
  1. //////////////////////////////////////////////////////////////////////////  
  2. //  COPYRIGHT NOTICE  
  3. //  Copyright (c) 2011, 華中科技大學 ticktick(版權聲明)  
  4. //  All rights reserved.  
  5. //   
  6. /// @file    main.c    
  7. /// @brief   tcp客戶端測試代碼  
  8. ///  
  9. /// 實現 tcp客戶端測試  
  10. ///  
  11. /// @version 1.0     
  12. /// @author  lujun   
  13. /// @E-mail  lujun.hust@gmail.com  
  14. /// @date    2011/08/21  
  15. //  
  16. //  
  17. //  修訂說明:  
  18. //////////////////////////////////////////////////////////////////////////  
  19.  
  20. #include <stdio.h>  
  21.  
  22. #include "client.h"  
  23.  
  24. #define LOCAL_IP_STR "127.0.0.1"  
  25. #define DEST_IP_STR  "192.201.0.8"  
  26. #define DEST_PORT    8000  
  27.  
  28. int main(int argc, char **argv)  
  29. {  
  30.     int i =0;  
  31.  
  32.     printf("tcp test start!\n");  
  33.  
  34.     if( connect_server(DEST_IP_STR,DEST_PORT) != 0)  
  35.     {  
  36.         return -1;  
  37.     }  
  38.  
  39.     ServerPack sPack;  
  40.     sPack.serverCMD = CMD_SAVE_USER_AGE;  
  41.     sPack.Parameters.UserAge.userage = 20;  
  42.  
  43.     if( send_cmd(sPack) == -1 )  
  44.       {  
  45.           printf("send cmd fail!\n");  
  46.       }  
  47.  
  48.     getchar();  
  49.     getchar();  
  50.  
  51.     close_connect();  
  52.  
  53.     printf("tcp test pass!\n");  
  54.  
  55.     return 0;  
  56. }  

6、服務器端測試代碼

  
  
           
  
  
  1. //////////////////////////////////////////////////////////////////////////  
  2. //  COPYRIGHT NOTICE  
  3. //  Copyright (c) 2011, 華中科技大學 ticktick(版權聲明)  
  4. //  All rights reserved.  
  5. //   
  6. /// @file    main.c    
  7. /// @brief   tcp客戶端代碼  
  8. ///  
  9. /// 實現tcp服務器端測試的代碼  
  10. ///  
  11. /// @version 1.0     
  12. /// @author  lujun   
  13. /// @E-mail  lujun.hust@gmail.com  
  14. /// @date    2011/08/21  
  15. //  
  16. //  
  17. //  修訂說明:  
  18. //////////////////////////////////////////////////////////////////////////  
  19.  
  20. #include <stdio.h>  
  21.  
  22. #include "server.h"  
  23. #include "serverIf.h"  
  24.  
  25. #define LOCAL_PORT 8000  
  26.  
  27. int main(int argc, char **argv)  
  28. {  
  29.     printf("tcp test start!\n");  
  30.  
  31.     if( open_port(LOCAL_PORT) != 0)  
  32.     {  
  33.         return -1;  
  34.     }  
  35.  
  36.     close_port();  
  37.  
  38.     printf(" close port !\n");  
  39.  
  40.     return 0;  
  41. }  

7、總結和說明

    本文後面的附件中有完整的代碼,歡迎下載使用。編譯方法,把代碼文件夾都拷貝到linux下,在本代碼文件夾的根目錄下,運行make,便可生成對應的可執行文件。在運行測試程序的時候,請先執行server.out,而後執行client.out

    另外,若是須要轉載本文或者代碼,請註明出處,本代碼來自ticktick的博客:http://ticktick.blog.51cto.com 謝謝。

    若是發現本代碼的任何bug或者有任何建議,歡迎留言或者來信交流。

相關文章
相關標籤/搜索