基于Linux的socket编程模板

在网络编程编程中,我们经常会遇到这样一种C/S架构,服务器端(Server)监听客户端(Client)发送过来的命令,然后解析该命令,并做对应的处理,最后返回处理结果(例如成功或者失败及原因)给客户端。
    最近,在Linux下做网络编程,涉及的就是上面的这种需求,简单地整理了下自己的代码,分享在这里吧,供初学者参考。
    首先说一下编程思路吧。
    在这种情况客户端必须实现的的接口有:连接服务器、发送、断开连接。
    服务器端,有一个主线程,用于监听客户端的连接请求,一旦有新客户端连接,则创建一个新的socket及线程专门服务这个客户端。这个服务线程专门监听该客户端的命令,并且解析命令进行服务器,直到客户端断开连接或者发送关闭连接的命令。
    另外,需要涉及一个通信协议,约定命令的包头、命令的识别码、命令的参数。
    思路就说到这儿了,下面的相关代码,附件中有完整的代码,包含了Makefile文件。
一、通信协议设计
// 
//  COPYRIGHT NOTICE 
//  Copyright (c) 2011, 华中科技大学 ticktick(版权声明) 
//  All rights reserved. 
//  
/// @file    Command.h   
/// @brief   命令包声明文件 
/// 
/// 定义各种TCP命令包以及相关结构体 
/// 
/// @version 1.0    
/// @author  lujun  
/// @E-mail   lujun.hust@gmail.com  
/// @date    2011/08/21 
// 
// 
//  修订说明: 
// 
 
#ifndef COMMAND_H_ 
#define COMMAND_H_ 
 
typedef unsigned char uint8_t; 
 
// TCP CMD header len 
#define TCP_CMD_HEADER_LEN 8 
 
// CMD header 
static uint8_t TCP_CMD_HEADER_STR[TCP_CMD_HEADER_LEN] = { 0xAA,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xFF }; 
 
// max user name len  
#define MAX_USER_NAME_LEN 20 
 
// server cmd struct 
typedef enum _ServerCMD 

    CMD_SAVE_USER_NAME,         // save user name 
    CMD_SAVE_USER_AGE,          // save user age 
 
}ServerCMD; 
 
// return cmd  
typedef enum _ReturnCMD 

    DVS_RETURN_SUCCESS = 0,       
    DVS_RETURN_FAIL,              
    DVS_RETURN_TIMEOUT,           
    DVS_RETURN_INVLID_HEADER,     
    DVS_RETURN_INVLID_CMD,        
    DVS_RETURN_INVLID_PRM,        
 
}ReturnCMD; 
 
// 1bytes aligning 
#pragma pack( push, 1 ) 
 
// server pack from client 
typedef struct _ServerPack  

    // cmd header 
    uint8_t cmdHeader[TCP_CMD_HEADER_LEN]; 
 
    // command id 
    ServerCMD serverCMD; 
 
    // cmd param 
    union
    { 
        // save user name 
          struct
          { 
              // user name 
            char username[MAX_USER_NAME_LEN]; 
 
          }UserName; 
 
          // save user age 
          struct
          { 
              // user age 
            int userage; 
 
          }UserAge; 
 
    }Parameters; 
 
}ServerPack; 
 
// return pack from server 
typedef struct _ReturnPack 

    // cmd header 
    uint8_t cmdHeader[TCP_CMD_HEADER_LEN]; 
 
    // return cmd  
    ReturnCMD returnCMD; 
 
}ReturnPack; 
 
#pragma pack( pop ) 
 
#define SERVER_PACK_LEN sizeof(ServerPack) 
#define RETURN_PACK_LEN sizeof(ReturnPack) 
 
#endif // COMMAND_H_ 
二、客户端代码
// 
//  COPYRIGHT NOTICE 
//  Copyright (c) 2011, 华中科技大学 ticktick(版权声明) 
//  All rights reserved. 
//  
/// @file    client.c   
/// @brief   tcp客户端代码 
/// 
/// 实现tcp客户端的相关接口 
/// 
/// @version 1.0    
/// @author  lujun  
/// @E-mail   lujun.hust@gmail.com  
/// @date    2011/08/21 
// 
// 
//  修订说明: 
// 
 
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
#include "client.h" 
 
// socket handle 
int g_hSocket; 
 
int connect_server( char *destIp, int destPort ) 

    int result; 
    struct sockaddr_in address; 
 
    // create a socket 
    g_hSocket = socket(AF_INET,SOCK_STREAM,0); 
 
    // set server addr 
    address.sin_family = AF_INET; 
 
    // use server ip and listen port to connect 
    address.sin_addr.s_addr = inet_addr( destIp ); 
    address.sin_port        = htons(destPort); 
     
    // connect tcp server 
    result = connect(g_hSocket,(struct sockaddr *)&address,sizeof(address) ); 
    if( result == -1 ) 
    { 
        printf("[tcp client] can't connect server !\n"); 
          return -1; 
    } 
     
    return 0; 

 
int close_connect() 

    printf("close connect with server !\n "); 
 
    close(g_hSocket); 
 
    return 0; 

 
int send_cmd( ServerPack sPack ) 

    int recvBytes = 0; 
    int sendBytes = 0; 
    ReturnPack rPack; 
 
    // add cmd header 
    memcpy(sPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN); 
 
    // send cmd 
    while(1) 
    { 
        sendBytes = send(g_hSocket,(uint8_t *)&sPack,SERVER_PACK_LEN,0); 
         
        if( sendBytes == SERVER_PACK_LEN ) 
        { 
            printf("successfully send bytes %d\n",SERVER_PACK_LEN); 
            break; 
        } 
        else if( sendBytes <= 0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN) 
        { 
            printf("disconnected or other errors!\n"); 
              return -1; 
        } 
        else
        { 
            continue; 
        } 
    } 
 
    // recv process result from server 
    while(1) 
    { 
        recvBytes = recv(g_hSocket,(uint8_t *)&rPack,RETURN_PACK_LEN,0); 
 
          if( recvBytes == RETURN_PACK_LEN ) 
          { 
              break; 
          } 
          else if( recvBytes <=0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN ) 
          { 
              printf("disconnected or error occur!\n close the socket!\n"); 
              return -1; 
          } 
          else
          {  
              continue; 
          } 
    } 
 
    // check header 
    if ( memcmp( rPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEADER_LEN ) != 0 ) 
    { 
        printf("return pack header errror!\n"); 
        return -2; 
    } 
 
    // get return status 
    if( rPack.returnCMD != DVS_RETURN_SUCCESS ) 
    { 
        printf("return status : fail!\n"); 
        return -3; 
    } 
     
    return 0;     

 
 
三、服务器主线程代码
// 
//  COPYRIGHT NOTICE 
//  Copyright (c) 2011, 华中科技大学 ticktick(版权声明) 
//  All rights reserved. 
//  
/// @file    server.c  
/// @brief   tcp服务器主线程代码 
/// 
/// 实现tcp服务端监听线程相关函数 
/// 
/// @version 1.0    
/// @author  lujun  
/// @E-mail   lujun.hust@gmail.com  
/// @date    2011/08/21 
// 
// 
//  修订说明: 
// 
 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
 
#include <netinet/in.h> 
#include <unistd.h> 
 
#include "serverIf.h" 
 
int g_hServerSocket; 
 
int open_port( int localport ) 

    int result; 
    int clientSocket,client_len; 
 
    struct sockaddr_in server_addr; 
    struct sockaddr_in client_addr; 
 
    // create a socket obj for server 
    g_hServerSocket = socket(AF_INET,SOCK_STREAM,0); 
 
    // bind tcp port 
    server_addr.sin_family = AF_INET; 
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    server_addr.sin_port = htons(localport); 
 
    result = bind(g_hServerSocket,(struct sockaddr *)&server_addr,sizeof(server_addr) ); 
    if( result != 0 )  
    { 
           printf("[tcp server] bind error!\n ");     
           return -1; 
    } 
 
    // begin to listen 
    result = listen(g_hServerSocket,5); 
    if( result != 0 ) 
    { 
        printf("[tcp server] listen error!\n "); 
          return -1; 
    } 
 
    // 注: ServerEnv用于给服务线程传参,定义于serverIf.h中
    ServerEnv env; 
    while(1) 
    { 
          client_len = sizeof(client_addr); 
          clientSocket = accept(g_hServerSocket,(struct sockaddr *)&client_addr,&client_len ); 
 
          if( clientSocket < 0 ) 
          { 
              printf("[tcp server] accept error!\n" ); 
              return -1; 
          } 
            
          env.m_hSocket = clientSocket; 
           
          // add new tcp server thread 
          add_new_tcp_process_thr(&env); 
    } 
 
    return 0; 

 
int close_port() 

    printf("close server port, stop listen!\n"); 
 
    close(g_hServerSocket); 
 
    return 0; 

四、服务器端服务线程代码
// 
//  COPYRIGHT NOTICE 
//  Copyright (c) 2011, 华中科技大学 ticktick(版权声明) 
//  All rights reserved. 
//  
/// @file    serverIf.c   
/// @brief   tcp服务线程代码 
/// 
/// 实现tcp服务线程相关接口 
/// 
/// @version 1.0    
/// @author  lujun  
/// @E-mail   lujun.hust@gmail.com  
/// @date    2011/08/21 
// 
// 
//  修订说明: 
// 
 
#include "serverIf.h" 
#include "../include/Command.h" 
#include <sys/socket.h> 
#include <stdio.h> 
#include <string.h> 
#include <errno.h> 
 
int add_new_tcp_process_thr( ServerEnv *envp ) 

    pthread_t tcpThr; 
 
    if( pthread_create( &tcpThr,NULL,tcpServerThrFxn,envp ) ) 
    { 
        printf("tcp thread create fail!\n"); 
        return -1; 
    } 
 
    printf("tcp thread has been created!\n"); 
 
    return 0; 

 
int save_user_name( char * pUsername ) 

    printf("ok,user name saved,username=%s\n",pUsername); 
 
    return 0; 

 
int save_user_age( int age ) 

    printf("ok,user age saved,userage=%d\n",age); 
 
    return 0; 

 
void * tcpServerThrFxn( void * arg ) 

    ServerEnv * envp = (ServerEnv *)arg; 
    int socketfd = envp->m_hSocket; 
    int returnBytes; 
 
    ServerPack sPack; 
    ReturnPack rPack; 
    memcpy(rPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN); 
 
    while(1) 
    { 
          // read cmd from client 
          returnBytes = recv(socketfd,(uint8_t *)&sPack,SERVER_PACK_LEN,0); 
          if( returnBytes == SERVER_PACK_LEN ) 
          { 
              //printf("ok,recv %d bytes! \n",SERVER_PACK_LEN); 
          } 
          else if( returnBytes <= 0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN ) 
          { 
              printf("disconnected or error occur! errno=%d\n ",errno); 
              break; 
          } 
          else
          { 
              continue; 
          } 
 
          // check the header 
          if ( memcmp( sPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEADER_LEN ) != 0 ) 
          { 
              rPack.returnCMD = DVS_RETURN_INVLID_HEADER; 
 
              // return error info to client 
              returnBytes = send(socketfd,(uint8_t *)&rPack,RETURN_PACK_LEN,0 ) ; 
              if( returnBytes < RETURN_PACK_LEN) 
              { 
                  printf("send error!\n"); 
                    continue; 
              } 
          } 
 
          // analyse cmd 
          rPack.returnCMD = DVS_RETURN_SUCCESS; 
          switch( sPack.serverCMD ) 
          { 
          case CMD_SAVE_USER_NAME: 
               {   
                     if( save_user_name(sPack.Parameters.UserName.username) != 0) 
                     { 
                         rPack.returnCMD = DVS_RETURN_FAIL; 
                     } 
               } 
          break; 
          case CMD_SAVE_USER_AGE: 
              { 
                   if( save_user_age(sPack.Parameters.UserAge.userage) != 0) 
                     {  
                         rPack.returnCMD = DVS_RETURN_FAIL; 
                     } 
              } 
          break; 
          default: 
             {   
                   rPack.returnCMD = DVS_RETURN_INVLID_CMD; 
             } 
          break; 
          }  
     
          // return result info to client 
          returnBytes = send(socketfd,(uint8_t *)&rPack,RETURN_PACK_LEN,0 ); 
          if( returnBytes < RETURN_PACK_LEN ) 
          { 
            printf("send error!\n"); 
            continue;        
          } 
    } 
 
    printf("close session socket!"); 
 
    // close socket 
    close(socketfd); 
 
    return (void*)0; 

五、客户端测试代码
// 
//  COPYRIGHT NOTICE 
//  Copyright (c) 2011, 华中科技大学 ticktick(版权声明) 
//  All rights reserved. 
//  
/// @file    main.c   
/// @brief   tcp客户端测试代码 
/// 
/// 实现 tcp客户端测试 
/// 
/// @version 1.0    
/// @author  lujun  
/// @E-mail   lujun.hust@gmail.com  
/// @date    2011/08/21 
// 
// 
//  修订说明: 
// 
 
#include <stdio.h> 
 
#include "client.h" 
 
#define LOCAL_IP_STR "127.0.0.1" 
#define DEST_IP_STR  "192.201.0.8" 
#define DEST_PORT    8000 
 
int main(int argc, char **argv) 

    int i =0; 
 
    printf("tcp test start!\n"); 
 
    if( connect_server(DEST_IP_STR,DEST_PORT) != 0) 
    { 
        return -1; 
    } 
 
    ServerPack sPack; 
    sPack.serverCMD = CMD_SAVE_USER_AGE; 
    sPack.Parameters.UserAge.userage = 20; 
 
    if( send_cmd(sPack) == -1 ) 
      { 
          printf("send cmd fail!\n"); 
      } 
 
    getchar(); 
    getchar(); 
 
    close_connect(); 
 
    printf("tcp test pass!\n"); 
 
    return 0; 

六、服务器端测试代码
// 
//  COPYRIGHT NOTICE 
//  Copyright (c) 2011, 华中科技大学 ticktick(版权声明) 
//  All rights reserved. 
//  
/// @file    main.c   
/// @brief   tcp客户端代码 
/// 
/// 实现tcp服务器端测试的代码 
/// 
/// @version 1.0    
/// @author  lujun  
/// @E-mail   lujun.hust@gmail.com  
/// @date    2011/08/21 
// 
// 
//  修订说明: 
// 
 
#include <stdio.h> 
 
#include "server.h" 
#include "serverIf.h" 
 
#define LOCAL_PORT 8000 
 
int main(int argc, char **argv) 

    printf("tcp test start!\n"); 
 
    if( open_port(LOCAL_PORT) != 0) 
    { 
        return -1; 
    } 
 
    close_port(); 
 
    printf(" close port !\n"); 
 
    return 0; 

七、总结和说明
    本文后面的附件中有完整的代码,欢迎 下载 使用。编译方法,把代码文件夹都拷贝到linux下,在本代码文件夹的根目录下,运行make,即可生成对应的可执行文件。在运行测试程序的时候,请先执行server.out,然后执行client.out
        如果发现本代码的任何bug或者有任何建议,欢迎留言或者来信交流
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值