文章目录
- 前言
- 基于Tcp的echo
- 成员变量
- 成员函数
- 封装SOCK类
- 成员变量
- 成员函数
- 结语
前言
上一篇文章我们用Udp简单实现了一个网络聊天室,今天我们一起来学习使用TCP套接字。
基于Tcp的echo
成员变量
//端口号
uint16_t _port;
//要执行的回调
func_t _func;
//listen套接字
int _socklisten;
成员函数
- Init
完成套接字的创建、绑定、监听
void initServer()
{// 1 创建socket接口,打开网络文件_socklisten = socket(AF_INET, SOCK_STREAM, 0);if (_socklisten < 0){logMessage(Error, "create socket error");exit(SOCKED_ERR);}logMessage(Info, "create socket success");// 2 给服务器指明IP地址和Portstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // host to network shortlocal.sin_addr.s_addr = INADDR_ANY; // bind本主机任意ipif (bind(_socklisten, (sockaddr*)&local, sizeof(local)) < 0){logMessage(Error, "bind socket error code:%d info:%s", errno, strerror(errno));exit(BIND_ERR);}logMessage(Info, "bind socket success");// 3 监听if (listen(_socklisten, backlog) < 0){logMessage(Error, "listen socket error");exit(LISTEN_ERR);}logMessage(Info, "listen socket success");
}
- start
我们分为四个版本来实现最终的业务:-
1、service 测试使用,不多讲解
-
2、多进程版本
使用多进程的方式,可以让多个进程处理不同的service请求,但使用这个方式需要考虑两个问题:-
a、子进程要不要等待?谁来等待?
子进程当然需要等待,但我们不希望是父进程,因为父进程还要去处理其他客户端的事情,这里就有两种方式来解决这个问题:- 使用信号忽略掉SIGCHLD
signal(SIGCHLD,SIG_IGN); - 托孤给bash进程
父进程退出,子进程就变成了孤儿进程。
- 使用信号忽略掉SIGCHLD
-
b、子进程和父进程不需要的fd是不是都需要关闭?
父进程的fd必须要关掉,而子进程的fd可以选择关掉。如果父进程不关闭用于通信的fd,很快就会导致fd越来越多,进而发生文件描述符泄露
-
-
3、源生线程版本
源生线程这里最需要注意的就是线程任务需要传入的参数很多,因此我们要构建一个ThreadData类ThreadDate *td = new ThreadData(sock, clientip, clientport, this);
在线程任务内再通过类型转换想办法执行类内要执行的函数。
若不想回收线程,可以使用pthread_detach(pthread_self()); -
4、线程池版本
线程池主要解决简短、频繁的请求。在多线程章节我们再详细聊聊线程池的设计。
-
void start()
{while (true){struct sockaddr_in client;socklen_t len = sizeof(client);// 4 获取连接,acceptint sock = accept(_socklisten, (struct sockaddr *)&client, &len);if (sock < 0){logMessage(Warning, "获取连接失败,code:%d,error string:%s", errno, strerror(errno));continue;}std::string clientip = inet_ntoa(client.sin_addr);uint16_t clientport = ntohs(client.sin_port);// 5 获取连接成功开始进行业务处理logMessage(Info, "获取连接成功:%d from %d,client:%s-%d", sock, _socklisten, clientip.c_str(), clientport);// 1 test// service(sock,clientip,clientport);// 2 多进程// pid_t id = fork();// if(id < 0)// {// logMessage(Warning,"创建线程失败 code: %d error:%s",errno,strerror(errno));// continue;// }// else if(id == 0)// {// //子进程可以选择关闭不需要的fd// close(_socklisten);// if(fork() > 0) exit(0);// //现在是孙子进程被bash1领养了,不需要等待了// service(sock,clientip,clientport);// exit(0);// }// //父进程必须关闭不用的fd防止fd泄露// close(sock);// 3 源生线程// pthread_t tid;// ThreadDate *td = new ThreadDate(sock, clientip, clientport, this);// pthread_create(&tid, nullptr, threadRoutine, td);// 4 线程池版本Task t(sock,clientip,clientport,std::bind(&TcpServer::service,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));ThreadPool<Task>::GetInstance()->pushTask(t);}
}
封装SOCK类
成员变量
仅需要一个成员变量,在服务端它可以是监听fd,在客户端他可以被当作通信fd使用。
int _sock;
成员函数
- Socket
和udp一样,套接字需要先被创建出来。
void Socket()
{_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){logMessage(Error, "create socket error id:%d info:%s", errno, strerror(errno));exit(SOCKET_ERR);}
}
- Bind
和udp一样,套接字需要bind对应的ip+端口号
void Bind(const uint16_t port)
{struct sockaddr_in local; local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sock, (sockaddr *)&local, sizeof(local)) < 0){logMessage(Error, "Bind error id:%d info:%s", errno, strerror(errno));exit(BIND_ERR);}logMessage(Info, "Bind success");}
- listen
将TCP套接字设置为监听状态,使其可以接收客户端的连接请求。gbacklog是最大的等待被接受的连接请求数,而不是最多的连接数,一般而言这个队列长度是gbacklog+1.
void Listen()
{if (listen(_sock, gbacklog) < 0){logMessage(Error, "Listen error id:%d info:%s", errno, strerror(errno));exit(LISTEN_ERR);}logMessage(Info, "Bind success");
}
- accept
用于接受客户端的连接请求,并创建一个新的套接字与客户端通信
// 将客户端信息存起来
int Accept(std::string *clientip, uint16_t *clientport)
{sockaddr_in temp;socklen_t len = sizeof(temp);int sock = accept(_sock, (sockaddr *)&temp, &len);if (sock < 0){logMessage(Error, "Accept error id:%d info:%s", errno, strerror(errno));}else {*clientip = inet_ntoa(temp.sin_addr);*clientport = ntohs(temp.sin_port);}logMessage(Info, "accept success");return sock;
}
- Connect
客户端向服务端发起连接请求
int Connect(const std::string serverip,const uint16_t& serverport)
{sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());return connect(_sock,(sockaddr*)&server,sizeof(server));
}
- Fd、Close
int Fd()
{return _sock;
}void Close()
{close(_sock);
}
结语
至此,Tcp的Sock封装我们就全部搞定了,