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

本文详细介绍了TCP传输控制协议的实现逻辑,包括服务器端的socket创建、监听、接受客户端连接及服务,以及客户端的socket连接、发送请求和接收应答。进一步探讨了如何通过多进程和多线程优化,使得多个客户端能够同时连接服务器,最后提到了线程池的使用以提高服务效率。
摘要由CSDN通过智能技术生成

实现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 在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;
}

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);
		}

};
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;
}

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);  //回收子进程的资源

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

  • 在服务器start之后,只要有客服端发送连接请求,服务器端就建立连接并创建新线程去开始服务。
  • 在start函数中,创建线程:
pthread_t tid;
pthread_create(&tid, nullptr, serviceRoutine, (void*)&sock);  //通信套接字的地址传参
  • 提供新线程逻辑函数:
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;
}

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值