Linux网络编程 4 - 多线程的TCP服务器

        上一篇介绍了tcp相关知识和linux系统的socket相关接口,以及一个简单的TCP服务端和客户端,这个TCP服务器只能接受一个客户端的连接,并处理客户端发来的消息,如果在第一个客户端没有断开和服务器的连接之前,有第二个客户端发起了连接,服务端就无法处理。

        这样的服务器显然不行, 下面介绍一个能够处理多个客户端连接的方法:多线程的TCP服务器。

1. Linux系统多线程编程简单介绍

    头文件 #include <pthread.h>

    编译链接库 -lpthread

(1)创建线程

        int pthread_create(pthread_t *thread, const pthread_attr_t *attr,  void *(*start_routine) (void *), void *arg);

        pthread_t *thread 是线程标识,即线程ID

        const pthread_attr_t *attr 设置线程的属性,一般设置为NULL即可,默认创建的线程是joinable的

        void *(*start_routine) (void *) 新建线程的回调函数,即新线程从start_routine这个函数开始运行

        void *arg 是回调函数start_routine的参数

(2)分离可结合线程

        int pthread_detach(pthread_t thread);

        phread_create的attr参数设置为NULL时,默认创建的线程是joinable 可结合的,这种线程可以被其他线程杀死和回收资源,如果没有线程回收他的资源(比如栈),就会造成内存泄露。所以,可结合的线程资源必须被显示的回收,也就是在主线程或其他线程中,调用pthread_join,或者调用pthread_detach使之分析,在其运行结束后,由系统自动回收资源。为了简单起见,这里仅在线程结束前调用了pthread_detach。

2. 多线程的TCP服务器

        基本的初始化和单线程代码一致,在服务器accept成功接受客户端的连接之后,调用pthread_create创建一个线程,用来接收这个客户端的消息,主线程在while循环中继续运行到accept等待新的客户端发起连接。

        详细代码如下:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
#include <iostream>
#include <sstream> 
#include <pthread.h>
using namespace std;  

//获取客户端 ip:port 格式的字符串
string getpeeraddrstr(int sockfd)
{
	struct sockaddr_in addr = {0};
	unsigned int size = sizeof(addr);
	getpeername(sockfd, (struct sockaddr*)&addr, &size);
	stringstream ssaddr;
	ssaddr << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
	return ssaddr.str();
}

void* recvmessage(void* args)
{
	int cltfd = *(int*)args;
	string straddr = getpeeraddrstr(cltfd);
	cout << "connect accept: " << straddr << endl;     

	while(1)  
	{  
		char buffer[100] = {0};  
		int  recvlen     = 0;  
		recvlen = recv(cltfd, buffer, sizeof(buffer), 0);  
		if(0 < recvlen)  //接收成功  
		{  
			cout << "recv from " << straddr << " " << buffer;  
		}  
		else  
		{  
			cout << "client " << straddr <<" closed" << endl;  
			break;  
		}  
	}
	//pthread_create默认创建joinable线程 在线程退出前分离 系统会自动回收线程资源 避免内存泄露
	pthread_detach(pthread_self()); 
	return NULL;
}

//TCP服务端主函数
int main()  
{  
	int opt    = 1;   
	int svrfd  = -1;  
	int cltfd  = -1;  
	unsigned short      svrport = 9999;     
	struct sockaddr_in  svraddr = {0};  
	struct sockaddr_in  cltaddr = {0};  
	unsigned int        addrlen = sizeof(cltaddr);  

	//创建socket  
	svrfd = socket(AF_INET, SOCK_STREAM, 0);  
	if(-1 == svrfd)  
	{  
		perror("socket failed");  
		return -1;  
	}  
	//设置地址重用  
	setsockopt(svrfd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));  
	//绑定ip地址和端口  
	svraddr.sin_family      = AF_INET;  
	svraddr.sin_port        = htons(svrport);     //服务端绑定端口  
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);  //服务端绑定任意IP  
	if(-1 == bind(svrfd, (struct sockaddr*)&svraddr, sizeof(svraddr)))  
	{  
		perror("bind failed");  
		close(svrfd);  
		return -1;  
	}  
	//开始监听  
	if(-1 == listen(svrfd, 10))  
	{  
		perror("listen failed");  
		close(svrfd);  
		return -1;  
	}  
	while(1)  
	{
		memset(&cltaddr, 0, sizeof(cltaddr));
		//服务端阻塞等待客户端的连接  
		cltfd = accept(svrfd, (struct sockaddr*)&cltaddr, &addrlen);  
		if(-1 == cltfd)  
		{  
		perror("accept failed");  
		return -1;  
		}
		//服务端接收客户端的连接后 创建一个线程 循环接收客户端发来的消息
		pthread_t threadid;
		pthread_create(&threadid, NULL, recvmessage, &cltfd);
	}  
} 

猜你喜欢

转载自blog.csdn.net/k117470154/article/details/79770032