您的位置:首页 > 科技 > 能源 > 上海市建设网站_网页设计个人总结_nba今日数据_seo网站推广教程

上海市建设网站_网页设计个人总结_nba今日数据_seo网站推广教程

2024/12/23 9:57:00 来源:https://blog.csdn.net/MaFei5200/article/details/144187838  浏览:    关键词:上海市建设网站_网页设计个人总结_nba今日数据_seo网站推广教程
上海市建设网站_网页设计个人总结_nba今日数据_seo网站推广教程

TCP/IP网络编程-C++ (下)

  • 一、基于Linux的进阶服务端
    • 1、`shutdown()` 函数 - 优雅的断开连接
      • 1.1、函数原型
      • 1.2 参数解析
      • 1.3 `shutdown()` 函数在client端应用示例代码:
    • 2、IP地址和域名相互转换
      • 2.1、`gethostbyname()` - 域名转换为IP
      • 2.2 `gethostbyaddr()` - IP转换为域名
    • 3、`select()`函数实现I/O复用服务器
      • 3.1、`select()`函数介绍
      • 3.2、I/O复用服务器代码示例:
    • 4、高级I/O - `readv()` & `writev()` 函数
      • 4.1、`writev()`函数介绍
      • 4.2、`readv()`函数介绍
    • 5、优与select的epoll
      • 5.1、`epoll_create()` 函数 - 创建保存epoll文件描述符的空间
      • 5.2、`epoll_ctl()` 函数- 向空间注册并注销文件描述符
      • 5.3、`epoll_wait()` 函数 - 等待文件描述符发生变化
      • 5.4、基于epoll的字符串转换服务端代码实现

一、基于Linux的进阶服务端

1、shutdown() 函数 - 优雅的断开连接

1.1、函数原型

	int shutdown(int sock, int howto);// 成功时返回0, 失败时返回-1

1.2 参数解析

参数参数解析
sock需要断开连接的套接字描述符
howto传递端口方式信息
  • 第二个参数howto可传递的值如下:
howto可传递值说明
SHUT_RD断开输入流,套接字无法再接收数据
SHUT_WR断开输出流,套接字无法再发送数据
SHUT_RDWR断开输入流和输出流,套接字无法接受和发送数据
  • 如果shutdown()第二个参数传递“SHUT_RDWR”参数,这样和close()函数有什么区别呢?区别在于使用shutdown()的“SHUT_RDWR”方式关闭,只是会关闭发送和接收数据的功能,并不会关闭套接字描述符,也就是说套接字资源并没有被释放。而close()函数不仅关闭了发送和接收数据的功能,同时关闭了套接字,释放了相关资源。

1.3 shutdown() 函数在client端应用示例代码:

/*头文件和之前示例中一样,故省略*/
const int BUFFER_SIZE = 128;
int main(void){
/*
这和字符串转换功能一样,这里值添加了shutdown()函数调用,故省略一些重复代码。
*/while(true){std::string str_input = "";std::cout<<"please input:";std::cin>>str_input;if(str_input == "q"){if(shutdown(clie_sock, SHUT_RD) == -1){std::cout<<"shutdown error\n";}// 调用了shtudown()后关闭了输入流,也就是不能再接收数据了,但还可以发送数据send(clie_sock, str_input.c_str(), str_input.size(), 0);break;}send(clie_sock, str_input.c_str(), str_input.size(), 0);char message[BUFFER_SIZE] = {0};int recv_size = 0;if((recv_size = recv(clie_sock, message, BUFFER_SIZE - 1, 0)) == -1){std::cout<<"read error\n";break;}}close(clie_sock);return 0; 
}

2、IP地址和域名相互转换

2.1、gethostbyname() - 域名转换为IP

  • 函数原型
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
// 成功返回hostent地址,失败返回NULL.
  • hostent结构体定义如下:
struct hostent
{char *h_name;			/* Official name of host.  */char **h_aliases;		/* Alias list.  */int h_addrtype;		/* Host address type.  */int h_length;			/* Length of address.  */char **h_addr_list;	/* List of addresses from name server.  */
};
  • 结构体成员说明:
hostent结构体成员说明
h_name存放官方域名
h_aliases存放除官方域名外的其它域名,可以通过这些域名访问同一主页
h_addrtype保存IP的地址族信息,若是IPv4则保存AF_INET,若是IPv6则保存PF_INET6
h_length保存IP地址长度
h_addr_list以整数形式保存域名对应的IP地址
  • gethostbyname()函数代码示例:
#include <iostream>
#include <netdb.h>
#include <arpa/inet.h>
int main(void){std::string domain_name = "www.baidu.com";struct hostent * host = gethostbyname(domain_name.c_str());if(host == NULL){std::cout<<"no domain name\n";return 0;}if(host->h_addrtype == AF_INET){std::cout<<"use IPv4\n";}else if(host->h_addrtype == AF_INET6){std::cout<<"use IPv6\n";}else{std::cout<<"use other\n";}for(int i = 0; host->h_addr_list[i]; ++i){std::string ip(inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));std::cout<<"ip "<<i<<" is: "<<ip<<"\n";}return 0;
}
// run result:
// use IPv4
// ip 0 is: 110.242.68.3
// ip 1 is: 110.242.68.4

2.2 gethostbyaddr() - IP转换为域名

  • 函数原型:
#include <netdb.h>
struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);
// 成功返回hostent地址,失败返回NULL.
  • 参数解析:
参数参数解析
addr含有IP地址信息的in_addr结构体指针。为了同时传递IP地址信息外的其它信息,该变量声明为char*类型
len地址信息的字节数,IPv4时为4,IPv6时为16
family地址族信息
  • gethostbyaddr()函数代码示例:
#include <iostream>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
int main(void){struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_addr.s_addr = inet_addr("127.0.0.1");struct hostent* host = gethostbyaddr(&addr.sin_addr, sizeof(addr.sin_addr), AF_INET);if(host == NULL){std::cout<<"not found\n";return 0;}for(int i = 0; host->h_aliases[i]; ++i){std::string domain_name(host->h_aliases[i]);std::cout<<"domain name:"<<domain_name<<"\n";}return 0;
}
// run result:
// domain name:localhost.localdomain

3、select()函数实现I/O复用服务器

3.1、select()函数介绍

  • 函数原型:
#include <sys/time.h>
#include <sys/select.h>
int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
// 成功时返回大于0的值,失败时返回-1
  • 参数解析:
参数参数解析
maxfd监视对象文件描述符数量
readset将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值
writeset将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值
exceptset将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值
timeout为防止select()函数陷入无限阻塞状态,所以传递timeout,到达时间后没有监视到变化也会返回
返回值发生错误时返回-1,超时时返回0(就是达到了timeout时间还未监视到变化),关注的事件发生变化时返回发生变化的文件描述符数量
  • fd_set类型说明:
    (1)、fd_set定义原型
typedef struct{long int fds_bits[1024];
}fd_set;

  (2)、fd_set就一个long int类型,大小为1024的数组,其下标就表示套接字的文件描述符,所有可以同时监视1024个套接字。每一位都初始化为0,如果某位值为1,则表示监视该套接字。如果下标为3的位值为1,表示监视文件描述符为3的套接字。
  (3)、操作fd_set的宏

FD_ZERO(fd_set* fdset);				// 将fd_set所有位初始化为0
FD_SET(int fd, fd_set* fdset);		// 在fd_set中注册文件描述符fd的信息,表示监视文件描述符为fd的套接字
FD_CLR(int fd, fd_set* fdset);		// 在fd_set中清楚文件描述符fd的信息,表示不在监视描述符为fd的套接字
FD_ISSET(int fd, fd_set* fdset);	// 判断fd_set中描述符fd是否被注册,若注册返回true,反之返回false,
  • timeval参数说明:
    (1)、原型及成员说明:
struct timeval{long tv_sec;	// 设置秒long tv_usec;	// 设置毫秒
}
  • select()函数功能

使用select()函数时可以将多个文件描述符集中到一起监视,会关注如下变化:
(1)、是否存在套接字接收数据
(2)、无需阻塞传输数据的套接字有哪些
(3)、哪些套接字发生了异常

  • select()函数调用顺序

第一步:
1、设置文件描述符:利用fd_set注册文件描述符
2、指定监视范围:范围就是套接字文件描述符最大值+1,也就是select()函数的第一个参数
3、设置超时:将超时时间填在timeout中。
第二步:
1、调用select()函数监听事件
第三步:
1、查看返回结果,获取变化的文件描述符进行相应处理

3.2、I/O复用服务器代码示例:

  • 此示例基于以前的字符串转换的服务端修改的,客户端没有变化,可以使用之前的客户端来验证测试。
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>
#include <regex>
#include <cctype>
const int BUFFER_SIZE = 128;void to_lower(const std::string& str_input, std::string& str_output){std::regex pattern("^[a-zA-Z]+$");bool is_letters = std::regex_match(str_input, pattern);if(is_letters){str_output.resize(str_input.size());for(const auto& da : str_input){str_output += std::tolower(da);}}else{str_output = "包含其它字符,转换失败!";}return;
}int main(void){int serv_sock = socket(PF_INET, SOCK_STREAM, 0);  if(serv_sock == -1)std::cout<<"socket error\n";struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(8080);if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)std::cout<<"bind error\n";if(listen(serv_sock, 3) == -1)std::cout<<"listen error\n";struct sockaddr_in clie_addr;memset(&clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size = 0;fd_set fd_read, fd_temp;FD_ZERO(&fd_read);FD_SET(serv_sock, &fd_read);int fd_max = serv_sock;struct timeval timeout;while(true){timeout.tv_sec = 5;timeout.tv_usec = 0;fd_temp = fd_read;int fd_num = select(fd_max + 1, &fd_temp, 0, 0, &timeout);if(fd_num == -1){std::cout<<"select error\n";break;}else if(fd_num == 0){std::cout<<"select timeout\n";continue;}for(int index = 0; index < fd_max + 1; ++index){if(FD_ISSET(index, &fd_temp)){if(index == serv_sock){int clie_sock = accept(serv_sock, (struct sockaddr*) &clie_addr, &clie_addr_size);if(clie_sock == -1){std::cout<<"accept error\n";break;}FD_SET(clie_sock, &fd_read);fd_max = fd_max > clie_sock ? fd_max : clie_sock;}else{char mess[BUFFER_SIZE] = {0};int recv_size = 0;if((recv_size = recv(index, mess, BUFFER_SIZE - 1, 0)) == -1) {std::cout<<"recv error\n";break;}mess[recv_size] = '\0';std::string str_message(mess, recv_size);if(str_message.empty()){FD_CLR(index, &fd_read);close(index);}else{std::string str_result = "";to_lower(str_message, str_result);   send(index, str_result.c_str(), str_result.size(), 0);}}}}}close(serv_sock);return 0;
}

4、高级I/O - readv() & writev() 函数

  • 通过writev()函数可以将分散保存在多个缓冲中的数据一并发送,通过readv()函数可在多个缓冲器分别接收数据。适当的调用这两个函数可以减少I/O函数的调用次数。

4.1、writev()函数介绍

  • 函数原型:
#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec* iov, int iovcnt);
// 成功时返回发送的字节数,失败时返回-1
  • 参数解析:
参数参数解析
filedes套接字文件描述符
ioviovec结构体 数组 的地址,iovec结构体包含待发送的数据和大小
iovcntiov参数的长度
  • struct iovec结构体原型如下:
struct iovec{void* iov_base;	//缓冲地址size_t iov_len;	//缓冲大小
}
  • writev()函数代码示例:
#include <sys/uio.h>
#include <iostream>
int main_(void){int data_vec_size = 2;struct iovec data_vec[data_vec_size];char buf_1[] = "hello";char buf_2[] = "world";data_vec[0].iov_base = buf_1;data_vec[0].iov_len = 5;data_vec[1].iov_base = buf_2;data_vec[1].iov_len = 5;int bytes = writev(1, data_vec, data_vec_size);if(bytes == -1){std::cout<<"writev error\n";}return 0;
}

4.2、readv()函数介绍

  • 函数原型:
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec* iov, int iovcnt);
// 成功时返回发送的字节数,失败时返回-1
  • readv()函数代码示例:
#include <sys/uio.h>
#include <iostream>
#include <string>
int main(void){int data_vec_size = 2;struct iovec data_vec[data_vec_size];char buf_1[128] = {0};char buf_2[128] = {0};data_vec[0].iov_base = buf_1;data_vec[0].iov_len = 128;data_vec[1].iov_base = buf_2;data_vec[1].iov_len = 128;int bytes = readv(1, data_vec, data_vec_size);if(bytes == -1){std::cout<<"readv error\n";}std::string str_buf_1(buf_1, 128);std::string str_buf_2(buf_2, 128);std::cout<<"buf_1:"<<str_buf_1<<"\n";std::cout<<"buf_2:"<<str_buf_2<<"\n";return 0;
}

5、优与select的epoll

5.1、epoll_create() 函数 - 创建保存epoll文件描述符的空间

  • 函数原型:
#include <sys/epoll.h>
int epoll_create(int size);
// 成功时返回epoll文件描述符,失败时返回-1
// size:epoll实例的大小
  • 调用epoll_create()函数时创建的文件描述符保存空间称为“epoll例程”,通过参数size传递的值决定“epoll例程”的大小,但该值并不能决定“epoll例程”的大小,而是仅供操作系统参考。

5.2、epoll_ctl() 函数- 向空间注册并注销文件描述符

  • 函数原型:
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
// 成功时返回0, 失败时返回-1
  • 参数解析:
参数参数解析
epfd用于注册监视对象的epoll例程的文件描述符
op用于指定监视对象的添加、删除或更改等操作
fd需要注册的监视对象文件描述符
event监视对象的事件类型
  • 调用示例:

epoll_ctl(A, EPOLL_CTL_ADD, B, C);
含义:epoll例程A中注册文件描述符B,主要目的是监视参数C中的事件
epoll_ctl(A, EPOLL_CTL_DEL, B, NULL);
含义:从例程A中删除文件描述符B

  • epoll_ctl()第二个参数op传递的常量及含义:
含义
EPOLL_CTL_ADD将文件描述符注册到epoll例程
EPOLL_CTL_DEL从epoll例程中删除文件描述符
EPOLL_CTL_MOD更改注册的文件描述的关注事件发生情况
  • epoll_ctl()第四个参数event结构
struct epoll_event{__uint32_t events;epoll_data_t data;
}typedef union epoll_data{void* ptr;int fd;__uint32_t u32;__uint64_t u64;}eooll_data_t;
  • 其中epoll_enent结构体中成员events可传递的常量及事件如下:
事件
EPOLLIN读取数据事件
EPOLLOUT输出缓冲为空,可以立即发送数据的事件
EPOLLPRI收到OOB数据情况
EPOLLRDHUP断开连接或半连接的情况,在边缘触发方式下非常有用
EPOLLERR发生错误的情况
EPOLLET以边缘触发的方式得到事件通知
EPOLLONESHOT发生一次事件后,文件描述符不在收到事件通知,需要再次设置事件

5.3、epoll_wait() 函数 - 等待文件描述符发生变化

  • 函数原型:
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
// 成功时返回发生事件的描述符数量,失败返回-1,超时返回0
  • 参数解析
参数参数解析
epfd表示事件发生监视范围的epoll例程的文件描述符
events保存发生事件的文件描述符集合的结构体地址,其所需要的缓冲要使用malloc()动态分配
maxevents第二个参数可保存的最大事件数
timeout以毫秒为单位的等待时间,传递-1时会一直等待直到事件发生

5.4、基于epoll的字符串转换服务端代码实现

  • 此示例基于使用select()服务端修改,客户端没有变化,使用之前的服务端即可测试
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>
#include <regex>
#include <cctype>
#include <sys/epoll.h>
#include <stdlib.h>
const int BUFFER_SIZE = 128;
const int EPOLL_SIZE = 50;void to_lower(const std::string& str_input, std::string& str_output){std::regex pattern("^[a-zA-Z]+$");bool is_letters = std::regex_match(str_input, pattern);if(is_letters){str_output.resize(str_input.size());for(const auto& da : str_input){str_output += std::tolower(da);}}else{str_output = "包含其它字符,转换失败!";}return;
}int main(void){int serv_sock = socket(PF_INET, SOCK_STREAM, 0);  if(serv_sock == -1)std::cout<<"socket error\n";struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(8080);if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)std::cout<<"bind error\n";if(listen(serv_sock, 3) == -1)std::cout<<"listen error\n";struct sockaddr_in clie_addr;memset(&clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size = 0;int epfd = epoll_create(EPOLL_SIZE);struct epoll_event *ep_events = (struct epoll_event*)malloc(sizeof(struct epoll_event)*EPOLL_SIZE);struct epoll_event events;events.events = EPOLLIN;events.data.fd = serv_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &events);while(true){int event_count = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);if(event_count == -1){std::cout<<"epoll error\n";break;}for(int index = 0; index < event_count; ++index){if(ep_events[index].data.fd == serv_sock){int clie_sock = accept(serv_sock, (struct sockaddr*) &clie_addr, &clie_addr_size);if(clie_sock == -1){std::cout<<"accept error\n";break;}events.events = EPOLLIN;events.data.fd = clie_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, clie_sock, &events);}else{char mess[BUFFER_SIZE] = {0};int recv_size = 0;if((recv_size = recv(ep_events[index].data.fd, mess, BUFFER_SIZE, 0)) == -1) {std::cout<<"recv error\n";break;}mess[recv_size] = '\0';std::string str_message(mess, recv_size);if(str_message.empty()){epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[index].data.fd, NULL);close(ep_events[index].data.fd);}else{std::string str_result = "";to_lower(str_message, str_result);   send(ep_events[index].data.fd, str_result.c_str(), str_result.size(), 0);}}}}close(serv_sock);return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com