select 實現 tcp demo
源碼地址
https://github.com/duchenlong/linux-text/tree/master/network/IO/SelectTcp
回憶TCP的連接過程
- 服務端
- 客戶端
select
關於select 的介紹,可以參考上一篇博客 https://blog.csdn.net/duchenlong/article/details/106758718
我們使用select
函數的地方,是我們服務端所在的地方。
利用select可以監控可讀事件的特性,將客戶端所發起的連接產生的新的套接字(也就是一個文件描述符),添加到可讀事件的集合中。
由於select中涉及到的處理有點多,我們可以對這些功能進行封裝,構造一個selectSvr的類
select 的封裝
tcp類的封裝
程序流程
程序
cli.cpp 客戶端建立連接
#include "Tcpsvr.hpp"
#include <cstdlib>
int main(int argc,char* argv[])
{
if(argc != 3)
{
cout<<"請輸入正確的參數 [./client] [ip] [port]"<<endl;
return 0;
}
string ip = argv[1];
uint16_t port = atoi(argv[2]);
Tcpsvr tcp;
if(!tcp.CreateSocket())
{
return 0;
}
if(!tcp.Connect(ip,port))
{
return 0;
}
while(1)
{
cout<<"請輸入想給服務端說的話 : ";
fflush(stdout);
string buf;
cin>>buf;
tcp.Send(buf);
buf.clear();
//等待接收數據
if(!tcp.Recv(buf))
{
cout<<"我方程序退出"<<endl;
break;
}
cout<<"服務端說 : "<<buf<<endl;
}
tcp.Close();
return 0;
}
SelectSvr.hpp 服務器的頭文件
#pragma once
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <vector>
#include <cstdio>
#include "Tcpsvr.hpp"
using namespace std;
class SelectSvr
{
public:
SelectSvr()
{
_maxFd = -1;
FD_ZERO(&_readfds);
}
//添加文件描述符
void AddFd(int fd)
{
FD_SET(fd,&_readfds);
//更新最大 文件描述符 數值
if(fd > _maxFd)
_maxFd = fd;
}
//刪除文件描述符
void DeleteFd(int fd)
{
FD_CLR(fd,&_readfds);
//更新最大 文件描述符 數值
for(int i = _maxFd; i >= 0; i--)
if(FD_ISSET(i,&_readfds) == 1)//找第一個存在的文件描述符
{
_maxFd = i;
break;
}
}
bool SelectWait(vector<Tcpsvr>& vec)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 3000;//設置超時時間
fd_set tmp = _readfds;
int ret = select(_maxFd + 1,&tmp,NULL,NULL,&tv);
if(ret < 0)
{
perror("select");
return false;
}
else if(ret == 0)
{
//cout<<"select timeout"<<endl;
return false;
}
//程序正常的流程
for(int i = 0; i <= _maxFd; i++)
if(FD_ISSET(i,&tmp))
{
//返回就緒的 文件描述符i 的類對象
Tcpsvr ts;
ts.SetFd(i);
vec.push_back(ts);
}
return true;
}
private:
int _maxFd;
fd_set _readfds;
};
Tcpsvr.hpp
#pragma once
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdio>
#include <iostream>
#include <string>
using namespace std;
class Tcpsvr
{
public:
Tcpsvr()
:_sockfd(-1)
{}
//創建套接字
bool CreateSocket()
{
int ret = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(ret < 0)
{
perror("socket");
return false;
}
_sockfd = ret;
//解決地址複用問題
int i = 0;
setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));
return true;
}
//綁定地址信息
bool Bind(const string& ip,uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = bind(_sockfd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in));
if(ret < 0)
{
perror("bind");
return false;
}
return true;
}
//建立監聽
bool Listen(int backlog = 5)
{
int ret = listen(_sockfd,backlog);
if(ret < 0)
{
perror("listen");
return false;
}
return true;
}
//獲取連接
bool Accept(struct sockaddr_in* addr,Tcpsvr& ts)
{
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
socklen_t addrLen = sizeof(struct sockaddr_in);
int serFd = accept(_sockfd,(struct sockaddr*)addr,&addrLen);
if(serFd < 0)
{
perror("accept");
return false;
}
ts._sockfd = serFd;
return true;
}
//發起連接
bool Connect(string& ip,uint16_t port)
{
//int connect(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = connect(_sockfd,(struct sockaddr*)&addr,sizeof(addr));
if(ret < 0)
{
perror("connect");
return false;
}
return true;
}
//發送數據
bool Send(string& data)
{
// ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t ret = send(_sockfd,data.c_str(),data.size(),0);
if(ret == 0)
{
perror("send");
return false;
}
return true;
}
//接收數據
bool Recv(string& data)
{
// ssize_t recv(int sockfd, void *buf, size_t len, int flags);
char buf[1024] = {'\0'};
ssize_t ret = recv(_sockfd,buf,sizeof(buf) - 1,0);
if(ret < 0)
{
perror("recv");
return false;
}
else if (ret == 0)
{
cout<<"對方關閉了連接"<<endl;
return false;
}
//ret 就是所接收數據的大小
data.assign(buf,ret);
return true;
}
//關閉套接字
void Close()
{
close(_sockfd);
_sockfd = -1;
}
//設置套接字
inline void SetFd(int fd)
{
_sockfd = fd;
}
//獲得套接字
inline int GetFd()
{
return _sockfd;
}
private:
int _sockfd;
};
main.cpp 主函數
#include "SelectSvr.hpp"
#define CHECK_RET(p) if(p != true) {return -1;}
int main()
{
Tcpsvr listen_ts;//監聽的對象
CHECK_RET(listen_ts.CreateSocket());
CHECK_RET(listen_ts.Bind("0.0.0.0",19998));
CHECK_RET(listen_ts.Listen());
//建立服務端
SelectSvr ss;
ss.AddFd(listen_ts.GetFd());
while(1)
{
//監控
vector<Tcpsvr> vec;
if(!ss.SelectWait(vec))
continue;
for(size_t i = 0; i < vec.size(); i++)
{
//接收新連接
if(listen_ts.GetFd() == vec[i].GetFd())
{
struct sockaddr_in addr;
Tcpsvr peerts;
listen_ts.Accept(&addr,peerts);
cout<<"有一個新連接 : ip = [" << inet_ntoa(addr.sin_addr)<<"] ";
cout<<"port = ["<<ntohs(addr.sin_port)<<" ]"<<endl;
//新創建的條件字 添加到select事件集合中
ss.AddFd(peerts.GetFd());
}
else //接收數據
{
string data;
bool ret = vec[i].Recv(data);
//如果對端關閉連接,或者發送了錯誤
//進行終止
if(!ret)
{
ss.DeleteFd(vec[i].GetFd());
vec[i].Close();
continue;
}
cout<<"客戶端["<<vec[i].GetFd()<< "]發送的數據是 : "<<data<<endl;
//給客戶端回覆數據
fflush(stdout);
cout<<"回覆客戶端數據 : ";
cin>>data;
vec[i].Send(data);
}
}
}
return 0;
}
makefile
all:main cli
cli:cli.cpp
g++ $^ -o $@ -g
main:main.cpp
g++ $^ -o $@ -g