linux套接字编程练习之网络选课模拟

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

一.题目:

1 .实验目的

  熟悉和掌握网络编程的基本方法和步骤;

  进一步理解client/server 交互模式;

  加深学生对于网络协议概念的理解以及协议的设计和实现方法

 

2 .实验任务

使用任意网络编程语言( Java 、 C 、 VB 、 Delphi 等)编写网络选课模拟程序,它由 client 和 server 两部分组成, client 和 server 之间的通信基于 TCP 协议,并且实现 NCSP 应用层协议( Network-based Course Selection Protocol )。

 

3 . NCSP 应用层协议

3.1 NCSP Server 的功能

  当接收到 client 发送的 GETCOURSE协议消息,返回该用户已经选择的所有课程名字;

  当接收到 client 发送的 SETCOURSE协议消息,记录用户名和课程名,以便 client 使用 GETCOURSE 命令查询;

  当接收到 client 发送的 SHUTDOWN 协议消息,检查用户是否有此权限,如果有则结束 server 程序。

 

3.2 NCSP Client 的功能

允许用户选择要发送的协议消息及其附带参数,然后向 server 发送 GETCOURSE 、 SETCOURSE 或者 SHUTDOWN 命令之一,并从 server 得到应答结果,显示给用户。

 

3.3 NCSP 协议

本次实验所要实现的网络选课程序,其核心是 client 和 server 之间所遵从的共同协议: NCSP 协议。下面我们详细描述该协议。

 

3.3.1 GETCOURSE

GETCOURSE 命令由 client 发送给 server ,它由一个 ASCII 字符串组成:首先是“GETCOURSE ”,然后紧跟着一个空格( space ),然后是用户名,最后是换行符( '\n' )。 client 然后等待服务器应答:如果返回的是“ 401 User does not exist ”字符串 ,说明该用户还没有选择课程;如果返回的是“200 OK ”字符串,说明该用户已经选择了课程, client 继续接收 server 发送的课程消息,每一门课程名是一个字符串,当接收到“ . ”字符串时,说明课程名发送完毕。 Client 断开连接,重新接收用户输入。

 

当 server 接收到 GETCOURSE 命令,它解析出用户名,然后检索该用户是否已经选择了课程,如果没有,则返回“ 401 User does not exist ”字符串,关闭连接;如果用户选择了课程,则返回“ 200 OK ”字符串,然后将用户选择的课程名返回给 client ,由于用户可能选择了多门课程,因此每一门课程名作为一个字符串返回给 client ,当课程名都发送完毕,发送“ . ”字符串作为结束标志。然后断开连接,重新监听新的 client 的连接请求。

 

当 server 解析 GETCOURSE 命令,发生错误时,返回“ 300 Message format error ”字符串。

 

下面是 client 和 server 的交互:

 

C: GETCOURSE XT

 

S: 200 OK

 

S: Computer Network

 

S: Database Principle

 

S: Java Language

 

S: .

 

3.3.2 SETCOURSE

SETCOURSE 命令由 client 发送给 server ,它由一个 ASCII 字符串组成:首先是“SETCOURSE ”,然后紧跟着一个空格( space ),然后是用户名,最后是换行符( '\n' )。 client 然后等待服务器应答:如果返回的是“ 301 User exists ”字符串,说明该用户已经选择了课程(这里我们假设用户必须一次选择好课程,不能更改);如果返回的是“ 200 OK ”字符串,说明该用户还没有选择课程,可以选课。 Client 将用户输入的课程名发送给 server ,每一门课程名是一个以换行符 '\n' 终结的字符串,当课程名都发送完毕,发送“ . ”字符串作为结束标志。然后 Client 断开连接,重新接收用户输入。

 

当 server 接收到 SETCOURSE 命令,它解析出用户名,然后检索该用户是否已经选择了课程,如果已经选择,则返回“ 301 User exists ”字符串,关闭连接;如果用户还没有选择课程,则返回“ 200 OK ”字符串, server 继续接收 client 发送的课程消息,每一门课程名是一个字符串,当接收到“ . ”字符串时,说明课程名发送完毕。 Server 将用户名和其选择的所有课程存储起来,然后断开连接,重新监听新的 client 的连接请求。

 

当 server 解析 SETCOURSE 命令,发生错误时,返回“ 300 Message format error ”字符串。

 

下面是 client 和 server 的交互:

 

C: SETCOURSE XT

 

S: 200 OK

 

C: Computer Network

 

C: Database Principle

 

C: Java Language

 

C: .

 

3.3.3 SHUTDOWN

SHUTDOWN 命令由 client 发送给 server ,它由一个 ASCII 字符串组成:首先是“SHUTDOWN ”,然后紧跟着一个空格( space ),然后是用户名,最后是换行符( '\n' )。 client 发送完毕后,接收 server 的响应结果,显示在屏幕上,然后关闭连接。

 

当 server 接收到 SHUTDOWN 命令,解析出用户名,如果此用户没有关闭 server 的权限,则返回“ 201 User not allowed to execute this command ”字符串;如果此用户具有关闭 server 的权限,则返回“ 200 OK ”字符串,然后关闭连接,结束 server 程序。

 

当 server 解析 SHUTDOWN 命令,发生错误时,返回“ 300 Message format error ”字符串。

 

下面是 client 和 server 的交互:

 

C: SHUTDOWN XT

 

S: 200 OK

 

4 .编程提示

  可以采用文本或者图形界面(GUI )进行输入和输出;

  协议命令的打包和解析是编制程序要考虑的关键问题之一;

  server 采用什么样的数据结构存储用户及其所选择的课程名也是编制程序要考虑的关键问题之一。为了简化程序,可以规定一个用户最多选择的课程数。

 

5 .实验报告提交内容

  server 源程序和 client 源程序,并且加上必要的注释(以便表明你理解代码的含义)

  实验报告

 

6 .实验成绩评分标准

  正确性 (70%)

  文档和注释 (20%)

  程序风格 (10%)

 




二.整体思路:

在文件中存储用户和课程信息,每一行存储一个用户或课程信息,然后从SETCOURSE命令为切入点,因为要先SET然后才能GET,然后一步一步的添加功能。对于权限问题,我采取的是随机生成一个0或1,然后加到用户名后面,0为特权用户,1为普通用户。



三.注意点:

在成功的运行了第一次之后,再次启动服务器端程序时,./server就会变得邪恶起来,在bind()这个函数中会出现了Address already in use这个错误。当出现这个错误时,我在百度上搜到的资料如下:

下面是IBM官网上对这一情况的具体解释,参见

http://www.ibm.com/developerworks/cn/linux/l-sockpit/

     bind 普遍遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回 EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。

等待 TIME_WAIT 结束可能是令人恼火的一件事,特别是如果您正在开发一个套接字服务器,就需要停止服务器来做一些改动,然后重启。幸运的是,有方法可以避开 TIME_WAIT 状态。可以给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用。

考虑清单 3 的例子。在绑定地址之前,我以 SO_REUSEADDR 选项调用 setsockopt。为了允许地址重用,我设置整型参数(on)为 1 (不然,可以设为 0 来禁止地址重用)。

即在服务器程序中,在创建一个套接字之后,在绑定本地地址之前,调用下面的函数:

1
2
3
4
5
if  (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse,  sizeof (reuse)) < 0)
        {
                 perror ( "setsockopet" );
                 return  -1;
        }




四.程序运行结果:

wKioL1c70g_SvSLAAACK1kQg8m8804.png


wKiom1c70U3Bl5bNAABEUTMkPWI047.png



wKioL1c70lSy0E8GAAAxb-Wi_ho122.png


wKioL1c70oeiPjxaAABQ_MSnh_I886.png





五.相关代码:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
   /****************************************                                                                                                 
   2     > File Name:server.c
   3     > Author:xiaoxiaohui
   4     > mail:1924224891@qq.com
   5     > Created Time:2016年05月15日 星期日 16时06分03秒
   6 ****************************************/
   7                                                                                                                                           
   8 #include<stdio.h>
   9 #include<stdlib.h>
  10 #include<sys/types.h>
  11 #include<sys/socket.h>
  12 #include<unistd.h>
  13 #include <arpa/inet.h>
  14 #include<netinet/in.h>
  15 #include<fcntl.h>
  16 #include<string.h>
  17 #include<fcntl.h>
  18 #include<pthread.h>
  19 
  20 #define LEN 1024
  21  const  int  PORT = 8080;
  22  int  listenSock, linkSock;
  23  struct  sockaddr_in lockal;
  24  struct  sockaddr_in client;
  25  char  buf[LEN];
  26 pthread_t tid;
  27 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;    //互斥锁  因为buf是全局的,所以只能有一个线程访问buf
  28 
  29 
  30  void  ListenSock()     //建立一个监听套接字
  31 {
  32     listenSock = socket(AF_INET, SOCK_STREAM, 0);    //返回一个文件描述符
  33 
  34     lockal.sin_family = AF_INET;
  35     lockal.sin_addr.s_addr = htonl(INADDR_ANY);     //适合多网卡
  36     lockal.sin_port = htons(PORT);
  37 
  38      int  on = 1;
  39      if ( (setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &on,  sizeof (on))) < 0)     //容许端口重用
  40     {
  41          perror ( "setsockopt " );  
  42          exit (EXIT_FAILURE);  
  43     }
  44 
  45      if ( bind(listenSock, ( struct  sockaddr*)&lockal,  sizeof (lockal)) < 0)    //绑定本地地址
  46     {
  47          perror ( "bind" );
  48          exit (0);
  49     }
  50 
  51      if ( listen(listenSock, 5) < 0)     //进入监听状态
  52     {
  53          perror ( "listen" );
  54          exit (1);
  55     }
  56 }
  57 
  58  int  LinkSock()            //返回一个已链接套接字
  59 {
  60      int  size =  sizeof (lockal);
  61     linkSock = accept(listenSock, ( struct  sockaddr*)&client, &size);   //创建一个已链接套接字
  62      if (linkSock < 0)
  63     {
  64          perror ( "accept" );
  65          return  -1;
  66     }
  67 
  68      return  linkSock;
  69 }
  70 
  71  int  checkUser( char * ptr)     //判断文件中是否有字符串ptr  有则返回1  无则返回0
  72 {
  73      if (ptr == NULL)
  74     {
  75          printf ( "checkUser parameter is error\n" );
  76          return  -1;
  77     }                                                                                                                                     
  78 
  79      while (*ptr !=  ' '  )
  80     {
  81         ptr++;
  82     }
  83     ptr++;                     //此时ptr指向用户名
  84 
  85      FILE * fp =  fopen ( "message.txt" "r" );
  86      if (fp == NULL)
  87     {
  88          printf ( "fopen is error!\n" );
  89          exit (3);
  90     }
  91      char * message = NULL;
  92      char  buf[LEN];
  93      while ( (message =  fgets (buf, LEN, fp)) != NULL)  
  94     {
  95          if strstr (message, ptr) != NULL)        //判断ptr是否为message的子串
  96         {
  97              fclose (fp);
  98              return  1;
  99         }
100     }
101      fclose (fp);
102      printf ( "there is not exist this user!\n" );
103      return  0;
104 }
105 
106  int  checkMode( char * ptr)    //检查命令格式 如果返回1则代表格式正确 否则格式错误
107 {
108      if (ptr == NULL)
109     {
110          printf ( "checkMode parameter is error\n" );
111          return  -1;
112     }
113                                                                                                                                           
114      int  count = 0;
115      while (*ptr !=  '\0' )    // 判断ptr中有多少个空格字符
116     {
117          if (*ptr ==  ' ' )
118         {
119             count++;
120         }
121         ptr++;
122     }
123      return  count;                                                                                                                         
124 }
125 
126  void  setcourse()
127 {
128      if ( checkMode(buf) == 1)      //如果只存在一个空格符则命令格式匹配
129     {
130          if ( checkUser(buf) == 1)       //用户名存在 说明已经选择了课程
131         {
132              char  message[] =  "301 User exist" ;
133             write(linkSock, message,  strlen (message));
134         }
135          else                      //用户名不存在,说明还没选择课程
136         {
137              FILE * fp =  fopen ( "message.txt" "a" );    //以追加的方式打开文件
138              fputs (buf,fp);                         //把命令和用户名写到文件中
139 
140              int  root = 0;             //   以linux为参考  0表示超级用户 1表示普通用户
141             root =  rand () % 1;        //随机生成0或1
142              if (root == 0)
143             {
144                  fputc ( '0' , fp);