上一篇文章我们用Udp简单实现了一个网络聊天室,今天我们一起来学习使用TCP套接字。
//端口号
uint16_t _port;
//要执行的回调
func_t _func;
//listen套接字
int _socklisten;
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地址和Port
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // host to network short
local.sin_addr.s_addr = INADDR_ANY; // bind本主机任意ip
if (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");
}
1、service 测试使用,不多讲解
2、多进程版本
使用多进程的方式,可以让多个进程处理不同的service请求,但使用这个方式需要考虑两个问题:
a、子进程要不要等待?谁来等待?
子进程当然需要等待,但我们不希望是父进程,因为父进程还要去处理其他客户端的事情,这里就有两种方式来解决这个问题:
- 使用信号忽略掉SIGCHLD
signal(SIGCHLD,SIG_IGN);- 托孤给bash进程
父进程退出,子进程就变成了孤儿进程。
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 获取连接,accept
int 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);
}
}
仅需要一个成员变量,在服务端它可以是监听fd,在客户端他可以被当作通信fd使用。
int _sock;
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);
}
}
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");
}
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");
}
// 将客户端信息存起来
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;
}
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));
}
int Fd()
{
return _sock;
}
void Close()
{
close(_sock);
}
至此,Tcp的Sock封装我们就全部搞定了,