我是靠谱客的博主 糊涂烧鹅,最近开发中收集的这篇文章主要介绍基于tcp协议的socket网络编程总结什么是套接字套接字:描述符socketconnectsendrecvclosebinlistenacceptLinuxWindowslibevent和IO模型的学习总结同步异步和阻塞IO模型Reactor模式Windows平台编译libeventLinux平台编译libeventlinux中每次装完一个新的库,需要进行ldconfig命令为什么修改LD_LIBRARY_PATH呢永久性添加基于event监控客户端接收连接evconnlistener绑定端口,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

什么是套接字

套接字是一个主机本地应用程序所创建的,为操作系统所控制的接口。
应用进程通过这个接口,使用传输层提供的服务,跨网络发送/接收消息到其他应用进程。
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] = '';
                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] = '';
		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

  1. 环境准备
    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源码,

  2. 编译zlib
    在这里插入图片描述

解压zlib-1.2.11.tar.gz到当前目录

打开x64_x86 Cross Tools Command Prompt for VS 2019

手动编译执行

nmake /f WIN32Makefile.msc

在这里插入图片描述
批处理脚本编译,build_zlib_vs2019_32.bat

set VS="C:Program Files (x86)Microsoft Visual Studio2019EnterpriseVCAuxiliaryBuildvcvarsamd64_x86.bat"
set OUT = "D:libeventoutvs2019_32zlib"
call %VS%
cd zlib-1.2.11
nmake /f win32Makefile.msc clean
nmake /f win32Makefile.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 Studio2019EnterpriseVCAuxiliaryBuildvcvarsamd64_x86.bat"
set OUT = "D:libeventoutvs2019_32zlib"
call %VS%
cd zlib-1.2.11
nmake /f win32Makefile.msc clean
nmake /f win32Makefile.msc
md D:libeventoutvs2019_32zliblib
md D:libeventoutvs2019_32zlibbin
md D:libeventoutvs2019_32zlibinclude
copy /Y *.lib D:libeventoutvs2019_32zliblib
copy /Y *.h D:libeventoutvs2019_32zlibinclude
copy /Y *.dll D:libeventoutvs2019_32zlibbin
copy /Y *.exe D:libeventoutvs2019_32zlibbin
pause

以管理员身份运行
在这里插入图片描述
在这里插入图片描述

  1. 编译openssl

安装 nasm 汇编器,设置PATH环境变量,添加D:libeventnasm-2.13.03-win64nasm-2.13.03
安装perl,默认安装即可
手动编译openssl
打开x64_x86 Cross Tools Command Prompt for VS 2019

set OUTPATH = "D:libeventoutvs2019_32openssl"
perl Configure {VC-WIN32|VC-WIN64|VC-CE} --prefix=%OUTPATH%

批处理脚本编译

@echo "start compile openssl"
set VS = "C:Program Files (x86)Microsoft Visual Studio2019EnterpriseVCAuxiliaryBuildvcvarsamd64_x86.bat"
set OUT = "D:libeventoutvs2019_32openssl"
call %VS%
D:
cd D:libeventopenssl-1.1.1g
perl Configure VC-WIN32 --prefix=D:libeventoutvs2019_32openssl
nmake clean
nmake
nmake install
@echo "compile end"
pause

perl Configure VC-WIN32 --prefix=%OUT%修改成这样无法正确执行,可能是权限问题,所以建议还是加上绝对路径。

  1. 编译libevent
    手动编译
nmake /f Makefile.nmake OPENSSL_DIR=D:libeventoutvs2019_32openssl

编译到最后会提示错误
LINK : fatal error LNK1181: 无法打开输入文件 opensslliblibeay32.lib

cd D:libeventlibevent-mastertest
修改Makefile.nmake
将SSL_LIBS的libeay32.lib和ssleay32.lib这两个文件替换成D:libeventoutvs2019_32openssllib目录下的libcrypto.lib和libssl.lib

SSL_LIBS=..libevent_openssl.lib $(OPENSSL_DIR)liblibeay32.lib $(OPENSSL_DIR)libssleay32.lib gdi32.lib User32.lib

修改为

SSL_LIBS=..libevent_openssl.lib $(OPENSSL_DIR)liblibcrypto.lib $(OPENSSL_DIR)liblibssl.lib gdi32.lib User32.lib

保存再重新手动执行

nmake /f Makefile.nmake OPENSSL_DIR=D:libeventoutvs2019_32openssl clean
nmake /f Makefile.nmake OPENSSL_DIR=D:libeventoutvs2019_32openssl

直到出现这样的画面就可以了。在这里插入图片描述
然后检测一下是否安装成功,打开目录D:libeventlibevent-mastertest
运行regress.exe,若仍然提示libcrypto-1_1.dll和libssl-1_1.dll缺失,可以将D:libeventoutvs2019_32opensslbin目录下的libcrypto-1_1.dll和libssl-1_1.dll拷贝到D:libeventlibevent-mastertest目录下,再重新执行regress.exe > …/…/out.txt

发现zlib没有测试通过,如何解决?
在这里插入图片描述
把之前编译好的zlib整个目录拷贝到D:libeventlibevent-master目录下,并修改D:libeventlibevent-mastertestMakefile.nmakeMakefile.nmake的CFLAGS,添加/I../zlib/include

在这里插入图片描述
添加..zliblibzdll.lib
在这里插入图片描述

添加regress_zlib.obj
在这里插入图片描述
修改D:libeventlibevent-masterWIN32-Codenmakeevent2event-config.h
在这里插入图片描述

先清除

nmake /f Makefile.nmake OPENSSL_DIR=D:libeventoutvs2019_32openssl clean

再重新编译

cd D:libeventlibevent-master
nmake /f Makefile.nmake 

最后编译通过
在这里插入图片描述
测试一下zlib例子

cd D:libeventlibevent-mastertest
regress.exe

在这里插入图片描述
把zlib1.dll文件拷贝到D:libeventlibevent-mastertest目录下
在这里插入图片描述

D:libeventlibevent-mastertest>regress.exe > ../../out.txt

bufferevent_zlib测试成功了
在这里插入图片描述
build_libevent_vs2019_32.bat

@echo "start compile libevent"
set VS = "C:Program Files (x86)Microsoft Visual Studio2019EnterpriseVCAuxiliaryBuildvcvarsamd64_x86.bat"
set OUT = D:libeventoutvs2019_32libevent
call "C:Program Files (x86)Microsoft Visual Studio2019EnterpriseVCAuxiliaryBuildvcvarsamd64_x86.bat"
cd /d D:libeventlibevent-master
nmake /f Makefile.nmake clean
nmake /f Makefile.nmake OPENSSL_DIR=D:libeventoutvs2019_32openssl
md ..outvs2019_32libeventlib
md ..outvs2019_32libeventbin
md ..outvs2019_32libeventinclude
copy /Y *.lib ..outvs2019_32libeventlib
xcopy /S/Y include ..outvs2019_32libeventinclude
xcopy /S/Y WIN32-Codenmake ..outvs2019_32libeventinclude
copy /Y *.dll ..outvs2019_32libeventbin
copy /Y *.exe ..outvs2019_32libeventbin

@echo "compile end"
pause
  1. 编写第一个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, "nOKn", 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

最后

以上就是糊涂烧鹅为你收集整理的基于tcp协议的socket网络编程总结什么是套接字套接字:描述符socketconnectsendrecvclosebinlistenacceptLinuxWindowslibevent和IO模型的学习总结同步异步和阻塞IO模型Reactor模式Windows平台编译libeventLinux平台编译libeventlinux中每次装完一个新的库,需要进行ldconfig命令为什么修改LD_LIBRARY_PATH呢永久性添加基于event监控客户端接收连接evconnlistener绑定端口的全部内容,希望文章能够帮你解决基于tcp协议的socket网络编程总结什么是套接字套接字:描述符socketconnectsendrecvclosebinlistenacceptLinuxWindowslibevent和IO模型的学习总结同步异步和阻塞IO模型Reactor模式Windows平台编译libeventLinux平台编译libeventlinux中每次装完一个新的库,需要进行ldconfig命令为什么修改LD_LIBRARY_PATH呢永久性添加基于event监控客户端接收连接evconnlistener绑定端口所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部