【计算机网络】套接字编程实验—TCP多进程并发服务器程序与单进程客户端程序

该文章仅供学习交流,严禁用于任何商业或非法用途,如有侵权,请联系本人删除!

实验名称

TCP多进程并发服务器与单进程客户端(简单回声)

实验内容

编写TCP多进程循环服务器程序与单进程客户端程序,实现以下主体功能:

  1. 客户端启动连接服务器之后,进入命令行交互模式。
  2. 操作人员在命令行窗口输入一行字符并回车后,客户端进程立刻从命令行(本质即 stdin)读取数据,并将该行信息发送给服务器。
  3. 服务器收到该行信息后,会将该信息原封不动的返回给客户端,即所谓消息回声(Message Echo)。
  4. 客户端收到服务器返回的消息回声后,将其打印输出至屏幕(本质即 stdout)。
  5. 客户端在从命令行收到 EXIT 指令后退出。
  6. 若服务器启动时设定 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字节长度的头部数据,随后再读取变长包含字符串语句的负载数据。

流程图

代码的流程图如下:
在这里插入图片描述

实验代码

  1. 服务器程序代码:

    #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;
    }
    
    
  2. 客户端程序代码:

    #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;
    }
    
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明月出天山_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值