- 所谓的ECHO服务就是在屏幕打印相关的参数,相关的应用过程如下:
- 1.服务器启动之后创建服务器socket,进行相应的设置后始终调用accept(2)等待客户端的连入,客户端 正常连入之后创建一个子进程作为业务进程,对客户端进行服务,父进程 始终作为监听进程等待下一个客户端的连入;
- 2.其中为了防止僵尸进程的出现,服务器需要有处理子进程退出的功能,简便起见,程序中直接安装一个信号处理程序,用于处理SIGCHLD信号,这个过程完全异步,没有体现在流程图内。
如图为程序流程图
但是该服务器的逻辑比较简单,只能实现对一个客户端的单次连接,在连接后向客户端发出一定的信息,然后断开连接
整理socket相关操作的函数原型:
int socket(int domain, int type, int protocol);//创建socket //sock_fd = socket(AF_INET, SOCK_STREAM, 0); //sock_fd = socket(AF_INET, SOCK_DGRAM, 0); int bind(int socket, const struct sockaddr *address, socklent address_len);//绑定地址端口,绑定 成功则 绑定失败则 int connect(int socket, const struct sockaddr *address, socklent address_len);//连接 //可以看出,其中的参数完全相同, //bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) int listen(int socket, int backlog);//监听模式 backlog 是指等待连接的队列长度,但是实际上的队列可能会大于这个数字,通常都取 5。 int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);//接受连接
代码解析:
- 1.服务器相关代码:
- 创建socket:server_sock = socket(AF_INET, SOCK_STREAM, 0);
int server_sock,conn_sock;
server_sock = socket(AF_INET,SOCK_STREAM,0); //功能相关
if(server_sock<0){ //处理错误
perror("socket(2) erros");
goto create_error;
//main内定义create_error内容
}
- 2 绑定到端口:bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_add))
struct sockaddr_in server_addr,client_addr; //IP 协议使用的地址描述数据结构,定义在头文件中。 socklen_t sock_len = sizeof(client_addr); (void)memset(&server_addr,0,sock_len);//函数的功能是:将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(LISTEN_PORT); if(bind(server_sock,(struct sockaddr *)&server_addr,sizeof(server_add))){ perror("bind(2) error"); goto err; }
绑定的地址和端口主要是在 57 到 59 行填充 sturct sockaddr_in 结构完成的,服务器没有
特殊要求的情况下,绑定地址用 INADDR_ANY 监听所有地址即可,另外要注意字节序的转
换,这对于程序,尤其是对可移植性有要求的程序是一定要注意的。
- 设置为被动监听:
if(listen(server_sock,5)){
perror("listen(2) error");
goto err;
}
- 接受新的连接:
while(true){
sock_len = sizeof(client_addr);
conn_sock = accept(server_sock,(struct sockaddr *)&client_addr,&sock_len);
if (conn_sock < 0) {
if (errno == EINTR) {
continue;
}
goto end;
}
printf("client from %s:%hu connectedn",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
fflush(stdout);//清除所有的输入输出文件内容
- 子进程连接客户端:
chld_pid = fork(); //通过fork创建子进程
if (chld_pid < 0) {//小于0则表明创建失败
perror("fork(2) error");
close(conn_sock);
goto err;
} else if (chld_pid == 0){//为0则代表返回子进程
int ret_code;
close(server_sock);
ret_code = tcp_echo(conn_sock);
close(conn_sock);
printf("client from %s:%hu disconnectedn",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
exit(ret_code); //exit(x)(x不为0)都表示异常退出 exit(0)表示正常退出 exit()的参数会被传递给一些操作系统,包括UNIX,Linux,和MS DOS,以供其他程序使用。
} else {
continue;
}
执行调用之后,后面的代码
就分为父子两个进程继续运行。我们在这里让子进程去对新连接的客户端提供服务,服务完
成后就退出。父进程则继续进行监听,等待下一个客户端的连接。这样,服务器就可以并发
的对多个客户端进行服务响应。
子进程首先调用 close(2)关闭自己的监听 Socket,然后调用 tcp_echo()函数进行
服务。服务完成后关闭 Socket,打印客户端断开连接信息后退出进程。
- 服务函数
int tcp_echo(int client_fd)
{
char buff[BUFF_SIZE] = {0};
ssize_t len = 0;
len = read(client_fd, buff, sizeof(buff));
if (len < 1) {
goto err;
}
(void)write(client_fd, buff, (size_t)len);
return EXIT_SUCCESS;
err:
return EXIT_FAILURE;
}
- SIGCHLD信号处理函数,防止僵尸进程:
所有的子进程在处理完毕服务之后,会直接调用 exit(3)终止自己。在这个
时候,系统会保留他们返回的终止状态,并发送 SIGCHLD 信号给父进程,同时子进程进入
僵尸态。只有父进程处理了之后资源才能真正地完全回收。所以可用如下这样一个函数来实
现对僵尸子进程的回收处理。
void zombie_cleaning(int signo)
{
int status;
(void)signo;
while (waitpid(-1, &status, WNOHANG) > 0);
}
- 安装信号 处理函数
struct sigaction clean_zombie_act;
(void)memset(&clean_zombie_act, 0, sizeof(clean_zombie_act));
clean_zombie_act.sa_handler = zombie_cleaning;
if (sigaction(SIGCHLD, &clean_zombie_act, NULL) < 0) {
perror("sigaction(2) error");
goto err;
}
客户端程序:
- 启动后立即创建 Socket,并且直接调用 connect(2) 连接服务器,省去 bind(2)调用,系统会将刚才创建的 Socket 隐式绑定到一个随机端口上。connect(2)后直接发送数据到服务器,发送完毕后直接读取服务器回发的数据并打印接收到的数据,然后结束
#include服务器代码:#include #include #include #include #include #include #include #include #include #include #define SERVER_IP "192.168.1.133" #define SERVER_PORT ((uint16_t)7007) #define BUFF_SIZE (1024 * 4) int main(int argc, char *argv[]) { int conn_sock; char test_str[BUFF_SIZE] = "tcp echo test"; struct sockaddr_in server_addr; conn_sock = socket(AF_INET, SOCK_STREAM, 0); if (conn_sock < 0) { perror("socket(2) error"); goto create_err; } (void)memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); if (argc != 3) { server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);//inet_addr()用来将参数cp 所指的网络地址字符串转换成网络所使用的二进制数字. 网络地址字符串是以数字和点组成的字符串, 例如:"163. 13. 132. 68". } else { server_addr.sin_addr.s_addr = inet_addr(argv[1]); snprintf(test_str, BUFF_SIZE, "%s", argv[2]); } if (connect(conn_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect(2) error"); goto err; } if (write(conn_sock, test_str, strlen(test_str)) < 0) { perror("send data error"); goto err; } (void)memset(test_str, 0, BUFF_SIZE); if (read(conn_sock, test_str, BUFF_SIZE) < 0) { perror("receive data error"); goto err; } printf("%sn", test_str); fflush(stdout);//清空文件缓冲区或者标准输入输出缓冲区 return EXIT_SUCCESS; err: close(conn_sock); create_err: fprintf(stderr, "client error"); return EXIT_FAILURE; }
#include#include #include #include #include #include #include #include #include #include #include #include #include #define LISTEN_PORT ((uint16_t)7007) #define BUFF_SIZE (1024 * 4) void zombie_cleaning(int signo) { int status; (void)signo; while (waitpid(-1, &status, WNOHANG) > 0); } int tcp_echo(int client_fd) { char buff[BUFF_SIZE] = {0}; ssize_t len = 0; len = read(client_fd, buff, sizeof(buff)); if (len < 1) { goto err; } (void)write(client_fd, buff, (size_t)len); return EXIT_SUCCESS; err: return EXIT_FAILURE; } int main(void) { int server_sock, conn_sock; //连接的定义 struct sockaddr_in server_addr, client_addr; socklen_t sock_len = sizeof(client_addr); pid_t chld_pid; struct sigaction clean_zombie_act; server_sock = socket(AF_INET, SOCK_STREAM, 0); if (server_sock < 0) { perror("socket(2) error"); //C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。 goto create_err; } (void)memset(&server_addr, 0, sock_len); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(LISTEN_PORT); if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr))) { perror("bind(2) error"); goto err; } if (listen(server_sock, 5)) { perror("listen(2) error"); goto err; } (void)memset(&clean_zombie_act, 0, sizeof(clean_zombie_act)); clean_zombie_act.sa_handler = zombie_cleaning; if (sigaction(SIGCHLD, &clean_zombie_act, NULL) < 0) { perror("sigaction(2) error"); goto err; } while (true) { sock_len = sizeof(client_addr); conn_sock = accept(server_sock, (struct sockaddr *)&client_addr, &sock_len); if (conn_sock < 0) { if (errno == EINTR) { continue; } goto end; } printf("client from %s:%hu connectedn", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); fflush(stdout); chld_pid = fork(); //通过fork创建子进程 if (chld_pid < 0) {//小于0则表明创建失败 perror("fork(2) error"); close(conn_sock); goto err; } else if (chld_pid == 0){//为0则代表返回子进程 int ret_code; close(server_sock); ret_code = tcp_echo(conn_sock); close(conn_sock); printf("client from %s:%hu disconnectedn", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); exit(ret_code); //exit(x)(x不为0)都表示异常退出 exit(0)表示正常退出 exit()的参数会被传递给一些操作系统,包括UNIX,Linux,和MS DOS,以供其他程序使用。 } else { continue; } } end: perror("exit with:"); close(server_sock); return EXIT_SUCCESS; err: close(server_sock); create_err: fprintf(stderr, "server error"); return EXIT_FAILURE; }



