1.4 Echo服务器程序

Echo服务器程序EchoSrv,在文件夹TEchoSrv中,等待客户的连接,接收客户发送过来的任何数据,并把它们简单地发回给客户端。服务器程序最多有一个参数,是服务器侦听的端口,没有使用协议规定的默认端口7。程序1.3是基于TCP协议实现的Echo服务器程序,它与1.3节的客户端程序一起工作。

程序1.3 Echo服务器程序 [EchoSrv.c]

1  #include <stdio.h>
2  #include <winsock2.h>
3  #pragma comment(lib, "ws2_32") /* WinSock使用的库函数 */
4  #define ECHO_DEF_PORT     7 /* 侦听的默认端口 */
5  #define ECHO_BUF_SIZE   256 /* 缓冲区的大小   */
6  int main(int argc, char **argv)
7  {
8      WSADATA wsa_data;
9      SOCKET  echo_soc = 0,     /* 侦听socket句柄    */
10               acpt_soc = 0;
11      struct sockaddr_in serv_addr,   /* socket的本地地址 */
12                                  clnt_addr;   /* socket的远端地址 */
13      unsigned short port = ECHO_DEF_PORT;
14      int result = 0;
15      int addr_len = sizeof(struct sockaddr_in);
16      char recv_buf[ECHO_BUF_SIZE];
17      if (argc == 2)
18           port = atoi(argv[1]);
19      WSAStartup(MAKEWORD(2,0), &wsa_data);/* 初始化WinSock资源 */
20      echo_soc = socket(AF_INET, SOCK_STREAM, 0); /* 创建socket */
      /* socket的本地地址 */
21      serv_addr.sin_family = AF_INET;
22      serv_addr.sin_port = htons(port);
23      serv_addr.sin_addr.s_addr = INADDR_ANY;
24      result = bind(echo_soc, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
25      if (result == SOCKET_ERROR)
26      {
27           printf("[Echo Server] bind error: %d\n", WSAGetLastError());
28           closesocket(echo_soc);
29           return -1;
30      }
31      listen(echo_soc, SOMAXCONN);
32      printf("[Echo Server] is running ... ...\n");
33      while (1)
34      {
35           acpt_soc = accept(echo_soc, (struct sockaddr *)&clnt_addr, &addr_len);
36           if (acpt_soc == INVALID_SOCKET)
37           {
38               printf("[Echo Server] accept error: %d\n", WSAGetLastError());
39               break;
40           }
41           result = recv(acpt_soc, recv_buf, ECHO_BUF_SIZE, 0);
42           if (result > 0)
43           {
44               recv_buf[result] = 0;
45               printf("[Echo Server] receives: \"%s\", from %s\r\n",
46                      recv_buf, inet_ntoa(clnt_addr.sin_addr));
47               result = send(acpt_soc, recv_buf, result, 0);
48           }
49           closesocket(acpt_soc);
50      }
51      closesocket(echo_soc);
52      WSACleanup();
53      return 0;
54  }

头和库文件

第1~5行,包含的头文件、链接的库文件、定义的常量,与客户端程序是一样的。

命令行参数

第17~18行,检查命令行参数,服务器程序可以有一个参数,指定服务器的端口号,没有时使用默认端口号。

绑定地址和端口号

第21~30行,指定服务器的地址和端口号,地址和端口号都是网络字节序。此处指定的服务器地址是INADDR_ANY,如果主机有多个网络接口,允许服务器在任意网络接口上接受客户的连接。如果服务器要限定在特定网络接口上接受连接,需要明确指定该网络接口的IP地址。函数bind把服务器的地址和端口绑定到socket套接口上。

侦听

第31行,listen要求socket接受到达的连接,第二个参数规定了可以接受的未完成的最大连接数量,值为SOMAXCONN要求底层来确定合理的最大连接数量。侦听函数listen只适用于面向连接的socket,如socket类型为SOCK_STREAM,它把socket转变到被动模式,这是服务器使用的典型功能。

处理客户连接

第33~50行,这是一个无限循环,处理客户的连接请求,并完成与客户的通信。

第35行,服务器调用accept后处于睡眠状态,等待客户的连接请求。连接的过程要经历三次握手,只有当握手完成时,函数accept才会返回。返回值是一个新的套接口描述符,已经处于连接状态,与新客户的通信都是用这个新的套接口描述符。函数accept的第二、第三个参数是对方的地址信息,地址的格式由建立连接时的地址簇确定,如果这两个参数为NULL,则不返回对方的地址信息。

第41~48行,服务器调用recv接收客户程序发送的数据,这里假定数据的长度小于ECHO_BUF_SIZE字节,如果没有错误,第45行把数据打印出来。

第47行,把从客户程序接收到的数据原样发回给客户端,第49行关闭与客户端的连接。

关闭服务器

第51~52行,程序出错时才会走到这里,关闭服务器的套接口,并释放WinSock的资源。

运行结果

服务器启动后屏幕显示如下:

[Echo Server] is running ... ...

当收到客户端的连接和数据时,它显示从客户程序收到的数据,并把数据原样发送回客户端。对于1.3节中的客户程序,在同一台机器上的运行结果为:

[Echo Server] is running ... ...
[Echo Server] receives: "Hello World!", from 127.0.0.1