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;
}