在網絡編程編程中,咱們常常會遇到這樣一種C/S架構,服務器端(Server)監聽客戶端(Client)發送過來的命令,而後解析該命令,並作對應的處理,最後返回處理結果(例如成功或者失敗及緣由)給客戶端。linux
最近,在Linux下作網絡編程,涉及的就是上面的這種需求,簡單地整理了下本身的代碼,分享在這裏吧,供初學者參考。編程
首先說一下編程思路吧。服務器
在這種狀況客戶端必須實現的的接口有:鏈接服務器、發送、斷開鏈接。網絡
服務器端,有一個主線程,用於監聽客戶端的鏈接請求,一旦有新客戶端鏈接,則建立一個新的socket及線程專門服務這個客戶端。這個服務線程專門監聽該客戶端的命令,而且解析命令進行服務器,直到客戶端斷開鏈接或者發送關閉鏈接的命令。session
另外,須要涉及一個通訊協議,約定命令的包頭、命令的識別碼、命令的參數。架構
思路就說到這兒了,下面的相關代碼,附件中有完整的代碼,包含了Makefile文件。socket
1、通訊協議設計tcp
- //////////////////////////////////////////////////////////////////////////
- // 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_
2、客戶端代碼ide
- //////////////////////////////////////////////////////////////////////////
- // 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;
- }
3、服務器主線程代碼函數
- //////////////////////////////////////////////////////////////////////////
- // 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;
- }
4、服務器端服務線程代碼
- //////////////////////////////////////////////////////////////////////////
- // 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;
- }
5、客戶端測試代碼
- //////////////////////////////////////////////////////////////////////////
- // 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;
- }
6、服務器端測試代碼
- //////////////////////////////////////////////////////////////////////////
- // 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;
- }
7、總結和說明
本文後面的附件中有完整的代碼,歡迎下載使用。編譯方法,把代碼文件夾都拷貝到linux下,在本代碼文件夾的根目錄下,運行make,便可生成對應的可執行文件。在運行測試程序的時候,請先執行server.out,而後執行client.out
另外,若是須要轉載本文或者代碼,請註明出處,本代碼來自ticktick的博客:http://ticktick.blog.51cto.com 謝謝。
若是發現本代碼的任何bug或者有任何建議,歡迎留言或者來信交流。