该文章仅供学习交流,严禁用于任何商业或非法用途,如有侵权,请联系本人删除!
实验名称
TCP多进程并发服务器与单进程客户端(简单回声)
实验内容
编写TCP多进程循环服务器程序与单进程客户端程序,实现以下主体功能:
- 客户端启动连接服务器之后,进入命令行交互模式。
- 操作人员在命令行窗口输入一行字符并回车后,客户端进程立刻从命令行(本质即 stdin)读取数据,并将该行信息发送给服务器。
- 服务器收到该行信息后,会将该信息原封不动的返回给客户端,即所谓消息回声(Message Echo)。
- 客户端收到服务器返回的消息回声后,将其打印输出至屏幕(本质即 stdout)。
- 客户端在从命令行收到 EXIT 指令后退出。
- 若服务器启动时设定 Established Queue的长度,即listen()第二个参数backlog为2,则最多可 以有2个客户编同时连上服务器并开展交互。此时,再启动另一个客户端连接服务器,观察体验是什么现象,并尝试分析现象背后的底层逻辑。
实验要求:PDU设计与收发
本实验设定PDU具有2字节的定长头部(Header)以及变长负载(Payload)。请求报文的2字节头部携带客户端随机编号CID,该编号由客户端程序启动时从命令行获取。响应报文的2字节头部携带服务器端验证码VCD,该编号由服务器程序启动时从命令行获取。
请求报文Request PDU与响应报文Response PDU的负载数据部分均为长度不等且以\n\0结尾的字符组合。
TCP客户端、服务器可通过read( ) & write()或者send() & recv())进行收发操作。本实验要求所有套接字读写操作必须使用read() & write(),以满足在线测评指标设定。
发送PDU时,必须准备好整个PDU后执行一次性发送,仅调用一次write()。
接收PDU时,必须首先读取2字节长度的头部数据,随后再读取变长包含字符串语句的负载数据。
流程图
代码的流程图如下:
实验代码
-
服务器程序代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define MAX_LEN 120 int sigint_flag = 0; typedef struct Request { uint16_t cid; char msg[MAX_LEN]; } Request; typedef struct Response { uint16_t vcd; char msg[MAX_LEN]; } Response; void handle_sigint(int sig) { printf("[srv] SIGINT is coming!\n"); sigint_flag = 1; } int install_sigint() { struct sigaction sa; sa.sa_flags = 0; sa.sa_handler = handle_sigint; sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa,NULL); return 0; } void sig_chld(int signo) { pid_t pid_chld; int stat; while((pid_chld = waitpid(-1, &stat, WNOHANG)) > 0); } int srv_biz(int connfd, int vcd) { Request request; Response response; // 读取客户端请求 while(1) { if(read(connfd, &request.cid, sizeof(short)) == 0) { return -1; } // 打印客户端请求内容 uint16_t cid = ntohs(request.cid); if (read(connfd, request.msg, MAX_LEN) == 0) { perror("Read error"); return -1; } printf("[chd](%d)[cid](%d)[ECH_RQT] %s", getpid(), cid, request.msg); uint16_t code_network = htons(vcd); // 转换为网络字节序 response.vcd = code_network; strcpy(response.msg, request.msg); int send_len = sizeof(Response); // 发送回声到客户端 if (write(connfd, &response, send_len) < 0) { perror("Write error"); return -1; } memset(&request, 0, sizeof(Request)); memset(&response, 0, sizeof(Response)); } return 0; } int main(int argc, char *argv[]) { if (argc < 4) { printf("Usage: %s <ip_address> <port> <veri_code>\n", argv[0]); return 1; } char *ip_address = argv[1]; int port = atoi(argv[2]); int vcd = atoi(argv[3]); struct sigaction sigchld; sigchld.sa_flags = 0; sigchld.sa_handler = sig_chld; sigemptyset(&sigchld.sa_mask); sigaction(SIGCHLD, &sigchld, NULL); install_sigint(); int listenfd, connfd; struct sockaddr_in servaddr, cliaddr; int clilen; // 创建监听套接字 listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { perror("Socket error"); return 1; } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(ip_address); servaddr.sin_port = htons(port); // 绑定监听套接字 if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("Bind error"); return 1; } if (listen(listenfd, 2) < 0) { perror("Listen error"); return 1; } clilen = sizeof(struct sockaddr_in); printf("[srv](%d)[srv_sa](%s:%d)[vcd](%d) Server has initialized!\n", getpid(), ip_address, port, vcd); while (!sigint_flag) { if ((connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen)) < 0) { if(errno == EINTR) continue; else { perror("accept error"); exit(1); } } printf("[srv](%d)[cli_sa](%s:%d) Client is accepted!\n", getpid(), inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port); // 创建子进程接受客户端请求 pid_t childpid = fork(); if (childpid < 0) { perror("Fork error"); continue; } else if (childpid == 0) { printf("[chd](%d)[ppid](%d) Child process is created!\n", getpid(), getppid()); close(listenfd); // 关闭子进程监听套接字 srv_biz(connfd, vcd); // 处理客户端请求 printf("[chd](%d)[ppid](%d)[cli_sa](%s:%d) Client is closed!\n", getpid(), getppid(), inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port); close(connfd); printf("[chd](%d)[ppid](%d) connfd is closed!\n", getpid(), getppid()); printf("[chd](%d)[ppid](%d) Child process is to return!\n", getpid(), getppid()); return 0; } close(connfd); // 关闭父进程连接 } close(listenfd); printf("[srv](%d) Server is shutting down...\n", getpid()); return 0; }
-
客户端程序代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define MAX_LEN 120 typedef struct Request { uint16_t cid; char msg[MAX_LEN]; } Request; typedef struct Response { uint16_t vcd; char msg[MAX_LEN]; } Response; int main(int argc, char *argv[]) { if (argc < 4) { printf("Usage: %s <ip_address> <port> <cid>\n", argv[0]); return 1; } char *ip_address = argv[1]; int port = atoi(argv[2]); int cid = atoi(argv[3]); int sockfd; struct sockaddr_in servaddr; int nbytes; // 创建套接字 sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("Socket error"); return 1; } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(ip_address); servaddr.sin_port = htons(port); // 连接服务器 if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)) < 0) { perror("Connect error"); return 1; } printf("[cli](%d)[srv_sa](%s:%d) Server is connected!\n", getpid(), ip_address, port); char send_msg[MAX_LEN]; Request request; Response response; while (1) { memset(send_msg, 0, sizeof(send_msg)); memset(&request, 0, sizeof(Request)); memset(&response, 0, sizeof(Response)); fgets(send_msg, sizeof(send_msg), stdin); if (strcmp(send_msg, "EXIT\n") == 0) { printf("[cli](%d) connfd is closed!\n", getpid()); break; } printf("[cli](%d)[cid](%d)[ECH_RQT] %s", getpid(), cid, send_msg); uint16_t cid_network = htons(cid); // 转换为网络字节序 request.cid = cid_network; send_msg[strlen(send_msg)] = '\0'; strcpy(request.msg, send_msg); // 发送消息至服务器 if (write(sockfd, &request, strlen(send_msg) + sizeof(short)) < 0) { perror("Write error"); break; } uint16_t vcd_network; // 网络字节序的VCD read(sockfd, &vcd_network, sizeof(vcd_network)); uint16_t vcd = ntohs(vcd_network); // 转换为主机字节序 nbytes = read(sockfd, response.msg, MAX_LEN); if (nbytes <= 0) { perror("Read error"); break; } printf("[cli](%d)[vcd](%d)[ECH_REP] %s", getpid(), vcd, response.msg); } close(sockfd); printf("[cli](%d) client is to return!\n", getpid()); return 0; }