基于Linux的socket编程模板

基于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或者有任何建议,欢迎留言或者来信交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值