把前面学到的api串联起来,做一个能跑起来的客户端和服务器端,客户端和服务器端采用阻塞的同步读写方式完成通信 windows环境下用boost库的话记得右键项目-属性-VC++目录-两个包含目录,以及引用头文件boost/asio.hpp
前置知识:多线程,智能指针
t.join()
t.join()方法只会使主线程(或者说调用t.join()的线程)进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。之前对于join()方法只是了解它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程。能够使得线程之间的并行执行变成串行执行。
需要注意的是,t1.join()需要等t1.start()执行之后执行才有效果,此外,如果t1.join()放在t2.start()之后的话,仍然会是交替执行,然而并不是没有效果
join()方法的底层是利用wait()方法实现的。可以看出,join方法是一个同步方法,当主线程调用t1.join()方法时,主线程先获得了t1对象的锁,随后进入方法,调用了t1对象的wait()方法,使主线程进入了t1对象的等待池,此时,A线程则还在执行,并且随后的t2.start()还没被执行,因此,B线程也还没开始。等到A线程执行完毕之后,主线程继续执行,走到了t2.start(),B线程才会开始执行。
ThreadTest t1=new ThreadTest("A");
ThreadTest t2=new ThreadTest("B");
ThreadTest t3=new ThreadTest("C");
System.out.println("t1start");
t1.start();
System.out.println("t1end");
System.out.println("t2start");
t2.start();
System.out.println("t2end");
t1.join();
System.out.println("t3start");
t3.start();
System.out.println("t3end");
System.out.println(Thread.currentThread().getName()+" end");
结果如下
1.
t1start
1. t1end
1. t2start
1. t2end
1. A-1
1. B-1
1. A-2
1. A-3
1. A-4
1. A-5
1. B-2
1. t3start
1. t3end
1. B-3
1. main end
1. B-4
1. B-5
1. C-1
1. C-2
1. C-3
1. C-4
1. C-5
主线程在t1.join()方法处停止,并需要等待A线程执行完毕后才会执行t3.start(),然而,并不影响B线程的执行。因此,可以得出结论,t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
PS:join源码中,只会调用wait方法,并没有在结束时调用notify,这是因为线程在die的时候会自动调用自身的notifyAll方法,来释放所有的资源和锁。
同步收发客户端编写
- 上下文
- 构造对端端点(在这里服务端也是本机,所以本机地址);
- 套接字
- 连接(做好连接失败策略)
- 发送:视为写入套接字
- 收取:视作从套接字里面读
#include<boost/asio.hpp>
#include <iostream>
#include<stdio.h>
//发送或接受的最大长度规定为1024字节
const int MAX_LENGTH = 1024;
int main()
{
try
{
boost::asio::io_context ioc;
//构造endpoint,对端地址为本机
boost::asio::ip::tcp::endpoint remote_ep(
boost::asio::ip::address::from_string("127.0.0.1"), 10086);
//第二个参数是对端协议,也可以不写
boost::asio::ip::tcp::socket sock(ioc, remote_ep.protocol());
//设定一个错误
boost::system::error_code error = boost::asio::error::host_not_found;
//error作为参数传进去,如果失败通过这个参数进行返回
sock.connect(remote_ep, error);
if (error) {
std::cout << "connect failed,code is" << error.value() <<
"error msg is" << error.message() << std::endl;
return 0;
}
std::cout << "Enter message";//自此连接成功
//客户端的发
//request承载要发送的信息
char request[MAX_LENGTH];
//通过一行一行读取用户输入放在数组里
std::cin.getline(request, MAX_LENGTH);
size_t request_length = strlen(request);
//通过全局函数一次性发送完
boost::asio::write(sock, boost::asio::buffer(request, request_length));
//客户端的收
char reply[MAX_LENGTH];
size_t reply_length = boost::asio::read(sock, boost::asio::buffer(reply, request_length));
std::cout << "Reply is:";
//用cout的write函数输出到控制台
std::cout.write(reply, reply_length);
std::cout << "\n";
}
catch (std::exception& e)//也可以用前面boost的异常捕获
{
std::cerr << "Exception:" << e.what() << std::endl;
}
return 0;
}
服务端的构造
int main()
{
try
{
boost::asio::io_context ioc;
server(ioc, 10086);//欢迎
//得等子线程全部退出,主线程才能退出
for (auto& t : thread_set) {
t->join();
}
}
catch (const std::exception& e)
{
std::cerr << "Excaption" << e.what() << "\n";
}
}
//创建了一个欢迎套接字acceptor
void server(boost::asio::io_context& io_context, unsigned short port) {
boost::asio::ip::tcp::acceptor a(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
for (;;) {
socket_ptr socket(new boost::asio::ip::tcp::socket(io_context));
a.accept(*socket);//欢迎套接字a进行迎宾
//c++11标准,效率更高
//创建一个智能指针t指向线程duixiang
//创建一个新的 std::thread 对象,它将执行 session 函数,
// 并且 socket 是传递给 session 函数的参数。
auto t = std::make_shared<std::thread>(session, socket);
thread_set.insert(t);
}
}
创建session函数,该函数为服务器处理客户端请求,每当我们获取客户端连接后就调用该函数。在session函数里里进行echo方式的读写,所谓echo就是应答式的处理 创建线程调用session函数可以分配独立的线程用于socket的读写,保证acceptor不会因为socket的读写而阻塞。
//这个函数是跑在线程里的,来一个客户,开一个线程
void session(socket_ptr sock) {
try
{
for (;;) {
char data[max_length];
//为了安全把数组里面数据都清空
memset(data, '\0', max_length);
boost::system::error_code error;
size_t length = sock->read_some(boost::asio::buffer(data, max_length), error);
if (error == boost::asio::error::eof) {
std::cout << "connection closed by peer" << std::endl;
break;
}
else if (error) {
throw boost::system::system_error(error);
}
std::cout << "receive from " << sock->remote_endpoint().address().to_string() << std::endl;
std::cout << "receive message is " << data << std::endl;
//回传给对方
boost::asio::write(*sock, boost::asio::buffer(data, length));
}
}
catch (const std::exception& e)
{
std::cerr << "Exception in thread: " << e.what() << "\n" << std::endl;
}
}
头文件
#include<boost/asio.hpp>
#include <iostream>
//这个集合用来存储线程
#include<set>
#include<memory>
const int max_length = 1024;
typedef std::shared_ptr<boost::asio::ip::tcp::socket> socket_ptr;
std::set<std::shared_ptr<std::thread>> thread_set;
//存放线程指针的数组
同步读写的优劣
1 同步读写的缺陷在于读写是阻塞的,如果客户端对端不发送数据服务器的read操作是阻塞的,这将导致服务器处于阻塞等待状态。
2 可以通过开辟新的线程为新生成的连接处理读写,但是一个进程开辟的线程是有限的,约为2048个线程,在Linux环境可以通过unlimit增加一个进程开辟的线程数,但是线程过多也会导致切换消耗的时间片较多。
3 该服务器和客户端为应答式,实际场景为全双工通信模式,发送和接收要独立分开。
4 该服务器和客户端未考虑粘包处理。
综上所述,是我们这个服务器和客户端存在的问题,为解决上述问题,我们在接下里的文章里做不断完善和改进,主要以异步读写改进上述方案。
当然同步读写的方式也有其优点,比如客户端连接数不多,而且服务器并发性不高的场景,可以使用同步读写的方式。使用同步读写能简化编码难度。
一些问题:
报错throw_error.hpp中未定义_什么玩意来着,解决方法:
在cpp文件前加上#define BOOST_DISABLE_CURRENT_LOCATION,调试就不报错了,但还是红,可以不调试直接执行就没问题。 贴一个解决地址:stackoverflow.com/questions/7…
error C1189: #error : WinSock.h has already been included
问题描述:在编译文件中包含多个文件使用使boost::asio时可能提示以上错误
解决办法:项目 ->属性 -> C/C++ -> 预处理器 -> 预处理器定义
在其中添加 : WIN32_LEAN_AND_MEAN(WIN32_LEAN_AND_MEAN表示不包含一些极少使用和偏门的资料)