套接字基础
套接字
一、socket
int socket(int domain, int type, int protocol);
-
参数一
domain
: -
参数二
type
: -
参数三
protocol
:使用示例:
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
二、setsockopt
使用示例:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
三、bind
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr_in
结构体
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* Port number */
struct in_addr sin_addr; /* IPv4 address */
};
这个结构体也是挺麻烦的,在使用之前我们需要进行初始化,因为我们一般的数据格式是无法在网络中传输了,需要转为特定格式才行,转格式就需要用到下面几个函数,头文件 #include <arpa/inet.h>
这个结构体在使用之前一般还需要清空
既然有从本地数据转为网络,自然也有从网络转本地的
使用示例:
struct sockaddr_in serverAddr;
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(6666);
Inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
Bind(listenfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
四、listen
使用示例:
listen(listenfd, 128);
五、select
#include <sys/select.h>
int select(int nfds, fd_set *_Nullable restrict readfds,
fd_set *_Nullable restrict writefds,
fd_set *_Nullable restrict exceptfds,
struct timeval *_Nullable restrict timeout);
//select 函数的返回值表示就绪的文件描述符数量
参数一 nfds
:
参数二 ,三,四 readfds
,writefds
,exceptfds
:
参数三:
使用示例:
count = select(maxfd + 1, &rset, nullptr, nullptr, nullptr);
由于select函数设计的比较好,所有使用是来就略显的复杂,由于select函数只返回符合条件的文件描述符的总数,我们并不知道具体是哪个文件描述符符合,所有我们需要不断的遍历1024次(1024是select所支持的最大文件描述符数量),这样效率看起来就很低,在遍历的过程中我们还需要配合几个函数使用,当然,select也可以通过改变实现方式提高效率,如添加一个数组,等下可以看实现,现在先讲配合select使用的四个函数
void FD_CLR(int fd, fd_set *set); //从集合中删除某文件描述符,即断开连接时使用
int FD_ISSET(int fd, fd_set *set);//查询某文件描述符是否在集合中
void FD_SET(int fd, fd_set *set);//将某文件描述符添加到集合中
void FD_ZERO(fd_set *set);//将集合清空,全部置0
关于为什么要每次都循环1024次,主要还是怕没检查到位而遗漏某请求,因为每有请求时都需要循环1024次,这样看起来效率就很低,关于为什么select还没有被淘汰,那就是它跨平台,耐打。关于使用数组优化,那就是在每添加一个请求时,将这个请求放入这个数组中,我们在某程度上就能减少循环次数,我们只需要循环 我们给定的数组就行,而不需要每次都遍历全部的连接文件描述符,借鉴于poll函数
/*select实现多路IO转接 */
#include <algorithm>
#include <iostream>
#include "wrap.h"
#define IPSERVER "127.0.0.1"
#define PORT 6666
int main() {
/* Create a client[] array to make up for the problem of selecting looping 1024 times each time */
int clientfd, client[FD_SETSIZE], listenfd, maxi, maxfd;
char buf[BUFSIZ], str[INET_ADDRSTRLEN];
struct sockaddr_in serverAddr, clientAddr;
socklen_t clientAddrLen;
fd_set rset, allset;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
Inet_pton(AF_INET, IPSERVER, &serverAddr.sin_addr);
serverAddr.sin_port = htons(PORT);
Bind(listenfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
Listen(listenfd, 128);
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
maxfd = listenfd;
maxi = -1;
for (int i = 0; i < FD_SETSIZE; i++) {
client[i] = -1;
}
int count;
clientAddrLen = sizeof(clientAddr);
while (true) {
rset = allset;
count = select(maxfd + 1, &rset, nullptr, nullptr, nullptr);
if (FD_ISSET(listenfd, &rset)) {
int i;
clientfd = Accept(listenfd, (struct sockaddr*)&clientAddr, &clientAddrLen);
for (i = 0; i < FD_SETSIZE; i++) {
if (client[i] < 0) {
client[i] = clientfd;
break;
}
}
if (maxi == FD_SETSIZE) {
perr_exit("文件描述符已满");
continue;
}
maxi = std::max(i, maxi);
maxfd = std::max(clientfd, maxfd);
FD_SET(clientfd, &allset);
if (count == 1)
continue;
}
for (int i = 0; i <= maxi; i++) {
if (FD_ISSET(client[i], &rset)) {
int n = Read(client[i], buf, BUFSIZ);
if (n == 0) {
Close(client[i]);
FD_CLR(client[i], &allset);
client[i] = -1;
}
Write(STDOUT_FILENO, buf, n);
for (int j = 0; j < n; ++j) {
buf[j] = toupper(buf[j]);
}
Write(client[i], buf, n);
}
}
}
return 0;
}
六、poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//返回值大于0,表示符合条件的总文件描述符数,等于0表示在指定的超时期间内没有任何事件发生,-1表示发生错误,错误原因存储在全局变量 errno 中。
参数一 fds
:
参数二 nfds
:
参数三 timeout
:
示例:
struct pollfd client[FD_SETSIZE];
int nready = poll(client, maxi + 1, -1);
#include <poll.h>
#include <algorithm>
#include <iostream>
#include "wrap.h"
#define IPADDR "127.0.0.1"
#define PORT 6666
int main() {
int clientfd, listenfd, maxi, n;
char buf[BUFSIZ], str[INET_ADDRSTRLEN];
struct sockaddr_in serverAddr, clientAddr;
socklen_t clientAddrLen;
struct pollfd client[FD_SETSIZE];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
Inet_pton(AF_INET, IPADDR, &serverAddr.sin_addr);
serverAddr.sin_port = htons(PORT);
Bind(listenfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
Listen(listenfd, 128);
client[0].fd = listenfd;
client[0].events = POLLIN;
for (int i = 1; i < FD_SETSIZE; i++) {
client[i].fd = -1;
}
while (1) {
int nready = poll(client, maxi + 1, -1);
if (nready < 0) {
perror("poll error");
exit(1);
}
if (client[0].revents & POLLIN) { // New connection
int i;
clientAddrLen = sizeof(clientAddr);
clientfd = accept(listenfd, (struct sockaddr*)&clientAddr, &clientAddrLen);
for (i = 1; i < FD_SETSIZE; i++) {
if (client[i].fd < 0) {
client[i].fd = clientfd;
break;
}
}
if (i == FD_SETSIZE) {
printf("Too many clients\n");
close(clientfd);
continue;
}
client[i].events = POLLIN;
if (i > maxi)
maxi = i;
if (--nready <= 0)
continue;
}
for (int i = 1; i <= maxi; i++) { // Check all clients for data
if (client[i].fd < 0)
continue;
if (client[i].revents & (POLLIN | POLLERR)) {
n = read(client[i].fd, buf, BUFSIZ);
if (n < 0) {
perror("read error");
close(client[i].fd);
client[i].fd = -1;
} else if (n == 0) {
printf("Client closed connection\n");
close(client[i].fd);
client[i].fd = -1;
} else {
write(client[i].fd, buf, n); // Echo back
write(STDOUT_FILENO, buf, n); // Echo back
}
}
}
}
return 0;
}
七、epoll
- epoll_create函数:
- epoll_ctl函数
参数一 epfd
:
参数二 op:
参数三 fd
:
参数四 event:
- epoll_wait函数
参数一 apfd
:
参数二 events
:
参数三 maxevents
:
参数四 timeout
:
//示例:
struct epoll_event temp, client[FD_SETSIZE];
ret = epoll_wait(epfd, client, 128, -1);
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include "wrap.h"
int main() {
int listenfd, clientfd;
socklen_t clientAddrLen;
struct sockaddr_in serverAddr, clientAddr;
char buf[BUFSIZ];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(6666);
Inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
Bind(listenfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
Listen(listenfd, 128);
/* 创建红黑树,得到句柄 */
int epfd = epoll_create(128);
/* temp用于设置单个fd的属性 */
struct epoll_event temp, client[FD_SETSIZE];
temp.events = EPOLLIN;
temp.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &temp);
int ret, n;
clientAddrLen = sizeof(clientAddr);
while (true) {
ret = epoll_wait(epfd, client, 128, -1);
for (int i = 0; i < ret; ++i) {
if (listenfd == client[i].data.fd) {
clientfd = Accept(listenfd, (struct sockaddr*)&clientAddr, &clientAddrLen);
client[i].data.fd = clientfd;
temp.events = EPOLLIN;
temp.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &temp);
} else {
n = Read(client[i].data.fd, buf, sizeof(buf));
if (n == 0) {
Close(client[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, client[i].data.fd, &temp);
} else {
Write(STDOUT_FILENO, buf, n);
}
}
}
}
return 0;
}
一、水平模式(Level-Triggered,LT)
二、边沿模式(Edge-Triggered,ET)
从**水平模式转为边沿模式,**只需要或上EPOLLET
#include <sys/epoll.h>
int epfd = epoll_create1(0);
if (epfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
struct epoll_event event;
int fd = ...; // 这里 fd 是你需要监控的文件描述符
event.events = EPOLLIN | EPOLLET; // 设置为边缘触发模式
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
边沿模式需要配合 无阻塞和忙轮询
,我们的epoll反应堆就是基于边沿模式,无阻塞,忙轮询,回调实现的
// epoll ET
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include "wrap.h"
int main() {
int listenfd, clientfd;
socklen_t clientAddrLen;
struct sockaddr_in serverAddr, clientAddr;
char buf[BUFSIZ];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(6666);
Inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
Bind(listenfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
Listen(listenfd, 128);
int epfd = epoll_create(128);
struct epoll_event temp, client[FD_SETSIZE];
temp.events = EPOLLIN | EPOLLET; // 设置ET模式_ET
// temp.events = EPOLLIN;
temp.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &temp);
int ret, n;
clientAddrLen = sizeof(clientAddr);
while (true) {
ret = epoll_wait(epfd, client, 128, -1);
for (int i = 0; i < ret; ++i) {
if (listenfd == client[i].data.fd) {
clientfd = Accept(listenfd, (struct sockaddr*)&clientAddr, &clientAddrLen);
// 设置非阻塞
int flags = fcntl(clientfd, F_GETFL, -1);
flags |= O_NONBLOCK;
fcntl(clientfd, F_SETFL, flags);
client[i].data.fd = clientfd;
temp.events = EPOLLIN;
temp.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &temp);
} else {
while (true) {
n = read(client[i].data.fd, buf, sizeof(buf));
if (n > 0) {
write(STDOUT_FILENO, buf, n);
} else if (n == -1 && errno == EAGAIN) {
break; // 没有更多数据
} else if (n == 0) {
// 客户端关闭了连接
close(client[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, client[i].data.fd, NULL);
break;
} else {
// 处理其他错误
perror("Read error");
close(client[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, client[i].data.fd, NULL);
break;
}
}
}
}
}
return 0;
}