使用epoll建立socket连接

1.用到的一些函数:​
#include <sys/socket.h>
// 返回:成功则返回非负描述符,出错返回-1
int socket (int family, int type, int protocol);

family 指协议族,有 AF_INET(IPV4协议)、AF_INET6(IPV6协议)、AF_LOCAL(Unix域协议)、AF_ROUTE(路由套接字)、AF_KEY(密钥套接字)
type套接字的类型,有 SOCK_STREAM(字节流套接字)、SOCK_DGRAM(数据报套接字)、SOCK_SEQPACKET(有序分组套接字)、SOCK_RAM(原始套接字)
protocol 协议类型,有 IPPROTO_TCP(TCP传输协议)、IPPROTO_UDP (UDP传输协议)、IPPROTO_SCTP(SCTP传输协议)


#include <sys/socket.h>
// 返回:成功返回0 出错返回-1
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

sockfd socket 函数返回的套接字描述符
servaddr 指向套接字地址结构的指针
addrlen 地址结构大小 ​
connect 函数仅在建立成功或出错才返回,出错返回可能有以下几种情况:1.客户端没有收到syn的响应,2.对客户端的响应是RST,(服务器没有在指定端口等待连接、TCP想取消一个连接、接收到一个根本不存在到连接上到分节,3.ICMP错误,地址不可达。失败后套接字必须关闭。


#include <sys/socket.h>
// 返回:若成功则为0,若出错则-1
int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen);

sockfd socket 函数返回的套接字描述符
myaddr 指向特定协议的地址结构的指针,INADDR_ANY通配地址,对于服务器,接收目的地是myaddr地址的客户端
addrlen 该地址结构的长度


#include <sys/socket.h>
// 返回:若成功则是0,出错为-1
int listen(int sockfd, int backlog);

内核中为仍和一个监听的套接字维护两个队列,1.未完成连接队列(服务器正在等待三次握手完成)2.已完成连接队列(每个已完成TCP三路握手过程中的客户)两个队列之和不能超过backlog。等待accept从已完成队列提取队列头。


#include <sys/socket.h>
// 返回:成功返回非负描述符,出错返回-1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

sockfd为监听套接字,曾为socket函数和bind函数的参数,生命周期直到服务器结束。
cliaddr 和 addrlen用来返回已连接的对端进程的协议地址。调用前,addrlen是cliaddr所指的套接字的地址结构大小,返回后,为内核存放在该套接字地址结构内实际的字节数。
如果accept成功,返回值是内核自动生成的一个全新的描述符,代表返回的TCP连接。


#include <unistd.h>
// 返回:若成功则返回0,若出错则返回-1
int close(int sockfd);

将sockfd标记为已关闭,不能再被调用进程使用。TCP将队列等待发送的数据尝试发送出去,最后是TCP终止序列。


#include <sys/socket.h>
// 返回:若成功返回0,出错返回-1
int shutdown(int sockfd, int howto);		

close把描述符的引用计数减1,仅在计数变为0时才关闭套接字,使用shutdown可以不管引用计数就让TCP发送终止序列FIN。
howto SHUT_RD 关闭连接的读,套接字不再有数据可接收,丢弃接收缓冲区的的数据。 SHUT_WR 关闭连接的写,当前留在套接字缓冲区中的数据被发送掉,然后发送FIN终止序列。SHUT_RDWR 读和写都关闭。


#include <sys/socket.h>
// 返回:成功返回0,出错返回-1
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

sockfd 是打开的套接字描述符
level 指定系统中解释选项的代码或通用套接字代码(SOL_SOCKET、IPPROTO_IP、IPPROTO_ICMPV6等)
optname指level对应选项名(如SOL_SOCKET有SO_REVBUF、SO_SNDBUF)
optval指向变量的指针,设置选项的值,optlen 是optval指针指向的指的大小。



2.TCPServer简单实现,epoll监听连接:

关于宏 TEMP_FAILURE_RETRY (expression)
正在read或write时,突然发生信号处理,中断io,信号处理返回后,read、write这些io操作也返回-1,错误码为EINTR。一般我们要再对这些库函数(read、write)再次调用,但是可能会忘记忽略。这个宏起到了重新调用库函数的作用。

#include <string>
#include <iostream>
#include <memory>
#include <csignal>
#include <functional>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <string.h>
#include <fcntl.h>

static int setNonBlocking(int fd)
{
	int flags, s;
	flags = fcntl(fd, F_GETFL, 0);
	if (-1 == flags) 
		return -1;
	flags |= O_NONBLOCK;
	s = fcntl(fd, F_SETFL, flags);
	if (-1 == s) 
		return -1;
	return 0;
}

class TCPServer
{
	public:
		TCPServer(const std::string& name):serverName(name)
		{
			// 创建epoll
			epfd = epoll_create(1);
		}
		~TCPServer()
		{
			TEMP_FAILURE_RETRY(::close(epfd));
			if(sockfd != -1)
			{
				::shutdown(sockfd, SHUT_RD);
				TEMP_FAILURE_RETRY(::close(sockfd));
				sockfd = -1;
			}
		}
		TCPServer(const TCPServer&) = delete;
		TCPServer& operator = (const TCPServer&) = delete;
		
		bool bind(const unsigned short port)
		{
			sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		       	if(-1 == sockfd)
			{
				std::cout << "SOCKET FAIL" << std::endl;
				return false;	
			}
			socklen_t size = 64 * 1024;
			::setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
			::setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
			int reuse = 1;
			::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

			struct sockaddr_in addr;
			bzero(&addr, sizeof(addr));
			addr.sin_family = AF_INET;
			addr.sin_addr.s_addr = htonl(INADDR_ANY);
			addr.sin_port = htons(port);
			
			int ret = ::bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
			if(-1 == ret)
			{
				std::cout << "BIND FAIL" << std::endl;
				shutdown();
				return false;
			}
			
			ret = ::listen(sockfd, 5);

			struct epoll_event ev;
			ev.events = EPOLLIN;
			ev.data.ptr = nullptr;
			epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
			std::cout << "BIND SUCCESS" << std::endl;
			return true;
		}
		int accept(struct sockaddr_in* addr)
		{
			socklen_t len = sizeof(struct sockaddr_in);
			bzero(addr, sizeof(struct sockaddr_in));
			struct epoll_event ev;
			int rc = epoll_wait(epfd, &ev, 1, 500);
			if(1 == rc && (ev.events & EPOLLIN))
				return TEMP_FAILURE_RETRY(::accept(sockfd, (struct sockaddr*)addr, &len));
			return -1;
		}
		void shutdown()
		{
			TEMP_FAILURE_RETRY(::close(epfd));
			if(-1 != sockfd)
			{
				::shutdown(sockfd, SHUT_RD);
				TEMP_FAILURE_RETRY(::close(sockfd));
			}
		}
	private:
		std::string serverName;
		int sockfd = -1;
		int epfd = -1;
};

namespace
{
	std::function<void(int)> shutdown_handler;
	void signal_handler(int signal) { shutdown_handler(signal); }
}

int main(int argc, char** argv)
{
	bool exitFlag = false;
	std::unique_ptr<TCPServer> server = std::make_unique<TCPServer>("小白服务器");
	if(!server)
		return 0;
	server->bind(8500);
	// daemon(1,1);

	shutdown_handler = [&exitFlag](int sig){ exitFlag = true; std::cout << "结束" << std::endl;};
	std::signal(SIGINT, signal_handler);

	char sendBuf[200];
	bzero(sendBuf,sizeof(sendBuf));
	strncpy(sendBuf, "Hi Client", sizeof(sendBuf) - 1);
	
	while(!exitFlag)
	{
		struct sockaddr_in addr;
		int ret = server->accept(&addr);
		if(ret >= 0)
		{
			/*
			 * newConn
			 */
			setNonBlocking(ret);
			TEMP_FAILURE_RETRY(::send(ret, sendBuf, sizeof(sendBuf), MSG_NOSIGNAL));
			TEMP_FAILURE_RETRY(::send(ret, sendBuf, sizeof(sendBuf), MSG_NOSIGNAL));
			TEMP_FAILURE_RETRY(::send(ret, sendBuf, sizeof(sendBuf), MSG_NOSIGNAL));
			TEMP_FAILURE_RETRY(::send(ret, sendBuf, sizeof(sendBuf), MSG_NOSIGNAL));
			std::cout << "收到客户端"<< std::endl;
		}
	}
	server->shutdown();
	
	return 0;
}

3.TCPClient简单实现,用两个epoll监听读事件,测试时Server发送四次消息,两个监听epoll各监听到两次读事件:
#include <string>
#include <iostream>
#include <memory>
#include <csignal>
#include <functional>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <strings.h>
#include <string.h>

class TCPClient
{
	public:
		
		TCPClient(const std::string& name) : clientName(name)
		{
			epfd_1 = epoll_create(1);
			epfd_2 = epoll_create(1);
		}
		bool connect(const std::string& ip, const unsigned short port)
		{
			sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if(-1 == sockfd)
				return false;
			socklen_t size = 64 * 1024;
			::setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
			::setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
			struct sockaddr_in addr;
			bzero(&addr, sizeof(addr));
			addr.sin_family = AF_INET;
			addr.sin_addr.s_addr = inet_addr(ip.c_str());
			addr.sin_port = htons(port);

			int ret = TEMP_FAILURE_RETRY(::connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)));
			if(ret == -1)
			{
				std::cout << "CONNECT FAIL" << std::endl;
				shutdown();
				return false;
			}
			
			struct epoll_event ev;
			ev.events = EPOLLIN;
			ev.data.ptr = nullptr;
			epoll_ctl(epfd_1, EPOLL_CTL_ADD, sockfd, &ev);

			struct epoll_event ev2;
			ev2.events = EPOLLIN;
			ev2.data.ptr = nullptr;
			epoll_ctl(epfd_2, EPOLL_CTL_ADD, sockfd, &ev2);

			std::cout << "CONNECT SUCCESS" << std::endl;
			return true;
		}
		void run()
		{
			struct epoll_event ev;
			struct epoll_event ev2;
			while(true)
			{
				if(TEMP_FAILURE_RETRY(::epoll_wait(epfd_1, &ev, 1, 0)) > 0)
				{
					if(ev.events & EPOLLIN)
					{
						char buf[200];
						bzero(buf, sizeof(buf));
						TEMP_FAILURE_RETRY(::recv(sockfd, buf, sizeof(buf), MSG_NOSIGNAL));
						std::cout << "ep1" << std::endl;
						printf("%s\n", buf);
					}
				}
				if(TEMP_FAILURE_RETRY(::epoll_wait(epfd_2, &ev2, 1, 0)) > 0)
				{
					if(ev2.events & EPOLLIN)
					{
						char buf[200];
						bzero(buf, sizeof(buf));
						TEMP_FAILURE_RETRY(::recv(sockfd, buf, sizeof(buf), MSG_NOSIGNAL));
						std::cout << "ep2" << std::endl;
						printf("%s\n", buf);
					}
				}
			}
		}
		void shutdown()
		{
			TEMP_FAILURE_RETRY(::close(epfd_1));
			TEMP_FAILURE_RETRY(::close(epfd_2));
			epfd_1 = -1;
			epfd_2 = -1;
			if(-1 != sockfd)
			{
				::shutdown(sockfd, SHUT_RDWR);
				TEMP_FAILURE_RETRY(::close(sockfd));
				sockfd = -1;
			}
		}
	private:
		std::string clientName;
		int sockfd = -1;
		int epfd_1 = -1;
		int epfd_2 = -1;
		
};

int main(int agrc, char** argv)
{
	std::unique_ptr<TCPClient> client = std::make_unique<TCPClient>("С°×¿Í»§¶Ë");
	if(!client)
		return 0;
	if(!client->connect("127.0.0.1", 8500))
	{
		std::cout << "Á¬½Óʧ°Ü" << std::endl;
		std::cout << strerror(errno) << std::endl;
		return 0;
	}
	client->run();
	client->shutdown();
	
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值