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