在网络编程编程中,我们经常会遇到这样一种C/S架构,服务器端(Server)监听客户端(Client)发送过来的命令,然后解析该命令,并做对应的处理,最后返回处理结果(例如成功或者失败及原因)给客户端。

    最近,在Linux下做网络编程,涉及的就是上面的这种需求,简单地整理了下自己的代码,分享在这里吧,供初学者参考。

    首先说一下编程思路吧。

    在这种情况客户端必须实现的的接口有:连接服务器、发送、断开连接。

    服务器端,有一个主线程,用于监听客户端的连接请求,一旦有新客户端连接,则创建一个新的socket及线程专门服务这个客户端。这个服务线程专门监听该客户端的命令,并且解析命令进行服务器,直到客户端断开连接或者发送关闭连接的命令。

    另外,需要涉及一个通信协议,约定命令的包头、命令的识别码、命令的参数。

    思路就说到这儿了,下面的相关代码,附件中有完整的代码,包含了Makefile文件。

一、通信协议设计

  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_  

二、客户端代码

  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.  

三、服务器主线程代码

  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. }  

四、服务器端服务线程代码

  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. }  

五、客户端测试代码

  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. }  

六、服务器端测试代码

  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. }  

七、总结和说明

    本文后面的附件中有完整的代码,欢迎下载使用。编译方法,把代码文件夹都拷贝到linux下,在本代码文件夹的根目录下,运行make,即可生成对应的可执行文件。在运行测试程序的时候,请先执行server.out,然后执行client.out

    另外,如果需要转载本文或者代码,请注明出处,本代码来自ticktick的博客:http://ticktick.blog.51cto.com 谢谢。

    如果发现本代码的任何bug或者有任何建议,欢迎留言或者来信交流。