• [Linux] 网络套接字编程之实现简单的TCP网络程序(下)


    实现TCP传输控制协议

    1)特点

    传输层协议、有连接、可靠传输、面向字节流

    2)实现逻辑

    1. 服务器端创建socket链接套接字,绑定端口号,然后监听套接字;服务器开始工作:接受客户端的链接请求,提供服务。

    1.1 在tdp_server.hpp

    使用recv函数接受信息,send函数发送信息,返回值类型都为size_t ;
    客户端停止发送信息后,服务器要把当前通信套接字关闭掉;在提供服务的时候,当前服务结束,也要把通信套接字关闭掉。

    #define BACKLOG 5
    
    struct tcpServer{
    	private:
    		int port;
    		int lsock;  //监听套接字
    	public:
    		tcpServer(int _port):port(_port), lsock(-1){
    
    		}
    
    		void initServer(){  //初始化Server:创建---绑定---监听服务器端套接字
    			lsock = socket(AF_INET, SOCK_STREAM, 0);
    			if(lsock < 0){
    				std::cout << "socket error!\n" << std::endl;
    				exit(2);
    			}else{
    				std::cout << "lsock: " << lsock << std::endl;
    			}
    			
    			struct sockaddr_in local;
    			local.sin_family = AF_INET;
    			local.sin_port = htons(port);
    			local.sin_addr.s_addr = INADDR_ANY;
    			if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
    				std::cout << "bind error!\n" << std::endl;
    				exit(3);
    			}
    
    			if(listen(lsock, BACKLOG) < 0){  //BACKLOG为底层全连接的数量
    				std::cout << "listen error!\n" << std::endl;
    				exit(4);
    			}
    		}
    
    		void service(int sock){  //接受客户端发送的请求,并返回应答信息
    			char msg[1024]; 
    			while(true){
    				ssize_t s = recv(sock, msg, sizeof(msg)-1, 0);
    				if(s > 0){  //接受信息成功,显示收到的信息,并发送应答
    					msg[s] = 0;
    					std::cout << "client# " << msg << std::endl;
    					std::string echo = msg;
    					echo += " [server echo!] ";
    					send(sock, echo.c_str(), echo.size(), 0);  
    				}
    				else if(s == 0){
    					std::cout << "client quit..." << std::endl;
    					close(sock);  //客户端停止发送信息后把套接字关闭掉
    					break;
    				}
    				else{
    					std::cout << "recv client data error.." << std::endl;
    					break;
    				}
    			}
    			close(sock);
    		}
    
    		void start(){  
    			struct sockaddr_in end_point;
    			socklen_t len = sizeof(end_point);
    			//tcpServer一旦开始工作,就一直等待着接受客户端发送建立连接的请求
    			while(true){
    				int sock = accept(lsock, (struct sockaddr*)&end_point, &len);
    				if(sock < 0){  //lsock:获取新链接		sock:服务新链接
    					std::cout << "accept error!\n" << std::endl;
    					continue;
    				}
    				std::cout << "get a new link...\n" << std::endl;
    				service(sock);
    			}
    		}
    
    		~tcpServer(){
    			close(lsock);
    		}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    1.2 在tdp_server.cc中

    以命令行参数的形式传入创建tdp服务器所需要的端口号参数。

    void Usage(std::string proc){
    	std::cout << "Usage: " << proc << "local_port" << std::endl;
    }
    
    int main(int argc, char *argv[]){
    	if(argc != 2){
    		Usage(argv[0]);
    		exit(1);
    	}
    	tcpServer *ts = new tcpServer(atoi(argv[1]));
    	ts->initServer();
    	ts->start();
    	delete ts;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. 客户端创建链接套接字,建立连接;然后向服务器端发送请求,得到应答。

    2.1 在tdp_client.cpp中

    connect()申请建立连接,返回值不为0时,表示申请失败。

    struct tcpClient{
    	private:
    		std::string svr_ip;
    		int svr_port;
    		int sock;  //连接套接字
    
    	public:
    		tcpClient(std::string _ip, int _port)
    			:svr_ip(_ip), svr_port(_port){
    		}
    
    		void initClient(){
    			sock = socket(AF_INET, SOCK_STREAM, 0);  
    			if(sock < 0){
    				std::cout << "socket error!\n" << std::endl;
    				exit(2);
    			}
    
    			struct sockaddr_in svr;
    			svr.sin_family = AF_INET;
    			svr.sin_port = htons(svr_port);
    			svr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
    			if(connect(sock, (struct sockaddr*)&svr, sizeof(svr)) != 0){  //不为0时,请求连接失败
    				std::cout << "connect error!\n" << std::endl;
    				exit(3);
    			}
    		}
    
    
    		void start(){
    			char msg[1024];
    			while(true){
    				std::cout << "Please Enter Message# " << std::endl;
    				size_t s = read(0, msg, sizeof(msg)-1);  //从标准输入流中读取信息
    
    				if(s > 0){  //输入信息成功
    					msg[s-1] = 0;
    					send(sock, msg, sizeof(msg)-1, 0);  //发送信息
    					size_t ss = recv(sock, msg, sizeof(msg)-1, 0);  //接受应答信息
    					if(ss > 0){
    						msg[ss] = 0;
    						std::cout << "server echo# \n" << msg << std::endl;
    					}
    				}
    			}
    		}
    
    		~tcpClient(){
    			close(sock);
    		}
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    2.2 在tdp_client.cc中

    以命令行参数的形式传入创建tdp客户端所需要的ip地址参数和端口号参数。

    void Usage(std::string proc){
    	std::cout << "Usage: " << proc << "srv_ip, srv_port" << std::endl;
    }
    
    int main(int argc, char *argv[]){
    	if(argc != 3){
    		Usage(argv[0]);
    		exit(1);
    	}
    	tcpClient *tc = new tcpClient(argv[1], atoi(argv[2]));
    	tc->initClient();
    	tc->start();
    	delete tc;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3)实现多进程版本,让多个客户端可以同时连接服务器

    1. 问题描述

    初始版本的tcp程序一次只能给一个客户端提供服务,当有其他客户端请求服务器时,可以connect连接上,但是无法通信,因为start循环体内的语句限制了只要当前客户端不退出,就要一直为这一个客户端服务。

    2. 优化方法

    • 方法1:让子进程提供服务,把2号信号的处理动作设置为SIGIGN,当子进程退出时,父进程采用忽略的处理方式,回收其资源,不会产生僵尸进程。
    • 方法2:把回收子进程的任务转交给操作系统:让孙子进程提供服务,关闭链接套接字,父进程直接退出,此时孙子进程变成了孤儿进程,当其退出时,会自动被1号进程所领养,回收其资源;当孙子进程结束后,爷爷进程关闭其通信套接字(当前客户端退出)。
    pid_t id = fork();
    if(id == 0){  //子进程
    	if(fork() > 0){  //子进程退出,留下孙子进程:避免出现僵尸进程
    		exit(0);
    	}
    	//让孙子进程处理IO服务
    	close(lsock);
    	service(sock);
    	exit(0);
    }
    close(sock);  //关闭IO套接字
    waitpid(id, NULL, 0);  //回收子进程的资源
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4)实现多线程版本,让多个客户端可以同时连接服务器

    • 在服务器start之后,只要有客服端发送连接请求,服务器端就建立连接并创建新线程去开始服务。
    • 在start函数中,创建线程:
    pthread_t tid;
    pthread_create(&tid, nullptr, serviceRoutine, (void*)&sock);  //通信套接字的地址传参
    
    • 1
    • 2
    • 提供新线程逻辑函数:
    static void *serviceRoutine(void *arg){
    	pthread_detach(pthread_self());  //为了避免线程等待,我们使用线程分离
    	std::cout << "create a new thread for IO..." << std::endl;
    	int *p = (int*)arg;
    	int sock = *p;
    	service(sock);
    	delete p;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5)实现线程池版本,提高效率

    线程池版本的TCP网络程序----相关实现代码点此处获取!!!

  • 相关阅读:
    【虚拟机】
    深入解析git、Gitee、GitHub与GitLab
    LeetCode --- 1260. Shift 2D Grid 解题报告
    快速构建后台管理系统-RUOYI学习之-VUE
    深度学习系列60: 大模型文本理解和生成概述
    ARM-A架构入门基础(二)异常处理
    net-java-php-python-会议管理系统计算机毕业设计程序
    重学FreeRTOS操作系统之任务篇(一)
    C语言 | Leetcode C语言题解之第329题矩阵中的最长递增路径
    Turtlebot4入门教程-产品特征
  • 原文地址:https://blog.csdn.net/Darling_sheeps/article/details/127463922