什么是套接字
套接字是一个主机本地应用程序所创建的,为操作系统所控制的接口。
应用进程通过这个接口,使用传输层提供的服务,跨网络发送/接收消息到其他应用进程。
Client/Server模式的通信接口——套接字接口
套接字:描述符
OS将文件描述符实现为一个指针数组,指向一个内部的数据结构:进程描述符表的下标
套接字和文件类似,每个活动套接字使用一个小整数标识,进程的文件描述符和套接字描述符值不能相同
socket函数:创建套接字描述符(不是open函数)
进程的文件描述符表

socket
int socket(int domain,int type,int protocol)
功能:创建一个新的套接字,返回套接字描述符
参数说明:
domain:域类型,指明使用的协议栈,如TCP/IP使用的是PF_INET
type:指明需要的服务类型,如
SOCK_DGRAM:数据报服务,UDP协议
SOCK_STREAM:流服务,TCP协议
protocol:一般都取0
举例:s = socket(PF_INET,SOCK_STREAM,0)
connect
int connect(int sockfd,struct sockaddr *server_addr,int sockaddr_len)
功能:同远程服务器建立主动连接,成功时返回0,若连接失败返回-1
参数说明:
Sockfd:套接字描述符,指明创建连接的套接字
Server_addr:指明远程端点:IP地址和端口号
sockaddr_len:地址长度
举例(P49):connect(s,remaddr,remaddrlen)
send
int send(int sockfd,const void * data,int data_len,unsigned int flags)
功能:
在TCP连接上发送数据,返回成功传送数据的长度,出错时返回-1
send会将外发数据复制到OS内核中,也可以使用send发送面向连接的UDP报文
参数说明:
sockfd:套接字描述符
data:指向要发送数据的指针
data_len:数据长度
flags:一直为0
recv
int recv(int sockfd,void *buf,int buf_len,unsigned int flags);
功能:
从TCP接收数据,返回实际接收的数据长度,出错时返回-1
服务器使用其接收客户请求,客户使用它接受服务器的应答。如果没有数据,将阻塞,如果收到的数据大于缓存的大小,多余的数据将丢弃。也可以使用recv接收面向连接的UDP的报文
参数说明:
Sockfd:套接字描述符
Buf:指向内存块的指针
Buf_len:内存块大小,以字节为单位
flags:一般为0
举例:recv(sockfd,buf,8192,0)
close
close(int sockfd);
功能:
撤销套接字
如果只有一个进程使用,立即终止连接并撤销套接字,如果多个进程共享该套接字,将引用数减一,如果引用数降到零,则撤销它。
参数说明:
Sockfd:套接字描述符
举例:close(socket_descriptor)
bin
int bin(int sockfd,struct sockaddr *my_addr,int addrlen)
功能:为套接字指明一个本地端点地址
TCP/IP协议使用sockaddr_in结构,包含IP地址和端口号
服务器使用它来指明熟知的端口号,然后等待连接
参数说明:
Sockfd:套接字描述符,指明创建连接的套接字
my_addr:本地地址,IP地址和端口号
addrlen:地址长度
举例:bin(sockfd,(struct sockaddr*)&address,sizeof(address));
listen
int listen(int sockfd,int input_queue_size)
功能:
面向连接的服务器使用它将一个套接字置为被动模式,并准备接收传入连接。用于服务器,指明某个套接字连接是被动的。
参数说明:
Sockfd:套接字描述符,指明创建连接的套接字
input_queue_size:该套接字使用的队列长度,指定在请求队列中允许的最大请求数
举例:listen(sockfd,20)
accept
int accept(int sockfd,void *addr,int *addrlen);
int accept(int sockfd,struct sockaddr *addr,int *addrlen);
功能:获取传入连接请求,返回新的连接的套接字描述符。
为每个新的连接请求创建了一个新的套接字,服务器只对新的连接使用该套接字,原来的监听套接字接受其他的连接请求。
新的连接上传输数据使用新的套接字,使用完毕,服务器将关闭这个套接字。
参数说明:
Sockfd:套接字描述符,指明正在监听的套接字
addr:提出连接请求的主机地址
addrlen:地址长度
举例:
new_sockfd = accept(sockfd,(struct sockaddr*)&address,sizeof(address));
用于整数转换的实用例程
网络字节顺序:最高位字节在前
有结套接字例程要求参数按照网络字节顺序存储。如sockaddr_in
需要网络字节顺序和本地主机字节顺序进行转换的函数,坚持使用,便于移植。
分为短(short 16位)和长(long 32位)两种
htons:将一个短整数从本地字节顺序转换为网络字节顺序;
ntohs:将一个短整数从网络字节顺序转换为本地字节顺序;
htonl和ntohl:类似如上

套接字API中的主要系统调用
read和write
在UNIX和Linux中,可以代替recv和send,因为都调用内核的sosend实现。

Linux
TCP服务端
Ubuntu 16.04.6 LTS
mkdir tcpserver
cd tcpserver
vim tcpserver.cpp
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
using namespace std;
//#ifndef errno
//extern int errno;
//#endif
int main(int argc,char *argv[])
{
unsigned short port = 8080;
if(argc>1)
{
port = atoi(argv[1]);
}
//1 create socket
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<=0)
{
cerr<<"cretor socket error "<<strerror(errno)<<endl;
return -1;
}
//2 bind port
sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = htonl(0);
int re = ::bind(sock,(sockaddr*)&saddr,sizeof(saddr));
if(re != 0)
{
cerr<<"bind port "<<port<<" failed!"<<strerror(errno)<<endl;
return -1;
}
cout<<"bind port "<<port<<" success!"<<endl;
//3 listen
listen(sock,10);
//4 accept
{
sockaddr_in caddr;
socklen_t addrlen = 0;
int client_sock = accept(sock,(sockaddr*)&caddr,&addrlen);
cout<<"client sock = "<<client_sock<<endl;
// send
char buf[1024] = "wellcome to xms";
int len = send(client_sock,buf,strlen(buf),0);
cout<<"send len = "<<len<<endl;
// recv
len = recv(client_sock,buf,sizeof(buf)-1,0);
if(len>0)
{
buf[len] = '\0';
cout<<buf<<endl;
}
}
return 0;
}
Ubuntu 18.x
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
using namespace std;
int main(int argc,char *argv[])
{
unsigned short port = 8080;
if(argc>1)
{
port = atoi(argv[1]);
}
//1 create socket
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<=0)
{
cerr<<"cretor socket error "<<strerror(errno)<<endl;
return -1;
}
//2 bind port
sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = htonl(0);
int re = ::bind(sock,(sockaddr*)&saddr,sizeof(saddr));
if(re != 0)
{
cerr<<"bind port "<<port<<" failed!"<<strerror(errno)<<endl;
return -1;
}
cout<<"bind port "<<port<<" success!"<<endl;
//3 listen
listen(sock,10);
//4 accept
{
sockaddr_in caddr;
socklen_t addrlen = 0;
int client_sock = accept(sock,(sockaddr*)&caddr,&addrlen);
cout<<"client sock = "<<client_sock<<endl;
// send
char buf[1024] = "wellcome to xms";
int len = send(client_sock,buf,strlen(buf),0);
cout<<"send len = "<<len<<endl;
// recv
len = recv(client_sock,buf,sizeof(buf)-1,0);
if(len>0)
{
buf[len] = '\0';
cout<<buf<<endl;
}
}
return 0;
}
vim makefile
TARGET=tcpserver
OBJS=tcpserver.o
$(TARGET):$(OBJS)
g++ $+ -o $@
clean:
rm -rf $(TARGET)
rm -rf $(OBJS)
make
TCP客户端
mkdirr tcpclient
touch tcpclient.cpp
cp tcpserver/makefile tcpclient
cd tcpclient
vim tcpclient.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
using namespace std;
int main(int argc,char *argv[])
{
unsigned short port = 8080;
const char *ip = "127.0.0.1";
//tcpclient 192.168.0.205 8080
if(argc>2)
{
ip = argv[1];
port = atoi(argv[2]);
}
int sock = socket(AF_INET,SOCK_STREAM,0);
sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = inet_addr(ip);
int re = connect(sock,(sockaddr*)&saddr,sizeof(saddr));
if(re != 0)
{
cerr<<"connect "<<ip<<":"<<port<<" failed!"<<strerror(errno)<<endl;
return -1;
}
cout<<"connect "<<ip<<":"<<port<<" success!"<<endl;
char buf[1024] = {0};
int len = recv(sock,buf,sizeof(buf)-1,0);
if(len>0)
{
cout<<buf<<endl;
}
strcpy(buf,"send from client");
len = send(sock,buf,strlen(buf),0);
close(sock);
return 0;
}
makefile
TARGET=tcpclient
OBJS=tcpclient.o
$(TARGET):$(OBJS)
g++ $+ -o $@ -std=c++11
clean:
rm -rf $(TARGET)
rm -rf $(OBJS)
Windows
TCP客户端
#include <iostream>
#include <sys/types.h>
#ifdef _WIN32
#include <windows.h>
#define close closesocket
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif
#include <string.h>
using namespace std;
int main(int argc,char *argv[])
{
//windows初始化socket库
#ifdef _WIN32
WSADATA ws;
WSAStartup(MAKEWORD(2, 2), &ws);
#endif
unsigned short port = 8080;
const char *ip = "127.0.0.1";
//tcpclient 192.168.0.205 8080
if(argc>2)
{
ip = argv[1];
port = atoi(argv[2]);
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
cerr << "socket failed!" << strerror(errno) << endl;
return -1;
}
sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = inet_addr(ip);
int re = connect(sock,(sockaddr*)&saddr,sizeof(saddr));
if(re != 0)
{
cerr<<"connect "<<ip<<":"<<port<<" failed!"<<strerror(errno)<<endl;
return -1;
}
cout<<"connect "<<ip<<":"<<port<<" success!"<<endl;
char buf[1024] = {0};
int len = recv(sock,buf,sizeof(buf)-1,0);
if(len>0)
{
cout<<buf<<endl;
}
strcpy(buf,"send from client");
len = send(sock,buf,strlen(buf),0);
close(sock);
return 0;
}
添加_CRT_SECURE_NO_WARNINGS


添加ws2_32.lib


输入参数IP+端口

linux server
./tcpserver &
windows client
./tcpclient
libevent和IO模型的学习总结
同步异步和阻塞
socket,阻塞,blocking,结果返回前,当前线程被挂起
epool select,非阻塞,nonblocking,结果返回之前,线程不阻塞
Sync,同步,功能调用无结果不返回,事件一件件做
Async,异步,功能调用后无结果立刻返回,等待通知,一件事没完成就可以做下一件——IOCP
IO模型
BIO,阻塞IO,Blocking IO,传统的socket编程方式
NIO,同步非阻塞IO,NonBlocking IO,主动询问是否就绪,select循环遍历检测
AIO,异步非阻塞IO,采用proactor模式,数据完成后,由OS主动通知应用程序IOCP
Reactor模式
event-driven architecture——事件驱动体系结构
reactor设计模式是event-driven architecture的一种实现方式
epoll基于事件驱动思想,采用reactor模式
事件分发器——事件分发器的两种模式,1Reactor,2Proactor
处理者(event handler)
说明
反应器设计模式(Reactor pattern)是一种为处理并发服务请求,并将请求提交到一个或者多个服务处理程序的事件设计模式。
当客户端请求抵达后,服务处理程序使用多路分配策略,由一个非阻塞的线程来接收所有的请求,然后派发这些请求至相关的工作线程进行处理。
简单说就是如何处理多个客户端的并发请求的解决模式。
首先Reactor模式中可定义三种角色:
1、Reactor负责监听和分配事件,将I/O事件分派给对应的Handler
2、Acceptor处理客户端新连接,并分派请求到处理器链中
3、Handlers执行非阻塞读/写任务,可用资源池来管理
4、Epoll本质来讲是同步非阻塞
Proactor模式
IOCP本质上来讲则是异步操作
Windows平台编译libevent
-
环境准备
Windows 10 64位
VS2019企业版
perl 编译openssl用
nasm
zlib 1.2.11源码,http://zlib.net/
openssl 1.1.1源码,https://www.openssl.org/source/
libevent 2.1.8源码, -
编译zlib

解压zlib-1.2.11.tar.gz到当前目录
打开x64_x86 Cross Tools Command Prompt for VS 2019
手动编译执行
nmake /f WIN32\Makefile.msc

批处理脚本编译,build_zlib_vs2019_32.bat
set VS="C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat"
set OUT = "D:\libevent\out\vs2019_32\zlib"
call %VS%
cd zlib-1.2.11
nmake /f win32\Makefile.msc clean
nmake /f win32\Makefile.msc
md %OUT%\lib
md %OUT%\bin
md %OUT%\include
copy /Y *.lib %OUT%\lib
copy /Y *.h %OUT%\include
copy /Y *.dll %OUT%\bin
copy /Y *.exe %OUT%\bin
pause
上面的bat脚本代码在不同版本下有可能会报错,因为权限的问题,换成下面绝对路径的写法
set VS = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat"
set OUT = "D:\libevent\out\vs2019_32\zlib"
call %VS%
cd zlib-1.2.11
nmake /f win32\Makefile.msc clean
nmake /f win32\Makefile.msc
md D:\libevent\out\vs2019_32\zlib\lib
md D:\libevent\out\vs2019_32\zlib\bin
md D:\libevent\out\vs2019_32\zlib\include
copy /Y *.lib D:\libevent\out\vs2019_32\zlib\lib
copy /Y *.h D:\libevent\out\vs2019_32\zlib\include
copy /Y *.dll D:\libevent\out\vs2019_32\zlib\bin
copy /Y *.exe D:\libevent\out\vs2019_32\zlib\bin
pause
以管理员身份运行


- 编译openssl
安装 nasm 汇编器,设置PATH环境变量,添加D:\libevent\nasm-2.13.03-win64\nasm-2.13.03
安装perl,默认安装即可
手动编译openssl
打开x64_x86 Cross Tools Command Prompt for VS 2019
set OUTPATH = "D:\libevent\out\vs2019_32\openssl"
perl Configure {VC-WIN32|VC-WIN64|VC-CE} --prefix=%OUTPATH%
批处理脚本编译
@echo "start compile openssl"
set VS = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat"
set OUT = "D:\libevent\out\vs2019_32\openssl"
call %VS%
D:
cd D:\libevent\openssl-1.1.1g
perl Configure VC-WIN32 --prefix=D:\libevent\out\vs2019_32\openssl
nmake clean
nmake
nmake install
@echo "compile end"
pause
perl Configure VC-WIN32 --prefix=%OUT%修改成这样无法正确执行,可能是权限问题,所以建议还是加上绝对路径。
- 编译libevent
手动编译
nmake /f Makefile.nmake OPENSSL_DIR=D:\libevent\out\vs2019_32\openssl
编译到最后会提示错误
LINK : fatal error LNK1181: 无法打开输入文件 openssl\lib\libeay32.lib
cd D:\libevent\libevent-master\test
修改Makefile.nmake
将SSL_LIBS的libeay32.lib和ssleay32.lib这两个文件替换成D:\libevent\out\vs2019_32\openssl\lib目录下的libcrypto.lib和libssl.lib
SSL_LIBS=..\libevent_openssl.lib $(OPENSSL_DIR)\lib\libeay32.lib $(OPENSSL_DIR)\lib\ssleay32.lib gdi32.lib User32.lib
修改为
SSL_LIBS=..\libevent_openssl.lib $(OPENSSL_DIR)\lib\libcrypto.lib $(OPENSSL_DIR)\lib\libssl.lib gdi32.lib User32.lib
保存再重新手动执行
nmake /f Makefile.nmake OPENSSL_DIR=D:\libevent\out\vs2019_32\openssl clean
nmake /f Makefile.nmake OPENSSL_DIR=D:\libevent\out\vs2019_32\openssl
直到出现这样的画面就可以了。
然后检测一下是否安装成功,打开目录D:\libevent\libevent-master\test
运行regress.exe,若仍然提示libcrypto-1_1.dll和libssl-1_1.dll缺失,可以将D:\libevent\out\vs2019_32\openssl\bin目录下的libcrypto-1_1.dll和libssl-1_1.dll拷贝到D:\libevent\libevent-master\test目录下,再重新执行regress.exe > …/…/out.txt
发现zlib没有测试通过,如何解决?

把之前编译好的zlib整个目录拷贝到D:\libevent\libevent-master目录下,并修改D:\libevent\libevent-master\test\Makefile.nmake\Makefile.nmake的CFLAGS,添加/I../zlib/include

添加..\zlib\lib\zdll.lib

添加regress_zlib.obj

修改D:\libevent\libevent-master\WIN32-Code\nmake\event2\event-config.h

先清除
nmake /f Makefile.nmake OPENSSL_DIR=D:\libevent\out\vs2019_32\openssl clean
再重新编译
cd D:\libevent\libevent-master
nmake /f Makefile.nmake
最后编译通过

测试一下zlib例子
cd D:\libevent\libevent-master\test
regress.exe

把zlib1.dll文件拷贝到D:\libevent\libevent-master\test目录下

D:\libevent\libevent-master\test>regress.exe > ../../out.txt
bufferevent_zlib测试成功了

build_libevent_vs2019_32.bat
@echo "start compile libevent"
set VS = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat"
set OUT = D:\libevent\out\vs2019_32\libevent
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat"
cd /d D:\libevent\libevent-master
nmake /f Makefile.nmake clean
nmake /f Makefile.nmake OPENSSL_DIR=D:\libevent\out\vs2019_32\openssl
md ..\out\vs2019_32\libevent\lib
md ..\out\vs2019_32\libevent\bin
md ..\out\vs2019_32\libevent\include
copy /Y *.lib ..\out\vs2019_32\libevent\lib
xcopy /S/Y include ..\out\vs2019_32\libevent\include
xcopy /S/Y WIN32-Code\nmake ..\out\vs2019_32\libevent\include
copy /Y *.dll ..\out\vs2019_32\libevent\bin
copy /Y *.exe ..\out\vs2019_32\libevent\bin
@echo "compile end"
pause
- 编写第一个libevent测试程序
visual studio 2019创建控制台C++应用程序,勾选“解决方案和项目放在同一目录”,工程项目保存到src目录下
工程目录结构如下:


项目工程右键属性,分别修改以下参数
(1)c/c++ ===> 预编译头 ===>不使用预编译头
(2)c/c++===> 常规 ===> 附加包含目录,添加$(SolutionDir)..\..\include
(3)链接器 ===> 常规 ===> 附加库目录,添加$(SolutionDir)..\..\lib
(4)链接器 ===> 输入 ===> 附加依赖项,添加libevent.lib;ws2_32.lib
(5)链接器 ===> 命令行 ,添加/NODEFAULTLIB:"libcmtd.lib"
修改main.cpp
#include <iostream>
#include <event2/event.h>
using namespace std;
int main()
{
#ifdef _WIN32
//初始化socket库,windows平台下一定要初始化socket库,而linux平台不用
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
std::cout << "test libevent!\n";
event_base* base = event_base_new();
if (base)
{
cout << "event_base_new success!" << endl;
}
system("pause");
return 0;
}
执行成功
test libevent!
event_base_new success!
Linux平台编译libevent
ubuntu18.04.01 64
apt-get install perl g++ make automake libtool unzip
zlib
tar -xvf zlib-1.2.11.tar.gz
cd zlib-1.2.11
./configure
make -j6
sudo make install
可以看到库文件都拷贝到/usr/local/lib目录下,头文件在/usr/local/include目录,在这些目录的文件,你的程序可以不用指定库文件路径,头文件也是一样。

openssl-1.1.1
tar -xvf openssl-1.1.1.tar.gz
./config
make -j6
sudo make install
同样在安装的时候也是把这些生成的文件拷贝到这些目录下
/usr/local/lib/
/usr/local/include
……
你的程序可以不用指定库文件路径,头文件也是一样。
libevent
在安装libevent之前,确保你的linux系统已经安装automake和libtool
unzip libevent-master.zip
cd libevent-master
./autogen.sh 生成configure
./configure
make -j6
sudo make install
test/regress >log.txt
zlib测试通过

ssl测试通过

在原来first-libevent项目的基础上修改一下,直接在linux环境下编译。
vim makefile
firstlibevent:first_libevent.cpp
g++ $^ -o $@ -levent
./$@
clean:
rm -rf firstlibevent
rm -rf *.o
按下esc,wq退出
$: make
g++ first_libevent.cpp -o firstlibevent -levent -L /usr/local/lib
./firstlibevent
./firstlibevent: error while loading shared libraries: libevent-2.2.so.1: cannot open shared object file: No such file or directory
linux 缺少动态连接库.so——cannot open shared object file: No such file or directory
$: whereis libevent-2.2.so
.1
libevent-2.2.so: /usr/local/lib/libevent-2.2.so.1
$: ldd /usr/local/lib/libevent-2.2.so.1
linux-vdso.so.1 => (0x00007fff8638f000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f3e29e84000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3e29aba000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3e2a2f8000)
libevent-2.2.so.1软链接的路径是正常存在的,出现这种情况,有可能是$LD_LIBRARY_PATH环境变量设置的问题
(caffe2_env) zhoujianwen@zhoujianwen-System:/media/zhoujianwen/Data/libevent/src/first_libevent$ echo $LD_LIBRARY_PATH
/usr/local/cuda-10.0/lib64
在makefile头部添加
LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
注意:它只对当次make执行操作有效,make之后再单独执行./firstlibevent仍然会提示找不到共享库xxx.so,要永久性添加就要将该 LD_LIBRARY_PATH 的 export 语句写到系统文件中,结尾会提及到。
vim makefile
LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
firstlibevent:first_libevent.cpp
g++ $^ -o $@ -levent
./$@
clean:
rm -rf firstlibevent
rm -rf *.o
(caffe2_env) zhoujianwen@zhoujianwen-System:/media/zhoujianwen/Data/libevent/src/first_libevent$ make clean
rm -rf firstlibevent
rm -rf *.o
(caffe2_env) zhoujianwen@zhoujianwen-System:/media/zhoujianwen/Data/libevent/src/first_libevent$ make
g++ first_libevent.cpp -o firstlibevent -levent
./firstlibevent
test libevent!
event_base_new success!
linux中每次装完一个新的库,需要进行ldconfig命令
sudo ldconfig
https://www.pianshen.com/article/8867176161/
https://blog.csdn.net/qq_33343767/article/details/90378535
为什么修改LD_LIBRARY_PATH呢
因为运行时动态库的搜索路径的先后顺序是:
1.编译目标代码时指定的动态库搜索路径;
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4.默认的动态库搜索路径/lib和/usr/lib;
这个顺序是compile gcc时写在程序内的,通常软件源代码自带的动态库不会太多,而我们的/lib和/usr/lib只有root权限才可以修改,而且配置文件/etc/ld.so.conf也是root的事情,我们只好对LD_LIBRARY_PATH进行操作啦。
永久性添加
每次我使用该软件都需要临时修改库文件,因为上面的方法是临时设置环境变量 LD_LIBRARY_PATH ,重启或打开新的 Shell 之后,一切设置将不复存在。
为了让这种方法更完美一些,可以将该 LD_LIBRARY_PATH 的 export 语句写到系统文件中,例如 /etc/profile、/etc/export、~/.bashrc 或者 ~/.bash_profile 等等,取决于你正在使用的操作系统咯。
修改完系统文件记得source ~/.bashrc才会生效。
基于event监控客户端接收连接
event注册服务端接收连接事件
(1) 创建event_base上下文
(2) 创建socket绑定端口
(3) 注册socket监听事件的回调函数
(4) 接收客户端连接
#include <iostream>
#include <event2/event.h>
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#else
#include <signal.h>
#endif
using namespace std;
void ListenCB(evutil_socket_t sock, short what, void *arg)
{
cout << "ListenCB" << endl;
if (!(what & EV_READ))
{
cout << "not read" << endl;
return;
}
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
socklen_t size = sizeof(sin);
evutil_socket_t client_socket = accept(sock, (sockaddr*)&sin, &size);
if (client_socket <= 0)
{
cerr << "accept error" << endl;
}
char ip[16] = { 0 };
evutil_inet_ntop(AF_INET, &sin.sin_addr, ip, sizeof(ip));
cout << "client ip is:" << ip << endl;
}
int main(int argc,char *argv[])
{
int server_port = 8080;
if (argc > 1)
{
server_port = atoi(argv[1]);
}
#ifdef _WIN32
//初始化socket库
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#else
//使用断开连接socket,会发出此信号,造成程序退出
if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
return 1;
#endif
std::cout << "test event server!\n";
//1 创建libevent上下文,默认是创建base锁
event_base* base = event_base_new();
if (base)
{
cout << "event_base_new success!" << endl;
}
//2 创建socket 绑定端口
evutil_socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
/*C4996 'strerror': This function or variable may be unsafe.
Consider using strerror_s instead.To disable deprecation, use _CRT_SECURE_NO_WARNINGS.
See online help for details.test_event_server*/
cout << "socket error" << strerror(errno) << endl;
return -1;
}
//设置地址复用和非阻塞
evutil_make_socket_nonblocking(sock);
evutil_make_listen_socket_reuseable(sock);
//绑定端口
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(server_port);
int re = ::bind(sock, (sockaddr*)&sin, sizeof(sin));
if (re != 0)
{
cerr << "bind port:" << server_port << "failed!" << strerror(errno) << endl;
return -1;
}
listen(sock, 10);
cout << "bind port:" << server_port << " success!" << endl;
//3 注册socket的监听事件回调函数 EV_PERSIST持久化,不然只进入一次事件
//EV_ET(边缘触发)默认水平触发(只要有数据没有处理(高电平),就一直触发)
// event_self_cbarg() 传递当前创建的event对象
event *ev = event_new(base, sock,EV_READ| EV_PERSIST, ListenCB,event_self_cbarg());
//开始监听事件 第二个参数,超时时间
event_add(ev, 0);
//事件主循环,监控事件是否发送,分发事件到回调函数
//如果没有事件注册则退出
event_base_dispatch(base);
evutil_closesocket(sock);
event_del(ev);
event_free(ev);
event_base_free(base);
return 0;
}
搭建服务器接收用户连接——evconnlistener_new_bind
使用bufferevent连接服务器
基于bufferevent数据通信
evconnlistener绑定端口监听连接和bufferevent服务端事件策略
#include <iostream>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#else
#include <signal.h>
#endif
using namespace std;
//读取数据事件回调函数
void ReadCB(struct bufferevent* bev, void* ctx)
{
cout << "+" << flush;
char buf[1024] = { 0 };
int len = bufferevent_read(bev, buf, sizeof(buf));
cout << buf << endl;
//插入buffer链表
bufferevent_write(bev, "\nOK\n", 7);
}
//写数据事件回调函数
void WriteCB(struct bufferevent* bev, void* ctx)
{
cout << "[W]" << endl;
}
// 异常事件回调函数
void Event_CB(struct bufferevent* bev, short what, void* ctx)
{
cout << "[E]" << endl;
//读超时
if (what &BEV_EVENT_TIMEOUT && what &BEV_EVENT_READING)
{
cout << "BEV_EVENT_TIMEOUT BEV_EVENT_READING" << endl;
//读取缓冲区内容
//清理空间,关闭监听
bufferevent_free(bev);
}
//写超时
else if (what & BEV_EVENT_TIMEOUT && what & BEV_EVENT_WRITING)
{
cout << "BEV_EVENT_TIMEOUT BEV_EVENT_WRITING" << endl;
//缓冲回滚,根据自己业务逻辑回滚回去再清理缓存区数据,否则直接清理空间会让S端无法知道缓存区的数据是否发出了
//清理空间,关闭监听
bufferevent_free(bev);
}
//异常错误
else if (what & BEV_EVENT_ERROR)
{
cout << "BEV_EVENT_ERROR" << endl;
//清理空间,关闭监听
bufferevent_free(bev);
}
//连接断开
else if(what & BEV_EVENT_EOF)
{
cout << "BEV_EVENT_ERROR" << endl;
//考虑缓冲的处理
//清理空间,关闭监听
bufferevent_free(bev);
}
}
void ListenCB(struct evconnlistener *evc, evutil_socket_t client_socket, struct sockaddr *client_addr, int socklen, void *arg)
{
char ip[16] = {0};
sockaddr_in *addr = (sockaddr_in*)client_addr;
evutil_inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
cout << "client ip is " << ip << endl;
event_base* base = (event_base*)arg;
//创建bufferevent 上下文
//BEV_OPT_CLOSE_ON_FREE 关闭bev时关闭socket,创建了event对象(read和write)
bufferevent* bev = bufferevent_socket_new(base, client_socket, BEV_OPT_CLOSE_ON_FREE);
if (!bev)
{
cerr << "bufferevent_socket_new failed!"<<endl;
}
//添加监控事件 设置内部权限参数
bufferevent_enable(bev, EV_READ | EV_WRITE);
//超时设定:秒,微秒(1/1000000秒), 解决C端连接长期占用S端资源的问题,比如C端死机。
//读、写超时时间应该根据自己业务情况修改参数
timeval t1 = { 10,0 };
bufferevent_set_timeouts(bev, &t1, 0);
//设置回调函数
bufferevent_setcb(bev, ReadCB, WriteCB, Event_CB, base);
}
int main(int argc,char *argv[])
{
int server_port = 8080;
if (argc > 1)
{
server_port = atoi(argv[1]);
}
#ifdef _WIN32
//初始化socket库
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#else
//使用断开连接socket,会发出此信号,造成程序退出
if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
return 1;
#endif
std::cout << "test event server!\n";
//1 创建libevent上下文,默认是创建base锁
event_base* base = event_base_new();
if (base)
{
cout << "event_base_new success!" << endl;
}
//绑定端口
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(server_port);
auto evc = evconnlistener_new_bind(base, ListenCB, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, 10, (sockaddr*)&sin, sizeof(sin));
//事件主循环,监控事件是否发送,分发事件到回调函数
//如果没有事件注册则退出
event_base_dispatch(base);
evconnlistener_free(evc);
event_base_free(base);
return 0;
}
服务器端几种模型:
1、阻塞式模型(blocking IO)
2、多线程的服务器模型(Multi-Thread)
3、非阻塞式模型(Non-blocking IO)
4、多路复用IO
5、使用事件驱动库libevent的服务器模型
6、信号驱动IO模型(Signal-driven IO)
7、异步IO模型(asynchronous IO)
几种服务器端IO模型的简单介绍及实现 http://www.cnblogs.com/luxiaoxun/p/3691800.html
Visual Studio 2019修改文件编码UTF-8
解决windows与linux共享文件内容乱码的问题
https://blog.csdn.net/kuangben2000/article/details/100579686
ubuntu16.04访问Windows的共享文件夹
ubuntu 18.10是能够正常访问Windwos的共享文件夹,只有ubuntu 16.04需要设置才能正常访问。
https://blog.csdn.net/wyq_841943/article/details/71055573
2019

被折叠的 条评论
为什么被折叠?



