在网络编程编程中,咱们常常会遇到这样一种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或者有任何建议,欢迎留言或者来信交流。