在网络编程编程中,我们经常会遇到这样一种C/S架构,服务器端(Server)监听客户端(Client)发送过来的命令,然后解析该命令,并做对应的处理,最后返回处理结果(例如成功或者失败及原因)给客户端。
最近,在Linux下做网络编程,涉及的就是上面的这种需求,简单地整理了下自己的代码,分享在这里吧,供初学者参考。
首先说一下编程思路吧。
在这种情况客户端必须实现的的接口有:连接服务器、发送、断开连接。
服务器端,有一个主线程,用于监听客户端的连接请求,一旦有新客户端连接,则创建一个新的socket及线程专门服务这个客户端。这个服务线程专门监听该客户端的命令,并且解析命令进行服务器,直到客户端断开连接或者发送关闭连接的命令。
另外,需要涉及一个通信协议,约定命令的包头、命令的识别码、命令的参数。
思路就说到这儿了,下面的相关代码,附件中有完整的代码,包含了Makefile文件。
一、通信协议设计
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #ifndef COMMAND_H_
- #define COMMAND_H_
-
- typedef unsigned char uint8_t;
-
-
- #define TCP_CMD_HEADER_LEN 8
-
-
- static uint8_t TCP_CMD_HEADER_STR[TCP_CMD_HEADER_LEN] = { 0xAA,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xFF };
-
-
- #define MAX_USER_NAME_LEN 20
-
-
- typedef enum _ServerCMD
- {
- CMD_SAVE_USER_NAME,
- CMD_SAVE_USER_AGE,
-
- }ServerCMD;
-
-
- 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;
-
-
- #pragma pack( push, 1 )
-
-
- typedef struct _ServerPack
- {
-
- uint8_t cmdHeader[TCP_CMD_HEADER_LEN];
-
-
- ServerCMD serverCMD;
-
-
- union
- {
-
- struct
- {
-
- char username[MAX_USER_NAME_LEN];
-
- }UserName;
-
-
- struct
- {
-
- int userage;
-
- }UserAge;
-
- }Parameters;
-
- }ServerPack;
-
-
- typedef struct _ReturnPack
- {
-
- uint8_t cmdHeader[TCP_CMD_HEADER_LEN];
-
-
- ReturnCMD returnCMD;
-
- }ReturnPack;
-
- #pragma pack( pop )
-
- #define SERVER_PACK_LEN sizeof(ServerPack)
- #define RETURN_PACK_LEN sizeof(ReturnPack)
-
- #endif // COMMAND_H_
二、客户端代码
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #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"
-
-
- int g_hSocket;
-
- int connect_server( char *destIp, int destPort )
- {
- int result;
- struct sockaddr_in address;
-
-
- g_hSocket = socket(AF_INET,SOCK_STREAM,0);
-
-
- address.sin_family = AF_INET;
-
-
- address.sin_addr.s_addr = inet_addr( destIp );
- address.sin_port = htons(destPort);
-
-
- 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;
-
-
- memcpy(sPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN);
-
-
- 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;
- }
- }
-
-
- 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;
- }
- }
-
-
- if ( memcmp( rPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEADER_LEN ) != 0 )
- {
- printf("return pack header errror!\n");
- return -2;
- }
-
-
- if( rPack.returnCMD != DVS_RETURN_SUCCESS )
- {
- printf("return status : fail!\n");
- return -3;
- }
-
- return 0;
- }
-
-
三、服务器主线程代码
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #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;
-
-
- g_hServerSocket = socket(AF_INET,SOCK_STREAM,0);
-
-
- 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;
- }
-
-
- 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_process_thr(&env);
- }
-
- return 0;
- }
-
- int close_port()
- {
- printf("close server port, stop listen!\n");
-
- close(g_hServerSocket);
-
- return 0;
- }
四、服务器端服务线程代码
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #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)
- {
-
- returnBytes = recv(socketfd,(uint8_t *)&sPack,SERVER_PACK_LEN,0);
- if( returnBytes == 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;
- }
-
-
- if ( memcmp( sPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEADER_LEN ) != 0 )
- {
- rPack.returnCMD = DVS_RETURN_INVLID_HEADER;
-
-
- returnBytes = send(socketfd,(uint8_t *)&rPack,RETURN_PACK_LEN,0 ) ;
- if( returnBytes < RETURN_PACK_LEN)
- {
- printf("send error!\n");
- continue;
- }
- }
-
-
- 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;
- }
-
-
- 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(socketfd);
-
- return (void*)0;
- }
五、客户端测试代码
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #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;
- }
六、服务器端测试代码
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #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
另外,如果需要转载本文或者代码,请注明出处,本代码来自ticktick的博客:http://ticktick.blog.51cto.com 谢谢。
如果发现本代码的任何bug或者有任何建议,欢迎留言或者来信交流。
Ctrl+Enter 发布
发布
取消