基于Linux的socket编程模板
基于Linux的socket编程模板
在网络编程编程中,我们经常会遇到这样一种C/S架构,服务器端(Server)监听客户端(Client)发送过来的命令,然后解析该命令,并做对应的处理,最后返回处理结果(例如成功或者失败及原因)给客户端。
最近,在Linux下做网络编程,涉及的就是上面的这种需求,简单地整理了下自己的代码,分享在这里吧,供初学者参考。
首先说一下编程思路吧。
在这种情况客户端必须实现的的接口有:连接服务器、发送、断开连接。
服务器端,有一个主线程,用于监听客户端的连接请求,一旦有新客户端连接,则创建一个新的socket及线程专门服务这个客户端。这个服务线程专门监听该客户端的命令,并且解析命令进行服务器,直到客户端断开连接或者发送关闭连接的命令。
另外,需要涉及一个通信协议,约定命令的包头、命令的识别码、命令的参数。
思路就说到这儿了,下面的相关代码,附件中有完整的代码,包含了Makefile文件。
一、通信协议设计
1.////////////////////////////////////////////////////////////////////////
//
2.// COPYRIGHT NOTICE
3.// Copyright (c) 2011, 华中科技大学 ticktick(版权声明)
4.// All rights reserved.
5.//
6./// @file Command.h
7./// @brief 命令包声明文件
8.///
9./// 定义各种TCP命令包以及相关结构体
10.///
11./// @version 1.0
12./// @author lujun
13./// @E-mail lujun.hust@https://www.360docs.net/doc/5511525006.html,
14./// @date 2011/08/21
15.//
16.//
17.// 修订说明:
18.////////////////////////////////////////////////////////////////////////
//
19.
20.#ifndef COMMAND_H_
21.#define COMMAND_H_
22.
23.typedef unsigned char uint8_t;
24.
25.// TCP CMD header len
26.#define TCP_CMD_HEADER_LEN 8
27.
28.// CMD header
29.static uint8_t TCP_CMD_HEADER_STR[TCP_CMD_HEADER_LEN] = { 0xAA,0xA1,0xA2
,0xA3,0xA4,0xA5,0xA6,0xFF };
30.
31.// max user name len
32.#define MAX_USER_NAME_LEN 20
33.
34.// server cmd struct
35.typedef enum _ServerCMD
36.{
37. CMD_SAVE_USER_NAME, // save user name
38. CMD_SAVE_USER_AGE, // save user age
39.
40.}ServerCMD;
41.
42.// return cmd
43.typedef enum _ReturnCMD
44.{
45. DVS_RETURN_SUCCESS = 0,
46. DVS_RETURN_FAIL,
47. DVS_RETURN_TIMEOUT,
48. DVS_RETURN_INVLID_HEADER,
49. DVS_RETURN_INVLID_CMD,
50. DVS_RETURN_INVLID_PRM,
51.
52.}ReturnCMD;
53.
54.// 1bytes aligning
55.#pragma pack( push, 1 )
56.
57.// server pack from client
58.typedef struct _ServerPack
59.{
60.// cmd header
61. uint8_t cmdHeader[TCP_CMD_HEADER_LEN];
62.
63.// command id
64. ServerCMD serverCMD;
65.
66.// cmd param
67.union
68. {
69.// save user name
70.struct
71. {
72.// user name
73.char username[MAX_USER_NAME_LEN];
74.
75. }UserName;
76.
77.// save user age
78.struct
79. {
80.// user age
81.int userage;
82.
83. }UserAge;
84.
85. }Parameters;
86.
87.}ServerPack;
88.
89.// return pack from server
90.typedef struct _ReturnPack
91.{
92.// cmd header
93. uint8_t cmdHeader[TCP_CMD_HEADER_LEN];
94.
95.// return cmd
96. ReturnCMD returnCMD;
97.
98.}ReturnPack;
99.
100.#pragma pack( pop )
101.
102.#define SERVER_PACK_LEN sizeof(ServerPack)
103.#define RETURN_PACK_LEN sizeof(ReturnPack)
104.
105.#endif // COMMAND_H_
二、客户端代码
1.////////////////////////////////////////////////////////////////////////
//
2.// COPYRIGHT NOTICE
3.// Copyright (c) 2011, 华中科技大学 ticktick(版权声明)
4.// All rights reserved.
5.//
6./// @file client.c
7./// @brief tcp客户端代码
8.///
9./// 实现tcp客户端的相关接口
10.///
11./// @version 1.0
12./// @author lujun
13./// @E-mail lujun.hust@https://www.360docs.net/doc/5511525006.html,
14./// @date 2011/08/21
15.//
16.//
17.// 修订说明:
18.////////////////////////////////////////////////////////////////////////
//
19.
20.#include
21.#include
22.#include
23.#include
24.#include
25.#include
26.#include
27.#include "client.h"
28.
29.// socket handle
30.int g_hSocket;
31.
32.int connect_server( char *destIp, int destPort )
33.{
34.int result;
35.struct sockaddr_in address;
36.
37.// create a socket
38. g_hSocket = socket(AF_INET,SOCK_STREAM,0);
39.
40.// set server addr
41. address.sin_family = AF_INET;
42.
43.// use server ip and listen port to connect
44. address.sin_addr.s_addr = inet_addr( destIp );
45. address.sin_port = htons(destPort);
46.
47.// connect tcp server
48. result = connect(g_hSocket,(struct sockaddr *)&address,sizeof(addres
s) );
49.if( result == -1 )
50. {
51. printf("[tcp client] can't connect server !\n");
52.return -1;
53. }
54.
55.return 0;
56.}
57.
58.int close_connect()
59.{
60. printf("close connect with server !\n ");
61.
62. close(g_hSocket);
63.
64.return 0;
65.}
66.
67.int send_cmd( ServerPack sPack )
68.{
69.int recvBytes = 0;
70.int sendBytes = 0;
71. ReturnPack rPack;
72.
73.// add cmd header
74. memcpy(sPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN);
75.
76.// send cmd
77.while(1)
78. {
79. sendBytes = send(g_hSocket,(uint8_t *)&sPack,SERVER_PACK_LEN,0);
80.
81.if( sendBytes == SERVER_PACK_LEN )
82. {
83. printf("successfully send bytes %d\n",SERVER_PACK_LEN);
84.break;
85. }
86.else if( sendBytes <= 0 && errno != EINTR && errno != EWOULDBLOC
K && errno != EAGAIN)
87. {
88. printf("disconnected or other errors!\n");
89.return -1;
90. }
91.else
92. {
93.continue;
94. }
95. }
96.
97.// recv process result from server
98.while(1)
99. {
100. recvBytes = recv(g_hSocket,(uint8_t *)&rPack,RETURN_PACK_LEN ,0);
101.
102.if( recvBytes == RETURN_PACK_LEN )
103. {
104.break;
105. }
106.else if( recvBytes <=0 && errno != EINTR && errno != EWOUL DBLOCK && errno != EAGAIN )
107. {
108. printf("disconnected or error occur!\n close the socke t!\n");
109.return -1;
110. }
111.else
112. {
113.continue;
114. }
115. }
116.
117.// check header
118.if ( memcmp( rPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEADER _LEN ) != 0 )
119. {
120. printf("return pack header errror!\n");
121.return -2;
122. }
123.
124.// get return status
125.if( rPack.returnCMD != DVS_RETURN_SUCCESS )
126. {
127. printf("return status : fail!\n");
128.return -3;
129. }
130.
131.return 0;
132.}
133.
134.
三、服务器主线程代码
1.////////////////////////////////////////////////////////////////////////
//
2.// COPYRIGHT NOTICE
3.// Copyright (c) 2011, 华中科技大学 ticktick(版权声明)
4.// All rights reserved.
5.//
6./// @file server.c
7./// @brief tcp服务器主线程代码
8.///
9./// 实现tcp服务端监听线程相关函数
10.///
11./// @version 1.0
12./// @author lujun
13./// @E-mail lujun.hust@https://www.360docs.net/doc/5511525006.html,
14./// @date 2011/08/21
15.//
16.//
17.// 修订说明:
18.////////////////////////////////////////////////////////////////////////
//
19.
20.#include
21.#include
22.#include
23.
24.#include
25.#include
26.
27.#include "serverIf.h"
28.
29.int g_hServerSocket;
30.
31.int open_port( int localport )
32.{
33.int result;
34.int clientSocket,client_len;
35.
36.struct sockaddr_in server_addr;
37.struct sockaddr_in client_addr;
38.
39.// create a socket obj for server
40. g_hServerSocket = socket(AF_INET,SOCK_STREAM,0);
41.
42.// bind tcp port
43. server_addr.sin_family = AF_INET;
44. server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
45. server_addr.sin_port = htons(localport);
46.
47. result = bind(g_hServerSocket,(struct sockaddr *)&server_addr,sizeof
(server_addr) );
48.if( result != 0 )
49. {
50. printf("[tcp server] bind error!\n ");
51.return -1;
52. }
53.
54.// begin to listen
55. result = listen(g_hServerSocket,5);
56.if( result != 0 )
57. {
58. printf("[tcp server] listen error!\n ");
59.return -1;
60. }
61.
62. // 注: ServerEnv用于给服务线程传参,定义于serverIf.h中
63. ServerEnv env;
64.while(1)
65. {
66. client_len = sizeof(client_addr);
67. clientSocket = accept(g_hServerSocket,(struct sockaddr *)&clie
nt_addr,&client_len );
68.
69.if( clientSocket < 0 )
70. {
71. printf("[tcp server] accept error!\n" );
72.return -1;
73. }
74.
75. env.m_hSocket = clientSocket;
76.
77.// add new tcp server thread
78. add_new_tcp_process_thr(&env);
79. }
80.
81.return 0;
82.}
83.
84.int close_port()
85.{
86. printf("close server port, stop listen!\n");
87.
88. close(g_hServerSocket);
89.
90.return 0;
91.}
四、服务器端服务线程代码
1.////////////////////////////////////////////////////////////////////////
//
2.// COPYRIGHT NOTICE
3.// Copyright (c) 2011, 华中科技大学 ticktick(版权声明)
4.// All rights reserved.
5.//
6./// @file serverIf.c
7./// @brief tcp服务线程代码
8.///
9./// 实现tcp服务线程相关接口
10.///
11./// @version 1.0
12./// @author lujun
13./// @E-mail lujun.hust@https://www.360docs.net/doc/5511525006.html,
14./// @date 2011/08/21
15.//
16.//
17.// 修订说明:
18.////////////////////////////////////////////////////////////////////////
//
19.
20.#include "serverIf.h"
21.#include "../include/Command.h"
22.#include
23.#include
24.#include
25.#include
26.
27.int add_new_tcp_process_thr( ServerEnv *envp )
28.{
29. pthread_t tcpThr;
30.
31.if( pthread_create( &tcpThr,NULL,tcpServerThrFxn,envp ) )
32. {
33. printf("tcp thread create fail!\n");
34.return -1;
35. }
36.
37. printf("tcp thread has been created!\n");
38.
39.return 0;
40.}
41.
42.int save_user_name( char * pUsername )
43.{
44. printf("ok,user name saved,username=%s\n",pUsername);
45.
46.return 0;
47.}
48.
49.int save_user_age( int age )
50.{
51. printf("ok,user age saved,userage=%d\n",age);
52.
53.return 0;
54.}
55.
56.void * tcpServerThrFxn( void * arg )
57.{
58. ServerEnv * envp = (ServerEnv *)arg;
59.int socketfd = envp->m_hSocket;
60.int returnBytes;
61.
62. ServerPack sPack;
63. ReturnPack rPack;
64. memcpy(rPack.cmdHeader,TCP_CMD_HEADER_STR,TCP_CMD_HEADER_LEN);
65.
66.while(1)
67. {
68.// read cmd from client
69. returnBytes = recv(socketfd,(uint8_t *)&sPack,SERVER_PACK_LEN,
0);
70.if( returnBytes == SERVER_PACK_LEN )
71. {
72.//printf("ok,recv %d bytes! \n",SERVER_PACK_LEN);
73. }
74.else if( returnBytes <= 0 && errno != EINTR && errno != EWOULD
BLOCK && errno != EAGAIN )
75. {
76. printf("disconnected or error occur! errno=%d\n ",errno);
77.break;
78. }
79.else
80. {
81.continue;
82. }
83.
84.// check the header
85.if ( memcmp( sPack.cmdHeader, TCP_CMD_HEADER_STR, TCP_CMD_HEAD
ER_LEN ) != 0 )
86. {
87. rPack.returnCMD = DVS_RETURN_INVLID_HEADER;
88.
89.// return error info to client
90. returnBytes = send(socketfd,(uint8_t *)&rPack,RETURN_PACK_
LEN,0 ) ;
91.if( returnBytes < RETURN_PACK_LEN)
92. {
93. printf("send error!\n");
94.continue;
95. }
96. }
97.
98.// analyse cmd
99. rPack.returnCMD = DVS_RETURN_SUCCESS;
100.switch( sPack.serverCMD )
101. {
102.case CMD_SAVE_USER_NAME:
103. {
104.if( save_user_name(https://www.360docs.net/doc/5511525006.html, ername) != 0)
105. {
106. rPack.returnCMD = DVS_RETURN_FAIL;
107. }
108. }
109.break;
110.case CMD_SAVE_USER_AGE:
111. {
112.if( save_user_age(https://www.360docs.net/doc/5511525006.html,erag
e) != 0)
113. {
114. rPack.returnCMD = DVS_RETURN_FAIL;
115. }
116. }
117.break;
118.default:
119. {
120. rPack.returnCMD = DVS_RETURN_INVLID_CMD;
121. }
122.break;
123. }
124.
125.// return result info to client
126. returnBytes = send(socketfd,(uint8_t *)&rPack,RETURN_PACK_ LEN,0 );
127.if( returnBytes < RETURN_PACK_LEN )
128. {
129. printf("send error!\n");
130.continue;
131. }
132. }
133.
134. printf("close session socket!");
135.
136.// close socket
137. close(socketfd);
138.
139.return (void*)0;
140.}
五、客户端测试代码
1.////////////////////////////////////////////////////////////////////////
//
2.// COPYRIGHT NOTICE
3.// Copyright (c) 2011, 华中科技大学 ticktick(版权声明)
4.// All rights reserved.
5.//
6./// @file main.c
7./// @brief tcp客户端测试代码
8.///
9./// 实现 tcp客户端测试
10.///
11./// @version 1.0
12./// @author lujun
13./// @E-mail lujun.hust@https://www.360docs.net/doc/5511525006.html,
14./// @date 2011/08/21
15.//
16.//
17.// 修订说明:
18.////////////////////////////////////////////////////////////////////////
//
19.
20.#include
21.
22.#include "client.h"
23.
24.#define LOCAL_IP_STR "127.0.0.1"
25.#define DEST_IP_STR "192.201.0.8"
26.#define DEST_PORT 8000
27.
28.int main(int argc, char **argv)
29.{
30.int i =0;
31.
32. printf("tcp test start!\n");
33.
34.if( connect_server(DEST_IP_STR,DEST_PORT) != 0)
35. {
36.return -1;
37. }
38.
39. ServerPack sPack;
40. sPack.serverCMD = CMD_SAVE_USER_AGE;
41. https://www.360docs.net/doc/5511525006.html,erage = 20;
42.
43.if( send_cmd(sPack) == -1 )
44. {
45. printf("send cmd fail!\n");
46. }
47.
48. getchar();
49. getchar();
50.
51. close_connect();
52.
53. printf("tcp test pass!\n");
54.
55.return 0;
56.}
六、服务器端测试代码
1.////////////////////////////////////////////////////////////////////////
//
2.// COPYRIGHT NOTICE
3.// Copyright (c) 2011, 华中科技大学 ticktick(版权声明)
4.// All rights reserved.
5.//
6./// @file main.c
7./// @brief tcp客户端代码
8.///
9./// 实现tcp服务器端测试的代码
10.///
11./// @version 1.0
12./// @author lujun
13./// @E-mail lujun.hust@https://www.360docs.net/doc/5511525006.html,
14./// @date 2011/08/21
15.//
16.//
17.// 修订说明:
18.////////////////////////////////////////////////////////////////////////
//
19.
20.#include
21.
22.#include "server.h"
23.#include "serverIf.h"
24.
25.#define LOCAL_PORT 8000
26.
27.int main(int argc, char **argv)
28.{
29. printf("tcp test start!\n");
30.
31.if( open_port(LOCAL_PORT) != 0)
32. {
33.return -1;
34. }
35.
36. close_port();
37.
38. printf(" close port !\n");
39.
40.return 0;
41.}
七、总结和说明
本文后面的附件中有完整的代码,欢迎下载使用。编译方法,把代码文件夹都拷贝到linux下,在本代码文件夹的根目录下,运行make,即可生成对应的可执行文件。在运行测试程序的时候,请先执行server.out,然后执行client.out