本文将实现一个echo(回传)服务器的程序:
(1)客户从标准输入读入一行文本,并写个服务器;
(2)服务器从网络输入读入这行文本,并回传给客户;
(3)客户从网络输入读入这行回传文本,并显示在标准输出上
这个例子是一个尽管简单但是有效的网络应用的例子,实现任何客户/服务器网络应用所需的所有基本步骤都可以通过本例子阐明。如果你想把本例扩充成自己的应用程序,只需修改服务器对来自客户的输入的处理过程。
TCP/IP 回传服务器
这个程序描述了一个简单的 TCP/IP 服务器, 它将接受一个来自客户端的应用的连接,接收一行文本,将这行文本回传到客户端并关闭连接。
这个程序诠释了一个经典的 TCP/IP 服务端程序的处理过程,总体而言,分下面几步:
- 调用 socket()来创建一个连接
- 将IP地址设置成 INADDR_ANY(服务器将监听任何IP地址)并且使用任意你希望使用的端口,来创建并初始化一个 socket 地址结构.
- 调用 bind() 函数来将socket地址绑定到套接字.
- 调用 listen() 函数表明这是一个主动的套接字,我们想要接收传入的请求而不是向外的
- 进入一个循环,在这我们将:
- 调用 accept() 来等待一个连接到来
- 为新的连接请求提供服务
- close() 连接,接着用循环等待另一个新的连接
服务端进入无限循环,它自身没有没有机制关闭, 我们需要使用类似kill的命令来终止它。
接下来看一下代码
helper.h
#include <unistd.h> /* ssize_t类型 */ #define LISTENQ (1024) /* 指定系统内核允许在这个监听描述符上排队的最大客户连接数 */ ssize_t Readline(int fd, void *vptr, size_t maxlen); ssize_t Writeline(int fc, const void *vptr, size_t maxlen);
helper.c
#include "helper.h" #include <sys/socket.h> #include <unistd.h> #include <errno.h> /* 从一个套接字读取一行文本 */ ssize_t Readline(int sockd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *buffer; buffer = vptr; for ( n = 1; n < maxlen; n++ ) { if ( (rc = read(sockd, &c, 1)) == 1 ) { *buffer++ = c; if ( c == '\n' ) break; } else if ( rc == 0 ) { if ( n == 1 ) return 0; else break; } else { if ( errno == EINTR ) continue; return -1; } } *buffer = 0; return n; } /* 向一个套接字写入一行文本 */ ssize_t Writeline(int sockd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *buffer; buffer = vptr; nleft = n; while ( nleft > 0 ) { if ( (nwritten = write(sockd, buffer, nleft)) <= 0 ) { if ( errno == EINTR ) nwritten = 0; else return -1; } nleft -= nwritten; buffer += nwritten; } return n; }
echoserv.c
#include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <unistd.h> #include "helper.h" #include <stdlib.h> #include <stdio.h> #define ECHO_PORT (2002) #define MAX_LINE (1000) int main(int argc, char *argv[]) { int list_s; /* 监听 socket */ int conn_s; /* 连接 socket */ short int port; /* 端口号 */ struct sockaddr_in servaddr; /* socket地址结构 */ char buffer[MAX_LINE]; /* 字符缓冲 */ char *endptr; /* 从命令行获取端口号,如果没有参数提供就使用默认的端口号 */ if (argc == 2) { port = strtol(argv[1], &endptr, 0); if (*endptr) { fprintf(stderr, "ECHOSERV: Invalid port number.\n"); exit(EXIT_FAILURE); } } else if (argc < 2) { port = ECHO_PORT; } else { fprintf(stderr, "ECHOSERV: Invalid arguments.\n"); exit(EXIT_FAILURE); } /* 创建监听 socket */ if ((list_s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "ECHOSERV: Error creating listening socket.\n"); exit(EXIT_FAILURE); } /* 将套接字地址结构中的所有字节都清0,并为其赋值 */ memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); /* 绑定我们的套接字地址到监听套接字,并调用 listen() */ if (bind(list_s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { fprintf(stderr, "ECHOSERV: Error calling bind()\n"); exit(EXIT_FAILURE); } if (listen(list_s, LISTENQ) < 0) { fprintf(stderr, "ECHOSERV: Error calling listen()\n"); exit(EXIT_FAILURE); } /* 进入一个无限循环来响应客户端请求并回传输入 */ while (1) { /* 等待连接,然后 accept() */ if ((conn_s = accept(list_s, NULL, NULL)) < 0) { fprintf(stderr, "ECHOSERV: Error calling accept()\n"); exit(EXIT_FAILURE); } /* 收到从已连接的套接字输入的一个文本行,然后简单地写入相同的套接字. */ Readline(conn_s, buffer, MAX_LINE - 1); Writeline(conn_s, buffer, strlen(buffer)); /* 关闭已连接的 socket */ if (close(conn_s) < 0) { fprintf(stderr, "ECHOSERV: Error calling close()\n"); exit(EXIT_FAILURE); } } }
接下来看看TCP/IP 客户端,它将用来连接到回传服务器并回传一个字符串
这个程序诠释了一个经典的 TCP/IP 客户端程序的处理过程,总体而言,分下面几步:
- 调用 socket() 来创建一个套接字
- 使用地址族(这里是 AF_INET)创建和初始化一个套接字地址结构,服务器的远程 IP 地址和服务器监听的端口
- 通过调用connect() 主动连接到服务器
- 与服务器通信, 在这种情况下允许服务器执行主动关闭
代码如下:
#include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <unistd.h> #include "helper.h" #include <stdlib.h> #include <string.h> #include <stdio.h> #define MAX_LINE (1000) int ParseCmdLine(int argc, char *argv[], char **szAddress, char **szPort); int main(int argc, char *argv[]) { int conn_s; /* 连接套接字 */ short int port; /* 端口号 */ struct sockaddr_in servaddr; /* 套接字地址结构 */ char buffer[MAX_LINE]; /* 字符缓存 */ char *szAddress; /* 保存远程IP地址 */ char *szPort; /* 保存远程端口 */ char *endptr; /* 获取命令行参数 */ ParseCmdLine(argc, argv, &szAddress, &szPort); /* 设置远程端口 */ port = strtol(szPort, &endptr, 0); if (*endptr) { printf("ECHOCLNT: Invalid port supplied.\n"); exit(EXIT_FAILURE); } /* 创建监听套接字 */ if ((conn_s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "ECHOCLNT: Error creating listening socket.\n"); exit(EXIT_FAILURE); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); /* 设置远程IP地址 */ if (inet_aton(szAddress, &servaddr.sin_addr) <= 0) { printf("ECHOCLNT: Invalid remote IP address.\n"); exit(EXIT_FAILURE); } /* connect()连接到远程回传服务器 */ if (connect(conn_s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("ECHOCLNT: Error calling connect()\n"); exit(EXIT_FAILURE); } /* 从用户获取发送到回传服务器的字符串 */ printf("Enter the string to echo: "); fgets(buffer, MAX_LINE, stdin); /* 将字符串发送给回传服务器, 并and retrieve response */ Writeline(conn_s, buffer, strlen(buffer)); Readline(conn_s, buffer, MAX_LINE - 1); /* 输出回传的字符串 */ printf("Echo response: %s\n", buffer); return EXIT_SUCCESS; } int ParseCmdLine(int argc, char *argv[], char **szAddress, char **szPort) { int n = 1; while (n < argc) { if (!strncmp(argv[n], "-a", 2) || !strncmp(argv[n], "-A", 2)) { *szAddress = argv[++n]; } else if (!strncmp(argv[n], "-p", 2) || !strncmp(argv[n], "-P", 2)) { *szPort = argv[++n]; } else if (!strncmp(argv[n], "-h", 2) || !strncmp(argv[n], "-H", 2)) { printf("Usage:\n\n"); printf(" timeclnt -a (remote IP) -p (remote port)\n\n"); exit(EXIT_SUCCESS); } ++n; } return 0; }
接下来运行程序,首先编译并在后台运行服务端程序,接着编译并带命令行参数运行客户端程序:
wu@ubuntu:~/linuxc/echosevcli$ gcc helper.c echoserv.c -o echoserv
wu@ubuntu:~/linuxc/echosevcli$ gcc helper.c echoclnt.c -o echoclnt
wu@ubuntu:~/linuxc/echosevcli$ ./echoserv 3357 &
[1] 9872
wu@ubuntu:~/linuxc/echosevcli$ ./echoclnt -a 127.0.0.1 -p 3357
Enter the string to echo: echo me, thanks.
Echo response: echo me, thanks.
wu@ubuntu:~/linuxc/echosevcli$
Comments