上一篇介绍了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);
}
}