c++网络编程

c++在windows和Linux上的网络开发流程实际上都差不多,用的API也一样

sock,bind,listen,connect,accept,aend,recv,select,gethostbyname,close

1.TCP网络通信的流程

服务端流程:

(1).创建套接字

(2).绑定IP和端口

(3).开启监听

(4).接受连接

(5).基于接受而新产生的套接字来接受和发送信息

(6).关闭套接字

客服端

(1).创建套接字

(2).绑定服务器

(3).发送信息,接受信息

(4).close

有个比较有趣的问题,服务器绑定的地址,如果只允许本地局域网连接,设置为127.0.0.1,如果允许外网连接,就设置为INADDR_ANY。客户端的端口和服务器的端口需要根据实际情况选择,1024以下的端口一般是预留给各种特殊程序的,所以最好不要占用。

服务器的端口一定要选择一个确定值,因为客户端要根据具体的地址连接到服务器。

至于客户端,它到底能不能绑定一个端口呢?

回答是可以,但是又不可以。

这是因为客户端也可以使用确定的端口地址来与服务器通信,如果你不去绑定,那么操作系统就随机给你分配一个。如果你绑定了,但是端口又是不合法的地址,那么操作系统又会给随机分配一个。就是这么一回事。

一个简单的通信例子

cpp 复制代码
/*服务端*/
#include<iostream>
#include<string.h>
#include<vector>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<vector>

using namespace std;

int main()
{

        //create socket
        size_t socket_int = socket(AF_INET, SOCK_STREAM, 0);
        if(socket_int < 0)
        {
                cout << "套接字创建失败" << endl;
                return -1;
        }

        //服务端的地址
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(3000);

        //绑定
        if(bind(socket_int, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) < 0)
        {
                cout << "bind error" << endl;
                return -1;
        }

        //监听
        if(listen(socket_int,50) < 0)
        {
                cerr << "监听失败" << endl;
                return -1;
        }

        while(true)
        {
                struct sockaddr_in clientaddr;
                socklen_t clientaddrlen = sizeof(clientaddr);

                //接受客户端连接
                int clientfd = accept(socket_int, (struct sockaddr*)&clientaddr, &clientaddrlen);
                if(clientfd < 0)
                        continue;
                char recvBuf[255];
                memset(recvBuf, '\0', 255);
                int ret = recv(clientfd, recvBuf, 255, 0);
                if(ret > 0)
                {
                        cout << "接收到信息:" << recvBuf << endl;
                        ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
                        ret = send(clientfd, recvBuf, strlen(recvBuf), 0);

                }
                else
                        cout << "recv error"<<endl;
                close(clientfd);

        }
        close(socket_int);
        return 0;
}
cpp 复制代码
#include<iostream>
#include<string.h>
#include<vector>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>


using namespace std;

int main()
{

        //create socket
        size_t socket_int = socket(AF_INET, SOCK_STREAM, 0);
        if(socket_int < 0)
        {
                cout << "套接字创建失败" << endl;
                return -1;
        }

        //服务端的地址
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        server_addr.sin_port = htons(3000);

        //绑定
        if(connect(socket_int, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) < 0)
        {
                cout << "bind error" << endl;
                return -1;
        }

        char sendMessage[] = "hello world";
        //发送信息
        int ret = send(socket_int, sendMessage, strlen(sendMessage), 0);
        if(ret != strlen(sendMessage))
        {
                cerr << "发送失败" << endl;
                return -1;
        }


        char recvBuf[255];
        memset(recvBuf, '\0', 255);
        ret = recv(socket_int, recvBuf, 255, 0);
        if(ret > 0)
        {
                cout << "接收到信息:" << recvBuf << endl;
        }
                else
                        cout << "recv error"<<endl;

        close(socket_int);
        return 0;
}

2.select函数

(1)select事件就绪

select函数是用来检测一组socket中是否有事件就绪,事件一般分为三类

(1).读事件

简单来讲就是内核缓冲区中有字符写入,或者描述符读取被关闭

(2).写事件

内核缓冲区有可以发送的字符,或者描述符的写端关闭

(3).异常事件

描述符出问题了

(2).函数描述

select(int nfds, fd_set* readfds. fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

(1).nfds:最大的事件检测值+1

(2).readfds:需要监听的写事件的fd集合

(3).writefds:需要监听的读事件的fd集合

(4).exceptfds:需要监听的异常事fd集合

timeout是一个结构体,它是一个时间设置,代表select在它规定的时间里面监听,超出这个时间就要返回。

struct timeval{

int tv_sec;//秒

int tv_usec;//微秒

};

关于这个函数的配套函数其实都是位操作。其中的核心fd_set是一个结构体

typedef struct{

long int __fds_bits[16];

}fd_set;

long int 为8个字节,因此它可以监听1024个信息

配套的函数都是宏定义实现的,我们先看看有哪些函数

(1).清空函数-把全部的位清空

void FD_ZERO(fd_set *set);

(2).删除一个位:把对应的fd从监听中取消

void FD_CLR(int fd, fd_set* set);

(3).添加一个位:添加一个fd到监听的队列中

void FD_SET(int fd, fd_set* set);

(4).判断某个fd是否有我们关注的事件

void FD_ISSET(int fd, fd_set* set);

下面我们介绍一下以下用法;

cpp 复制代码
#include<iostream>
#include<string.h>
#include<vector>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<vector>

using namespace std;

int main()
{

        //create socket
        size_t socket_int = socket(AF_INET, SOCK_STREAM, 0);
        if(socket_int < 0)
        {
                cout << "套接字创建失败" << endl;
                return -1;
        }

        //服务端的地址
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(3000);

        //绑定
        if(bind(socket_int, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) < 0)
        {
                cout << "bind error" << endl;
                return -1;
        }

        //监听
        if(listen(socket_int,50) < 0)
        {
                cerr << "监听失败" << endl;
                return -1;
        }

        vector<size_t> clientfds;
        int maxfd;
        while(true){
                fd_set readset;
                FD_ZERO(&readset);

                //监听可读事件
                FD_SET(socket_int, &readset);
                //将客户端的事件加入到监听中
                maxfd = socket_int;
                int clientfdslength = clientfds.size();
                for(int i = 0; i < clientfdslength; ++i)
                {
                        if(clientfds[i] != -1)
                        {
                                FD_SET(clientfds[i], &readset);
                                if(maxfd < clientfds[i])
                                        maxfd = clientfds[i];

                        }

                }

                struct timeval tm;
                tm.tv_sec = 1;
                tm.tv_usec = 0;
                //暂时只检测可读的事件
                int ret = select(maxfd+1, &readset, NULL, NULL, &tm);
                if(ret < 0)
                {
                        cout << "select error\n";
                        break;
                }
                else if(ret == 0)//检测超时
                {
                        continue;
                }
                else
                {
                        //检测事件
                        if(FD_ISSET(socket_int,&readset))
                        {

                                struct sockaddr_in clientaddr;
                                socklen_t clientlength = sizeof(struct sockaddr_in);
                                int clientfd = accept(socket_int, (struct sockaddr*)&clientaddr, &clientlength);
                                if(clientfd == -1)
                                {
                                        break;
                                }

                                cout << "接受到连接" << endl;
                                clientfds.push_back(clientfd);
                        }
                        else
                        {
                                char recvBuf[255] = "";
                                size_t clientlength = clientfds.size();
                                for(int i = 0; i < clientlength; ++i)
                                {
                                        if(clientfds[i]!=-1 && FD_ISSET(clientfds[i], &readset))
                                        {
                                                int length = recv(clientfds[i], recvBuf, 255, 0);
                                                if(length <= 0)
                                                {
                                                        cout << "数据接受失败"<<endl;
                                                        close(clientfds[i]);
                                                        clientfds[i] = -1;
                                                        continue;
                                                }
                                                cout << "接受到的数据:" << recvBuf << endl;
                                        }
                                }


                        }

                }

        }

        int clientfdslength = clientfds.size();
        for(int i = 0; i < clientfdslength; ++i)
        {
                if(clientfds[i] != -1)
                        close(clientfds[i]);
        }
        close(socket_int);
        return 0;
}

首先,我们创建一个套接字,然后绑定服务器的IP和端口,然后创建fd_set,把创建的套接字纳入到监听中,然后不断循环测试,如果检测到被修改的套接字,则给它建立一个连接,生成一个套接字,然后加入到监听组中,通过循环不断遍历,查找已经产生变化的套接字,然后接受他们的信息。

需要注意的是select在调用前后可能会修改监听的套接字,需要调用FD_ZERO和FD_SET来及时设置他们。其次time的值设定问题,如果设置这个参数的事件为0,则select立刻返回,如果设置为NULL,则会阻塞。select的参数必须是最大套接字+1

3.select的缺点

(1)每次调用select的时候,都需要把fd集合从用户态复制到内核态,这个开销在fd较多的时候开销很大,同时每次调用select都需要在内核中遍历传递进来的所有fd,这个开销在fd较多时也很大。

(2).能够监控的文件描述符也有限,一般是1024,也有512的,也有2048的

(3).select调用的时候都需要对传入的参数进行重新设定,很麻烦

4.socket的阻塞模式和非阻塞模式

(1).阻塞与非阻塞的区别

当满足条件的时候,执行线程会继续进行,但是条件不满足的时候,线程是否会继续执行下去?

如果会,则它是非阻塞。

如果不会,则它不是阻塞。

默认情况下,socket是阻塞的。

(2).设置非阻塞的方法

如果我们想要创建一个非阻塞的套接字,可以使用以下几种办法

(1).在创建套接字的时候设置type为SOCK_NONBLOCK

int sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

(2).使用accept4的第四个参数为SOCK_NONBLOCK

(3).使用万能的修改描述符函数fcntl或者ioctl函数

(3).send和recv函数在阻塞和非阻塞的表现

send函数是先把需要发送的字节从用户态复制到内核缓冲区,在TCP协议中,数据需要累积到一定的程度才会被发送出去,同样的recv会从内核缓冲区不断复制字节到用户区。这就要求服务方和用户方一致的表现。

假设出现了一种情况,接收方没有读取内核缓冲区数据,那么发送方的内核缓冲区满了怎么办?

这要分情况讨论

(1).非阻塞模式:发送方继续调用send,然后返回一个错误记录,对于recv也一样

(2).阻塞模式:阻塞在send处,对于recv也一样。

这里顺便提一下三次握手和四次挥手

(1).客户端连接服务器,发送一个seq = a

(2).服务器接收到a, 返回一个ack=a+1, 同时发送一个seq = b

(3).客户端接收到b,给服务器发送ack = b +1

如果一次连接,则客服端则无法确定与服务器是否建立了连接

如果只有两次,服务器无法确定客服端的状态

三次就刚刚好,可以相互确认

四次挥手还是以客服端为主动方

(1).客户端发送一个FIN,表示不在发送数据,但是它还可以接收数据,因为你不确定服务器还发送数据不?

(2).服务器受到到FIN,发送一个ACK,表示知道了

(3).服务器再发送一个FIN,表示服务器也没有需要发送的数据了

(4).客服端接收到之后,就知道自己也不会接收到服务器的数据了,于是就返回一个确认ACK,这是表示我知道你不会发送了,而我也不在接收,然后等待一会就自动关闭。

(3)recv和send的返回值问题

一般来讲,当返回值大于0的时候,代表成功发送的字节

但是大于0不代表正确,这是因为有可能你想要发送(接收)20个字节,但是却只发送(接收)了10个,所有你需要重新传输。

其次返回值为0的问题:

返回值为0代表对方端口关闭了,或者你发送了0个字节

最后返回值为负数:

如果是阻塞模式,它肯定错了;但是如果是非阻塞模式,这也不代表一定错了,可能是缓冲区满了无法send或者缓冲区空了无法recv,也有可能是TCP的窗口拥塞。

5.connect的阻塞和非阻塞模式

在阻塞模式下,connect函数会等待到有了明确的连接结构才会返回,否则会阻塞。

在非阻塞模式下,调用函数之后,无论成功与否,都会立刻返回,如果失败了则会返回具有明确意义的错误码,并不代表一定出错。如果我们需要在非阻塞模式下编写代码,则可以先创建阻塞模式的套接字,然后使用fcntl设置为非阻塞。如果需要再非阻塞状态下判断连接情况,就需要使用select或者poll来判断当前套接字是否可写。

6.poll函数的用法

poll函数也是用来检测一组描述符可读可写和异常事件的,

(1).函数解释

int poll(struct pollfd* fds, nfds_t nfds,int timeout);

fds是一个结构体指针,指向数组第一位。

nfds,类型其实是long int,代表数组长度

timeout代表检测时间

结构体的定义如下:

struct pollfd{

int fd;//事件集合

short event;//关心的事件组合

short revent;//检测之后得到的事件类型

};

event的事件类型如下

|------------|--------------------|----------|----------|
| 事件 | 事件描述 | 是否可以作为输入 | 是否可以作为输出 |
| POLLIN | 数据可读 | 是 | 是 |
| POLLOUT | 数据可写 | 是 | 是 |
| POLLRDNORM | 数据可读 | 是 | 是 |
| POLLRDBAND | 优先级带数据可读 | 是 | 是 |
| POLLPRI | 高级优先级数据可读 | 是 | 是 |
| POLLWRNORM | 数据可写 | 是 | 是 |
| POLLWRBAND | 优先带数据可写 | 是 | 是 |
| POLLRDHUP | TCP连接对端关闭,或者关闭了写操作 | 是 | 是 |
| POLLHUP | 挂起 | 否 | 否 |
| POLLERR | 错误 | 否 | 否 |
| POLLVAL | 文件描述符没有打开 | 否 | 否 |

poll比起select有许多优点,第一就是不用担心检测事件的数量,第二是不用重复设置监听,第三是不用买担心监听最大文件描述符最大值+1,第四是速度更快。

cpp 复制代码
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<fcntl.h>
#include<poll.h>
#include<iostream>
#include<string.h>
#include<errno.h>
#include<vector>

int main()
{
//创建套接字
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if(listenfd == -1)
        {
                std::cerr << "create socket error " << std::endl;
                return -1;
        }
//套接字修改为非阻塞
        int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
        int newSocketFlag = oldSocketFlag | O_NONBLOCK;
        if(fcntl(listenfd, F_SETFL, newSocketFlag)==-1)
        {
                close(listenfd);
                std::cerr<<"scoket flag change error" <<std::endl;
        }
//复用IP和端口
        int no = 1;
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*) &no, sizeof(no));
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*) &no, sizeof(no));
//设置服务器地址
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(3000);
//绑定
        if(bind(listenfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) == -1)
        {
                std::cout << "绑定失败" << std::endl;
                close(listenfd);
                return -1;
        }
//开启监听
        if(listen(listenfd, 100) == -1)
        {
                close(listenfd);
                return -1;
        }
//开始准备监听套接字和事件
        std::vector<pollfd> fds;
        pollfd listen_fd_info;
        listen_fd_info.fd = listenfd;//先监听服务器的套接字
        listen_fd_info.events = POLLIN;//监听可读事件
        listen_fd_info.revents = 0;
        fds.push_back(listen_fd_info);//放入监听数组

        bool exist_invalid_fd;
        int n;
        while(true)
        {
                exist_invalid_fd = false;
                n = poll(&fds[0], fds.size(), 1000);//检查是否有可读的套接字
                if(n < 0)
                {
                        std::cerr << "poll is error" << std::endl;
                        if(errno == EINTR)
                                continue;
                        break;
                }
                else if(n == 0) continue;
                //poll的缺点来了,不管有没有可读的,全都遍历一遍读取
                for(size_t i = 0; i < fds.size(); ++i)
                {
                        //事件可读
                        if(fds[i].events & POLLIN)
                        {
                                if(fds[i].fd == listenfd)//服务器套接字可读,客户端连接来了
                                {
                                        struct sockaddr_in clientaddr;
                                        memset(&clientaddr, 0, sizeof(struct sockaddr_in));
                                        socklen_t len = sizeof(struct sockaddr_in);
                                        int clientsock = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
                                        if(clientsock != -1)
                                        {
                                                int oldSocketFlag = fcntl(clientsock, F_GETFL, 0);
                                                int newSocketFlag = oldSocketFlag | O_NONBLOCK;
                                                if(fcntl(clientsock, F_SETFL, newSocketFlag) == -1)
                                                {
                                                        close(clientsock);
                                                        std::cerr<<"change client socket fail\n";
                                                }
                                                else
                                                {
                                                        struct pollfd client_fd_info;
                                                        client_fd_info.fd = clientsock;
                                                        client_fd_info.events = POLLIN;
                                                        client_fd_info.revents = 0;
                                                        fds.push_back(client_fd_info);
                                                        std::cout << "new client socket create successful" << std::endl;
                                                }
                                        }
                                }
                                else//其他事件可读
                                {
                                        char recvBuf[100] = "";
                                        int m = recv(fds[i].fd, recvBuf, 100, 0);
                                        if(m <= 0)
                                        {
                                                if(errno != EINTR && errno != EWOULDBLOCK)
                                                {
                                                        for(std::vector<pollfd>::iterator iter = fds.begin();iter != fds.end(); ++iter)
                                                        {
                                                                if(iter->fd == fds[i].fd)
                                                                {
                                                                        std::cout << "client diconnected, clientfd:" << fds[i].fd << std::endl;
                                                                        close(fds[i].fd);
                                                                        iter->fd = -1;
                                                                        exist_invalid_fd = true;
                                                                        break;
                                                                }
                                                        }

                                                }
                                        }
                                        else
                                        {
                                                std::cout << "recv form client: " << recvBuf << ", clientfd: "<< fds[i].fd << std::endl;
                                        }
                                }
                        }
                        else if(fds[i].revents & POLLERR)
                        {
                                std::cerr<< "revents is error\n";
                        }
                }

                if(exist_invalid_fd)
                {
                        for(std::vector<pollfd>::iterator iter = fds.begin(); iter != fds.end();)
                        {
                                if(iter->fd == -1)
                                        iter = fds.erase(iter);
                                else
                                        ++iter;

                        }

                }

        }
                for(std::vector<pollfd>::iterator iter = fds.begin(); iter != fds.end(); ++iter)
                        close(iter->fd);





}

poll的缺点也很明显,第一,它会不管数组中的文件描述符是否发送了变化,全都从内核空间和用户空间之间整体复制;第二,poll函数返回之后,需要遍历fd集合来获取就绪的fd;第三,同时连接大量用户在某一时刻可能只有很少的就绪状态,因为随着监视的描述符数量的增长,其效率也会下降。

7.epoll模型

1.基本用法

(1).创建epollfd

int epoll_create(int size);

size传入一个大于0的参数就行了,成功创建则返回一个非负数的epollfd;

(2).将需要操作的fd进行操作,比如将fd绑定到epollfd上,或者解绑下来

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

epfd:被创建出来的epollfd

op:操作类型,比如解绑,绑定到epfd;它的取值如下:

1).EPOLL_CTL_ADD绑定到epfd

2).EPOLL_CTL_MOD修改fd

3).EPOLL_CTL_DEL从epfd上解绑

fd:被监听的描述符

event:一个结构体地址,如果没有特殊要求可以设置为NULL

它的结构如下:

struct epoll_event{

uint_32_t events;//需要检测的事件

epoll_data_t data;//用户自定义的数据

};

成功调用返回0,失败返回-1

(3).设置好fd需要检测的事件,绑定好fd之后就可以开始调用epoll_wait函数来检测事件了

int epoll_wait(int epfd, struct epoll_event* events, int maxevent, int timeout);

epfd:需要检测描述符集合

events:监听的事件集合

maxevent:最大事件数量

timeout:监听的时间,毫秒

函数调用成功则需要返回返回事件fd的数量,返回0代表超时,返回-1代表失败。

2.epoll和poll的区别

epoll_wait调用结束后,可以得到所有事件就绪的套接字,而poll在调用结束后,得到了是原本全部的套接字,需要一个一个去检查谁才是可以用的。

3.边沿触发ET与水平触发LT

epoll有水平触发和边缘促发,这个概念很简单,不要说不懂。

水平触发:事件存在则不断触发

边沿触发:事件不存在的时候,突然有了则触发,如果事件还存在则不促发;如果事件没有了,然后再次出现,则触发。简单来讲,事件状态发生了改变就触发。

以socket读事件为例子

水平触发条件:socket上有可读的数据

边沿触发条件:socket上来了新的数据

socket写事件的例子

水平触发(LT):socket可写变成了不可写,或者时socket不可写变成了可写

边沿触发(ET):socket不可写变成了可写

那么对于读写过程就要慎重处理:

(1)读过程

如果socket上存在可读数据,使用边沿触发就要一次读取全部可读。

(2)写过程

如果socket上存在可写数据,使用水平触发,一定要一次把全部写数据写完。

上面这两点很好理解吧,因为如果在读过程中,边沿触发只进行一次,除非下来来新的数据,如果不一次读完,则会出现问题,也许是数据丢失。

cpp 复制代码
​

#include<sys/types.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<fcntl.h>
#include<poll.h>
#include<iostream>
#include<string.h>
#include<errno.h>
#include<vector>

int main(){

//创建套接字
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1)
{
	std::cerr << "create socket error " << std::endl;
	return -1;
}
//设置为非阻塞
int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if(fcntl(listenfd, F_SETFL, newSocketFlag)==-1)
{
	close(listenfd);
	std::cerr<<"scoket flag change error" <<std::endl;
}
	
	int no = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*) &no, sizeof(no));
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*) &no, sizeof(no));
//设置服务器的地址
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(struct sockaddr_in));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	server_addr.sin_port = htons(3000);
//绑定服务器的地址
	if(bind(listenfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) == -1)
	{
		std::cout << "绑定失败" << std::endl;
		close(listenfd);
		return -1;
	}
//监听
	if(listen(listenfd, 100) == -1)
	{
		close(listenfd);
		return -1;
	}

//创建一个epollfd
int epollfd = epoll_create(1);
if(epollfd == -1)
{
	std::cerr << "create epollfd error\n";
	close(listenfd);
	return -1;
}	
//事件的设置
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;//设置监听对象
listen_fd_event.events = EPOLLIN;//设置监听事件
//默认使用的是水平触发
//加入宏定义之后可以使用边沿触发
#ifdef __ET__
std::cout << "set ET " <<std::endl;
listen_fd_event.events |= EPOLLET;
#endif
		
//将listenfd绑定到epollfd上面
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
	std::cerr<<"epoll_ctl error\n";
	close(listenfd);
	return -1;
}

int n;
while(true)
{
	//检测事件,将监听的事件写入到下面这个数组中
	epoll_event epoll_events[1024];
	n = epoll_wait(epollfd, epoll_events, 1024, 1000);
	if(n < 0)
	{
		if(errno == EINTR)
			continue;
		std::cerr << "epoll_wait is error" << std::endl;
		break;
	}
	//监听超时,继续监听	
	else if(n == 0) 
		continue;
	//检测事件集合
	for(size_t i = 0; i < n; ++i)
	{
		//如果事件被检测到可读
		if(epoll_events[i].events & EPOLLIN)
		{
			if(epoll_events[i].data.fd == listenfd)//如果可读的事件是listenfd
			{
				//给连接到的客户端创建套接字,设置为非阻塞
				struct sockaddr_in clientaddr;
				memset(&clientaddr, 0, sizeof(struct sockaddr_in));
				socklen_t len = sizeof(struct sockaddr_in);
				int clientsock = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
				if(clientsock != -1)
				{
					int oldSocketFlag = fcntl(clientsock, F_GETFL, 0);
					int newSocketFlag = oldSocketFlag | O_NONBLOCK;
					if(fcntl(clientsock, F_SETFL, newSocketFlag) == -1)
					{
						close(clientsock);
						std::cerr<<"change client socket fail\n";
					}
					else
					{
						epoll_event client_fd_event;
						client_fd_event.data.fd = clientsock;
						client_fd_event.events = EPOLLIN;
						#ifdef __EL__
						client_fd_event.events |= EPOLLET;
						#endif
						if(epoll_ctl(epollfd, EPOLL_CTL_ADD, clientsock, &client_fd_event))
						{
							std::cout << "new client socket create successful" << std::endl;
						}
						else
						{
							std::cerr << "client epoll ctl is error " << std::endl;
							
							close(clientsock);
						}
					}	
				}
				else//其他事件可读,每次读取一个字节。用来测试水平触发
				{
					char recvBuf[100] = "";
					int m = recv(epoll_events[i].data.fd, recvBuf, 1, 0);
					if(m == 0)
					{
						//对端关闭了连接,从epollfd上移除clientfd
						if(epoll_ctl(epollfd,EPOLL_CTL_DEL,epoll_events[i].data.fd, NULL))
						{
							std::cout << "客户端断开连接" << epoll_events[i].data.fd << std::endl;
						}
						close(epoll_events[i].data.fd);
					}
					else if(m < 0)
					{
						//如果出错
						if(errno != EINTR && errno != EWOULDBLOCK)
						{
							if(epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL)!=-1)
							{
								std::cout << "客户端断开连接" << epoll_events[i].data.fd << std::endl;
							}
							close(epoll_events[i].data.fd);
						}
					}
					else
					{
						std::cout << "recv form client: " << recvBuf << ", clientfd: "<< epoll_events[i].data.fd << std::endl;						
					}
				}
			}
			else if(epoll_events[i].events & EPOLLERR)
			{		
				std::cerr<< "events is error\n";
			}

		}		
	}
	close(listenfd);
	return 0;
}
​

编译的时候可以加入不同的宏定义来测试,可以得知使用水平触发的时候,只要缓存区有数据,就会被不断读取。我们方式一个字符串到服务器,现在服务器每次只能读取一个字符,会发现水平触发的时候,它每次都读取一个字符,直到读取完毕,而加入宏定义__ET__之后,发送一个长字符串,它读取一个字符后就不动了,不读取了。

同样的,我们修改一下事件触发的方式,可以看看写模式下的两种触发方式

在这种写模式下,水平触发会不断发送内核缓存可写的信号,导致程序疯狂输出。

总结:在ET模式下,服务器给客服端注册可写模式后,在经过一次触发后,除非新的数据到达,否则不会被触发,如果需要在缓存区有数据的时候再次触发,则需要再次注册,检测可写事件。在ET模式下,读事件时必须把数据读取干净,否则下一次事件发送过来,就没有机会读上一次没读完的数据了。在LT模式下,不需要写事件的时候,一定要移除对他的注册

4.EPOLLONESSHOT选项

如果epoll模型注册了这个模式,则被监听的对象在触发一次之后,再也不会被触发

8.readv和writev

高效的服务要减少系统调用,而write和read都是系统调用,他们读取文件描述符对应的缓冲区,然后将缓冲区的数据写入到其他地方,这样就需要频繁进行系统调用,我们也可以一次性的把其他缓冲区的内容放在一起,按照自定义的规则进行一次系统调用。这个方法就是如下几个函数:

ssize_t readv(int fd, const struct iovec* iov, int iovcnt);

ssize_t writev(int fd, const struct iovec* iov, int iovcnt);

ssize_t preadv(int fd, const struct iovec* iov, int iovcnt, off_t offset);

ssize_t pwritev(int fd, const struct iovec* iov, int iovcnt, off_t offset);

fd代表文件描述符

offset代表平移指针位置,也就是从某个地方开始读取或者写入

iov是一个结构体指针,用来指向一个结构体数据,它是用来连接多个需要一起连接到fd的对象组

iovcnt代表数组元素个数.

返回值为读取或者写入的字节数

struct iovcnt{

void* iov_base;//数据起始位置

size_t iov_len;//需要传输的字节数

};

cpp 复制代码
char *buf1[] = "第一个缓存区11111111111111";
char *buf2[] = "第二个缓冲区22222222222222";
char *buf3[] = "第三个缓冲区33333333333333";

struct iovec iov[3];
iov[0].iov_base = buf1;
iov[0].iov_len = strlen(buf1);
iov[1].iov_base = buf2;
iov[1].iov_len = strlen(buf2);
iov[2].iov_base = buf3;
iov[2].iov_len = strlen(buf3);

size_t nwirtelen = writev(fpCSV, iov, 2);

9.本机字节序和网络字节序

本机字节序分为小端和大端两种,小端指的是一个整数,它的高位被储存在高位地址,低位储存在低位地址,大端相反。网络字节序是网络传输过程使用的字节序,他用的是大端。

给一个例子

比如一个数字位0x1234

小端字节序上,如果我们将他转换为char类型,然后作为整数输出,得到的是12

大端字节序就是34

相关推荐
麻瓜也要学魔法2 小时前
链路状态路由协议-OSPF
网络
Estar.Lee2 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
傻啦嘿哟3 小时前
代理IP在后端开发中的应用与后端工程师的角色
网络·网络协议·tcp/ip
Red Red3 小时前
网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
网络·笔记·学习·安全·web安全
亚远景aspice5 小时前
ISO 21434标准:汽车网络安全管理的利与弊
网络·web安全·汽车
Estar.Lee6 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
友友马6 小时前
『 Linux 』网络层 - IP协议(一)
linux·网络·tcp/ip
码老白7 小时前
【老白学 Java】Warshipv2.0(二)
java·网络
HackKong7 小时前
小白怎样入门网络安全?
网络·学习·安全·web安全·网络安全·黑客