文章目录
- 网络编程套接字(3)
-
- 4. 简单的TCP网络程序
-
- 4.1 服务端创建
-
- (1) 创建套接字
- (2) 绑定端口
- (3) 监听
- (4) 获取新连接
- (5) 处理读取与写入
- 4.2 客户端创建
-
- (1)连接服务器
- 4.3 代码编写
-
- (1) v1__简单发送消息
- (2) v2_多进程版本
- (3) v3_多线程版本
- (4) v4_线程池版本
网络编程套接字(3)
4. 简单的TCP网络程序
4.1 服务端创建
(1) 创建套接字
还是之前udp部分的socket函数,这里只是简单说明一下与udp的差异
int socket(int domain, int type, int protocol);
只需将第二个参数type换成:
SOCK_STREAM: 基于TCP的网络通信,流式套接字,提供的是流式服务(对应TCP的特点:面向字节流)
(2) 绑定端口
还是和之前一样的接口
(3) 监听
UDP服务器的初始化操作只有2步,第一步:创建套接字,第二步:是绑定。但是TCP服务器是面向连接的,客户端在正式向TCP服务器发送数据之前,需要先与TCP服务器建立连接,然后才能与服务器进行通信。
因此TCP服务器需要时刻注意是否有客户端发来连接请求,此时就需要将TCP服务器创建的套接字设置为监听状态
listen for connections on a socket: 监听套接字上的连接
头文件:
#include
#include
函数原型:
int listen(int sockfd, int backlog);
参数说明:
第一个参数sockfd: 需要设置为监听状态的套接字对应的文件描述符
第二个参数backlog: 这里当成一个整数,后续详细解释
返回值:
监听成功: 返回0
监听失败: 失败返回-1,并设置错误码
(4) 获取新连接
客户端有新链接到来,服务端可以获取到新链接,这一步需要死循环获取客户端新链接。
accept a connection on a socket: 接收套接字上的连接
头文件:
#include
#include
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
第一个参数sockfd: 监听套接字
第二个参数addr: 获取对方一端网络相关的属性信息
第三个参数addrlen: addr的长度
返回值:
连接成功: 返回接收到的套接字的文件描述符
连接失败: 失败返回-1,并设置错误码
关于accept的返回值: 也是一个文件描述符
为什么又返回一个新的文件描述符??返回的这个新的文件描述符跟旧的文件描述符_sockfd有什么关系?
感性理解:
对比listen监听套接字与accept函数返回的套接字
- listen监听套接字:用于获取客户端发来的连接请求。accept函数会不断从监听套接字当中获取新连接
- accept函数返回的套接字:用于为本次accept获取到的连接提供服务。
- 而listen监听套接字的任务只是不断获取新连接,而真正为这些连接提供服务的套接字是accept函数返回的套接字,而不是监听套接字。
(5) 处理读取与写入
因为TC 提供的是流式服务,所以这里利用read和write来实现读取与写入
4.2 客户端创建
4步:创建套接字,客户端向服务器发起连接请求,bind(不需要自己绑定,由OS自动分配),处理数据读取与写入
(1)连接服务器
initiate a connection on a socket: 在套接字上发起连接
头文件:
#include
#include
函数原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
第一个参数sockfd: 表示通过该套接字发起连接请求
第二个参数addr: 对方一端网络相关的属性信息
第三个参数addrlen: addr的长度
返回值:
连接成功: 返回0
连接失败: 失败返回-1,并设置错误码
4.3 代码编写
这里一共提供4个版本的tcp代码
err.hpp
:这个代码是公用的后续不在给出
#pragma once
enum
{
USAGE_ERR=1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
};
(1) v1__简单发送消息
客户端向服务端发送消息,服务端收到后再把消息发回给客户端
tcpServer.hpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
// 问题: 目前的服务器, 无法处理多个client的问题, 为什么?
// 单进程服务, 当服务端向客户端提供业务处理服务时, 没有办法accet, 不能处理连接
namespace ns_server
{
static const uint16_t defaultport=8081;
static int backlog=32;
using func_t=functionstring(const string&)>; // 回调函数,一种处理逻辑
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port=defaultport)
:func_(func)
,port_(port)
,quit_(true)
{
}
void InitServer()
{
// 1. 创建socket文件
listensock_=socket(AF_INET,SOCK_STREAM,0);
if(listensock_0)
{
cerr"create socket error"endl;
exit(SOCKET_ERR);
}
// 2. bind
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_port=htons(port_);
local.sin_family=AF_INET;
local.sin_addr.s_addr=INADDR_ANY;
int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
if(n0)
{
cerr"bind socket error"endl;
exit(BIND_ERR);
}
// 3. 监听
int m=listen(listensock_,backlog);
if(m0)
{
cerr"listen socket error"endl;
exit(LISTEN_ERR);
}
}
void Start()
{
quit_=false;
while(!quit_)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 4. 获取连接, accept
int sock=accept(listensock_,(struct sockaddr*)&client,&len);
if (sock 0) // accept失败并不会终止进程, 只要获取下一个连接
{
cerr "accept error" endl;
continue;
}
// 提取client信息 --- debug
string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列
// 5. 获取新连接成功后, 开始进行业务处理
cout"获取新连接成功: "sock" from "listensock_", " clientip "- " clientportendl;
// v1
service(sock,clientip,clientport);
}
}
// 流式 - 利用read和write
void service(int sock, const string&clientip,const uint16_t clientport)
{
string who=clientip + "-" + to_string(clientport);
char buffer[1024];
while(true)
{
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
string res=func_(buffer); // 进行回调
coutwho ">>> " resendl;
// 把收到的消息返回(写给客户端)
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
// 对方将连接关闭了
close(sock);
cout who " quit, me too"endl;
break;
}
else
{
close(sock);
cerr"read error: "strerror(errno)endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t port_;
int listensock_;
bool quit_; // 标志服务器是否运行字段
func_t func_;
};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;
// ./tcp_server port
// 使用手册
static void usage(string proc)
{
cout"usage:nt"proc" portn"endl;
}
string echo(const string&message)
{
return message;
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);
unique_ptrTcpServer> tsvr(new TcpServer(echo,port));
tsvr->InitServer();
tsvr->Start();
return 0;
}
tcpClient.cc
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
static void usage(string proc)
{
cout"usage:nt"proc" serverip serverportn" endl;
}
// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
// 准备工作
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);
// 1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock 0)
{
cerr "create socket error: " strerror(errno) endl;
exit(SOCKET_ERR);
}
// (2) 客户端要不要bind呢? 要
// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接
// 2. connect 客户端向服务器发起连接请求
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_port=htons(serverport);
server.sin_family=AF_INET;
// server.sin_addr.s_addr=INADDR_ANY; 绝对不是
inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制
int cnt=5;
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败
{
sleep(1);
cout"正在给你重连, 重连次数还有: "cnt--endl;
if(cnt0)
break;
}
if(cnt0)
{
cerr"连接失败"endl;
exit(CONNECT_ERR);
}
char buffer[1024];
// 3. 连接成功
while(true)
{
string line;
cout"Enter>> ";
getline(cin,line);
write(sock,line.c_str(),line.size());
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if (s > 0)
{
buffer[s] = 0;
cout"server echo >>>"bufferendl;
}
else if (s == 0)
{
cerr "server quit" endl;
break;
}
else
{
cerr "read error: " strerror(errno) endl;
break;
}
}
close(sock);
return 0;
}
运行结果:
(2) v2_多进程版本
v2版本是把单执行流服务器改成多进程版的服务器
-
在accept获取新连接成功后,fork创建创建子进程,此时子进程对外提供服务, 父进程只进行accept
-
父进程的文件描述符会被子进程继承,但并不是父子共用同一张文件描述符表,因为子进程会拷贝继承父进程的文件描述符表
-
对于套接字文件也是相同的,父进程创建的子进程也会继承父进程的套接字文件,此时子进程就能够对特定的套接字文件进行读写操作,进而完成对对应客户端的服务
关于阻塞等待与非阻塞等待
- 若采用阻塞式等待,那么服务端还是需要等待服务完当前客户端,才能继续获取下一个连接请求,此时服务端仍然是以一种串行的方式为客户端提供服务
- 若采用非阻塞式等待,虽然在子进程为客户端提供服务期间服务端可以继续获取新连接,但此时服务端就需要将所有子进程的PID保存下来,并且需要不断花费时间检测子进程是否退出
- 由此可见两种都有缺陷,所以我们可以考虑让服务端不等待子进程退出
常见的方式有两种:
- 捕捉SIGCHLD信号,将其处理动作设置为忽略。
- 让父进程创建子进程,子进程再创建孙子进程,子进程退出,让孙子进程为客户端提供服务,孙进程的回收工作由OS来承担
下面是创建孙进程的方案:
tcpServer.hpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
namespace ns_server
{
static const uint16_t defaultport=8081;
static int backlog=32;
using func_t=functionstring(const string&)>; // 回调函数,一种处理逻辑
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port=defaultport)
:func_(func)
,port_(port)
,quit_(true)
{
}
void InitServer()
{
// 1. 创建socket文件
listensock_=socket(AF_INET,SOCK_STREAM,0);
if(listensock_0)
{
cerr"create socket error"endl;
exit(SOCKET_ERR);
}
// 2. bind
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_port=htons(port_);
local.sin_family=AF_INET;
local.sin_addr.s_addr=INADDR_ANY;
int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
if(n0)
{
cerr"bind socket error"endl;
exit(BIND_ERR);
}
// 3. 监听
int m=listen(listensock_,backlog);
if(m0)
{
cerr"listen socket error"endl;
exit(LISTEN_ERR);
}
}
void Start()
{
// signal(SIGCHLD,SIG_IGN); // ok, 最推荐
// signal(SIGCHLD,handler); // 回收子进程, 不太推荐
quit_=false;
while(!quit_)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 4. 获取连接, accept
int sock=accept(listensock_,(struct sockaddr*)&client,&len);
if (sock 0) // accept失败并不会终止进程, 只要获取下一个连接
{
cerr "accept error" endl;
continue;
}
// 提取client信息 --- debug
string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列
// 5. 获取新连接成功后, 开始进行业务处理
cout"获取新连接成功: "sock" from "listensock_", " clientip "- " clientportendl;
// v2: 多进程版本
// 子进程对外提供服务, 父进程只进行accept
pid_t id=fork();
if(id0)
{
close(sock);
continue;
}
else if(id==0) // child, 父进程的fd会被子进程继承吗? 会; 父子会用同一张文件描述符表吗?不会, 子进程会拷贝继承父进程的fd table
{
// 建议关闭掉不需要的fd
close(listensock_);
if(fork()>0) exit(0); // 就这一行代码
// 子进程已经退了(则下面的wait立马返回, 回收子进程资源), 孙子进程在运行(无父进程, 变成孤儿进程, 被系统领养),提供服务
// 孙子进程的回收工作由系统来承担
service(sock,clientip,clientport);
exit(0);
}
// 父进程, 一定要关闭不需要的fd(否则会导致父进程的文件描述符变少, 即父进程文件描述符资源的浪费[文件描述符泄露])
close(sock);
// 不等待子进程, 会导致子进程僵尸之后无法回收, 近而导致内存泄漏
pid_t ret=waitpid(id,nullptr,0); // 父进程默认是阻塞的, waitpid(id,nullptr,WNOHANG);不推荐
if(ret==id)
cout "wait child "id " success" endl;
}
}
// 流式 - 利用read和write
void service(int sock, const string&clientip,const uint16_t clientport)
{
string who=clientip + "-" + to_string(clientport);
char buffer[1024];
while(true)
{
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
string res=func_(buffer); // 进行回调
coutwho ">>> " resendl;
// 把收到的消息返回(写给客户端)
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
// 对方将连接关闭了
close(sock);
cout who " quit, me too"endl;
break;
}
else
{
close(sock);
cerr"read error: "strerror(errno)endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t port_;
int listensock_;
bool quit_; // 标志服务器是否运行字段
func_t func_;
};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;
// ./tcp_server port
// 使用手册
static void usage(string proc)
{
cout"usage:nt"proc" portn"endl;
}
string echo(const string&message)
{
return message;
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);
unique_ptrTcpServer> tsvr(new TcpServer(echo,port));
tsvr->InitServer();
tsvr->Start();
return 0;
}
tcpClient.cc
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
static void usage(string proc)
{
cout"usage:nt"proc" serverip serverportn" endl;
}
// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
// 准备工作
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);
// 1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock 0)
{
cerr "create socket error: " strerror(errno) endl;
exit(SOCKET_ERR);
}
// (2) 客户端要不要bind呢? 要
// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接
// 2. connect 客户端向服务器发起连接请求
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_port=h服务器托管网tons(serverport);
server.sin_family=AF_INET;
// server.sin_addr.s_addr=INADDR_ANY; 绝对不是
inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制
int cnt=5;
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败
{
sleep(1);
cout"正在给你重连, 重连次数还有: "cnt--endl;
if(cnt0)
break;
}
if(cnt0)
{
cerr"连接失败"endl;
exit(CONNECT_ERR);
}
char buffer[1024];
// 3. 连接成功
while(true)
{
string line;
cout"Enter>> ";
getline(cin,line);
write(sock,line.c_str(),line.size());
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if (s > 0)
{
buffer[s] = 0;
cout"server echo >>>"bufferendl;
}
else if (s == 0)
{
cerr "server quit" endl;
break;
}
else
{
cerr "read error: " strerror(errno) endl;
break;
}
}
close(sock);
return 0;
}
运行结果:
(3) v3_多线程版本
频繁的创建进程会给OS带来巨大的负担,并且创建线程的成本比创建线程高得多。因此在实现多执行流的服务器时最好采用多线程进行实现。
主线程创建出新线程后,也是需要等待新线程退出的,否则也会造成类似于僵尸进程这样的问题。但对于线程来说,如果不想让主线程等待新线程退出,直接线程分离即可,当这个线程退出时系统会自动回收该线程所对应的资源。
各个线程共享是同一张文件描述符表,也就是说服务进程(主线程)调用accept函数获取到一个文件描述符后,其他创建的新线程是能够直接访问这个文件描述符的。
所以不能关闭不要的套接字文件描述符,该文件描述符的关闭操作应该又新线程来执行。因为是新线程为客户端提供服务的,只有当新线程为客户端提供的服务结束后才能将该文件描述符关闭。
tcpServer.hpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
namespace ns_server
{
static const uint16_t defaultport=8081;
static int backlog=32;
using func_t=functionstring(const string&)>; // 回调函数,一种处理逻辑
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd, const string&ip,const uint16_t&port,TcpServer*ts)
:sock(fd)
,clientip(ip)
,clientport(port)
,current(ts)
{
}
public:
int sock;
string clientip;
uint16_t clientport;
TcpServer*current;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port=defaultport)
:func_(func)
,port_(port)
,quit_(true)
{
}
void InitServer()
{
// 1. 创建socket文件
listensock_=socket(AF_INET,SOCK_STREAM,0);
if(listensock_0)
{
cerr"create socket error"endl;
exit(SOCKET_ERR);
}
// 2. bind
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_port=htons(port_);
local.sin_family=AF_INET;
local.sin_addr.s_addr=INADDR_ANY;
int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
if(n0)
{
cerr"bind socket error"endl;
exit(BIND_ERR);
}
// 3. 监听
int m=listen(listensock_,backlog);
if(m0)
{
cerr"listen socket error"endl;
exit(LISTEN_ERR);
}
}
void Start()
{
// signal(SIGCHLD,SIG_IGN); // ok, 最推荐
// signal(SIGCHLD,handler); // 回收子进程, 不太推荐
quit_=false;
while(!quit_)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 4. 获取连接, accept
int sock=accept(listensock_,(struct sockaddr*)&client,&len);
if (sock 0) // accept失败并不会终止进程, 只要获取下一个连接
{
cerr "accept error" endl;
continue;
}
// 提取client信息 --- debug
string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列
// 5. 获取新连接成功后, 开始进行业务处理
cout"获取新连接成功: "sock" from "listensock_", " clientip "- " clientportendl;
// v3: 多线程版本 --- 原生多线程
// 1. 要不要关闭不要的socket? 绝对不能,一个进程的文件描述符表共享, 关了影响其他线程
// 2. 要不要回收线程?要;如何回收?会不会阻塞
pthread_t tid;
ThreadData*td=new ThreadData(sock,clientip,clientport,this); // 要开出一块独立的空间
pthread_create(&tid,nullptr,threadRoutine,td);
}
}
static void*threadRoutine(void*args)
{
pthread_detach(pthread_self());
ThreadData*td=static_castThreadData*>(args);
td->current->service(td->sock,td->clientip,td->clientport);
delete td;
}
// 流式 - 利用read和write
void service(int sock, const string&clientip,const uint16_t clientport)
{
string who=clientip + "-" + to_string(clientport);
char buffer[1024];
while(true)
{
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
string res=func_(buffer); // 进行回调
coutwho ">>> " resendl;
// 把收到的消息返回(写给客户端)
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
// 对方将连接关闭了
close(sock);
cout who " quit, me too"endl;
break;
}
else
{
close(sock);
cerr"read error: "strerror(errno)endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t port_;
int listensock_;
bool quit_; // 标志服务器是否运行字段
func_t func_;
};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;
// ./tcp_server port
// 使用手册
static void usage(string proc)
{
cout"usage:nt"proc" portn"endl;
}
string echo(const string&message)
{
return message;
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);
unique_ptrTcpServer> tsvr(new TcpServer(echo,port));
tsvr->InitServer();
tsvr->Start();
return 0;
}
tcpClient.cc
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
static void usage(string proc)
{
cout"usage:nt"proc" serverip serverportn" endl;
}
// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
// 准备工作
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);
// 1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock 0)
{
cerr "create socket error: " strerror(errno) endl;
exit(SOCKET_ERR);
}
// (2) 客户端要不要bind呢? 要
// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接
// 2. connect 客户端向服务器发起连接请求
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_port=htons(serverport);
server.sin_family=AF_INET;
// server.sin_addr.s_addr=INADDR_ANY; 绝对不是
inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制
int cnt=5;
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败
{
sleep(1);
cout"正在给你重连, 重连次数还有: "cnt--endl;
if(cnt0)
break;
}
if(cnt0)
{
cerr"连接失败"endl;
exit(CONNECT_ERR);
}
char buffer[1024];
// 3. 连接成功
while(true)
{
string line;
cout"Enter>> ";
getline(cin,line);
write(sock,line.c_str(),line.size());
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if (s > 0)
{
buffer[s] = 0;
cout"server echo >>>"bufferendl;
}
else if (s == 0)
{
cerr "server quit" endl;
break;
}
else
{
cerr "read error: " strerror(errno) endl;
break;
}
}
close(sock);
return 0;
}
运行结果:
(4) v4_线程池版本
多线程版的问题:
- 每当有新连接到来时,服务端的主线程都会为该客户端创建提供服务的新线程,当服务结束时就会将新线程销毁,这样做既麻烦又效率低下,每当有新连接到来才开始创建提供服务的新线程
- 若有大量的客户端请求,此时服务端要为每一个客户端创建对应的服务线程。计算机中的线程越多,CPU的压力越大
线程池
- 在服务端预先创建一批线程,当有客户端请求连接时就让这些线程为客户端提供服务,此时客户端一来就有线程为其提供服务,而不是当客户端来了才创建对应的服务线程(减少了频繁创建线程的开销)
- 当某个线程为客户端提供完服务后,不要让该线程退出,而是让该线程继续为下一个客户端提供服务,如果当前没有客户端连接请求,则可以让该线程先进入休眠状态,当有客户端连接到来时再将该线程唤醒。
- 服务端创建的这一批线程的数量不能太多,此时CPU的压力也就不会太大
task.hpp
#pragma once
#include
#include
#include
#include
#include
using namespace std;
using cb_t=functionvoid(int sock, const string&,const uint16_t&)>;
class Task
{
public:
Task()
{
}
Task(int sock, const string& ip,const uint16_t&port,cb_t cb)
:_sock(sock)
,_ip(ip)
,_port(port)
,_cb(cb)
{
}
void operator()()
{
_cb(_sock,_ip,_port);
}
~Task()
{
}
private:
int _sock;
string _ip;
uint16_t _port;
cb_t _cb;
};
LockGuard.hpp
#include
#include
using namespace std;
class Mutex //自己不维护锁,由外部传入
{
public:
Mutex(pthread_mutex_t* mutex)
:_pmutex(mutex)
{
}
void lock()
{
pthread_mutex_lock(_pmutex);
}
void unlock()
{
pthread_mutex_unlock(_pmutex);
}
~Mutex()
{}
private:
pthread_mutex_t* _pmutex; //锁的指针
};
class LockGuard //自己不维护锁,由外部传入
{
public:
LockGuard(pthread_mutex_t* mutex)
:_mutex(mutex)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
private:
Mutex _mutex; //锁的指针
};
thread.hpp
#include
#include
using namespace std;
class Thread
{
public:
typedef enum
{
NEW=0,
RUNNING,
EXITED
}ThreadStatus;
typedef void (*func_t)(void*); //函数指针, 参数是void*
Thread(int num, func_t func, void*args)
:_tid(0)
,_status(NEW)
,_func(func)
,_args(args)
{
char name[128];
snprintf(name,sizeof(name),"thread-%d",num);
_name=name;
}
int status() {return _status;}
string threadname() {return _name;}
pthread_t thread_id()
{
if(_status==RUNNING)
return _tid;
else
return 0;
}
// runHelper是不是类的成员函数, 而类的成员函数, 具有默认参数this, 需要static
// void*runHelper(Thread*this, void*args) , 而pthread_create要求传的参数必须是: void*的, 即参数不匹配
// 但是static会有新的问题: static成员函数, 无法直接访问类属性和其他成员函数
static void*runHelper(void*args)
{
Thread*ts=(Thread*)args; //就拿到了当前对象
// _func(_args);
(*ts)();
}
//仿函数
void operator()()
{
_func(_args);
}
void run()
{
int n=pthread_create(&_tid,nullptr,runHelper,this); //this: 是当前线程对象Thread
if(n!=0) exit(-1);
_status=RUNNING;
}
void join()
{
int n=pthread_join(_tid,nullptr);
if(n!=0)
{
cerr" main thread join thread " _name " error "endl;
}
_status=EXITED;
}
~Thread()
{}
private:
pthread_t _tid;
string _name;
func_t _func; //线程未来要执行的回调
void*_args; //调用回调函数时的参数
ThreadStatus _status;
};
threadPool_v4.hpp
#include
#include
#include
#include
#include
#include"thread.hpp"
#include"lockGuard.hpp"
using namespace std;
const static int N=5;
templateclass T>
class threadPool
{
public:
pthread_mutex_t* getlock()
{
return &_lock;
}
void threadWait()
{
pthread_cond_wait(&_cond,&_lock);
}
void threadWakeup()
{
pthread_cond_signal(&_cond); // 唤醒在条件变量下等待的线程
}
bool isEmpty()
{
return _tasks.empty();
}
T popTask()
{
T t=_tasks.front();
_tasks.pop();
return t;
}
static void threadRoutine(void*args)
{
threadPoolT>*tp=static_castthreadPoolT>*>(args);
while(true)
{
// 1. 检测有没有任务 --- 本质是看队列是否为空
// --- 本质就是在访问共享资源 --- 必定加锁
// 2. 有: 处理
// 3. 无: 等待
// 细节: 必定加锁
T t;
{
LockGuard lockguard(tp->getlock());
while (tp->isEmpty())
{
// 等待, 在条件变量下等待
tp->threadWait();
}
t = tp->popTask(); // 把任务从公共区域拿到私有区域
}
// for test
// 处理任务应不应该在临界区中处理, 不应该, 这是线程自己私有的事情
t();
}
}
static threadPoolT> * getinstance()
{
if (instance == nullptr) // 为什么要这样? 提高效率, 减少加锁的次数
{
LockGuard lockguard(&instance_lock);
if (instance == nullptr)
{
cout"线程池单例形成"endl;
instance = new threadPoolT>();
instance->init();
instance->start();
}
}
return instance;
}
void init()
{
for(int i=0;i_num;++i)
{
_threads.push_back(Thread(i,threadRoutine,this));
couti" thread running"endl;
}
}
void check()
{
for(auto&t:_threads)
{
coutt.threadname()" running..."endl;
}
}
void start()
{
for(auto&t:_threads)
{
t.run();
}
}
void pushTask(const T&t)
{
LockGuard lockguard(&_lock);
_tasks.push(t);
threadWakeup();
}
~threadPool()
{
for(auto&t:_threads)
{
t.join();
}
pthread_mutex_destroy(&_lock);
pthread_cond_destroy(&_cond);
}
private:
threadPool(int num=N)
:_num(num)
{
pthread_mutex_init(&_lock,nullptr);
pthread_cond_init(&_cond,nullptr);
}
threadPool(const threadPoolT>&tp)=delete;
void operator=(const threadPoolT>&tp)=delete;
private:
vectorThread> _threads;
int _num;
queueT> _tasks;
pthread_mutex_t _lock;
pthread_cond_t _cond;
static threadPoolT>*instance;
static pthread_mutex_t instance_lock;
};
templateclass T>
threadPoolT> * threadPoolT>::instance=nullptr;
templateclass T>
pthread_mutex_t threadPoolT>::instance_lock=PTHREAD_MUTEX_INITIALIZER;
tcpServer.hpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
#include"threadPool_v4.hpp"
#include"task.hpp"
using namespace std;
namespace ns_server
{
static const uint16_t defaultport=8081;
static int backlog=32;
using func_t=functionstring(const string&)>; // 回调函数,一种处理逻辑
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd, const string&ip,const uint16_t&port,TcpServer*ts)
:sock(fd)
,clientip(ip)
,clientport(port)
,current(ts)
{
}
public:
int sock;
string clientip;
uint16_t clientport;
TcpServer*current;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port=defaultport)
:func_(func)
,port_(port)
,quit_(true)
{
}
void InitServer()
{
// 1. 创建socket文件
listensock_=socket(AF_INET,SOCK_STREAM,0);
if(listensock_0)
{
cerr"create socket error"endl;
exit(SOCKET_ERR);
}
// 2. bind
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_port=htons(port_);
local.sin_family=AF_INET;
local.sin_addr.s_addr=INADDR_ANY;
int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
if(n0)
{
cerr"bind socket error"endl;
exit(BIND_ERR);
}
// 3. 监听
int m=listen(listensock_,backlog);
if(m0)
{
cerr"listen socket error"endl;
exit(LISTEN_ERR);
}
}
void Start()
{
quit_=false;
while(!quit_)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 4. 获取连接, accept
int sock=accept(listensock_,(struct sockaddr*)&client,&len);
if (sock 0) // accept失败并不会终止进程, 只要获取下一个连接
{
cerr "accept error" endl;
continue;
}
// 提取client信息 --- debug
string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列
// 5. 获取新连接成功后, 开始进行业务处理
cout"获取新连接成功: "sock" from "listensock_", " clientip "- " clientportendl;
// v4: 线程池版本
// 一旦用户来了,你才创建线程, 线程池吗
// 使用线程池的时候, 一定是有限的线程个数, 一定要处理短任务
Task t(sock,clientip,clientport, bind(&TcpServer::service, this,placeholders::_1,placeholders::_2,placeholders::_3));
threadPoolTask>::getinstance()->pushTask(t);
}
}
static void*threadRoutine(void*args)
{
pthread_detach(pthread_self());
ThreadData*td=static_castThreadData*>(args);
td->current->service(td->sock,td->clientip,td->clientport);
delete td;
}
// 流式 - 利用read和write
void service(int sock, const string&clientip,const uint16_t clientport)
{
string who=clientip + "-" + to_string(clientport);
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
string res = func_(buffer); // 进行回调
cout who ">>> " res endl;
// 把收到的消息返回(写给客户端)
write(sock, res.c_str(), res.size());
}
else if (s == 0)
{
cout who " quit, me too" endl;
}
else
{
close(sock);
cerr "read error: " strerror(errno) endl;
}
close(sock);
}
~TcpServer()
{
}
private:
uint16_t port_;
int listensock_;
bool quit_; // 标志服务器是否运行字段
func_t func_;
};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;
// ./tcp_server port
// 使用手册
static void usage(string proc)
{
cout"usage:nt"proc" portn"endl;
}
string echo(const string&message)
{
return message;
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);
unique_ptrTcpServer> tsvr(new TcpServer(echo,port));
tsvr->InitServer();
tsvr->Start();
return 0;
}
tcpClient.cc
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
static void usage(string proc)
{
cout"usage:nt"proc" serverip serverportn" endl;
}
// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
// 准备工作
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);
// 1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock 0)
{
cerr "create socket error: " strerror(errno) endl;
exit(SOCKET_ERR);
}
// (2) 客户端要不要bind呢? 要
// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接
// 2. connect 客户端向服务器发起连接请求
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_port=htons(serverport);
server.sin_family=AF_INET;
// server.sin_addr.s_addr=INADDR_ANY; 绝对不是
inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制
int cnt=5;
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接服务器托管网失败
{
sleep(1);
cout"正在给你重连, 重连次数还有: "cnt--endl;
if(cnt0)
break;
}
if(cnt0)
{
cerr"连接失败"endl;
exit(CONNECT_ERR);
}
char buffer[1024];
// 3. 连接成功
while(true)
{
string line;
cout"Enter>> ";
getline(cin,line);
write(sock,line.c_str(),line.size());
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if (s > 0)
{
buffer[s] = 0;
cout"server echo >>>"bufferendl;
}
else if (s == 0)
{
cerr "server quit" endl;
break;
}
else
{
cerr "read error: " strerror(errno) endl;
break;
}
}
close(sock);
return 0;
}
运行结果:
ip serverportn” }
// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
// 准备工作
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);
// 1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock > ";
getline(cin,line);
write(sock,line.c_str(),line.size());
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if (s > 0)
{
buffer[s] = 0;
cout>>"
}
运行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/eadac05d3a6c43dbb8c5e44f9ccebca6.png)
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
1. 前言 emmm,说起网络知识学习肯定离不来wireshark工具,这个工具能够帮助我们快速地定位网络问题以及帮助正在学习网络协议这块的知识的同学验证理论与实际的一大利器,平时更多的只是停留在初步的使用阶段。也是利用部门内部的网络兴趣小组的讨论机会,私下对…