2021-02-01 11:13:12
浏览数 (2)
TCP客户端
代码语言:javascript
复制// 定义 _GNU_SOURCE 是为了获得对 EPOLLRDHUP 即 TCP链接被对方关闭或者对方关闭了写操作的 这一事件类型的支持
#define _GNU_SOURCE 1
// 下面是系统调用需要依赖的头文件
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <stdbool.h>
// 最大读缓冲区大小
#define BUFFER_SIZE 64
// int setnonblocking(int fd) {
// int old_option = fcntl(fd, F_GETFL);
// int new_option = old_option | O_NONBLOCK;
// fcntl(fd, F_SETFL, new_option);
// return old_option;
// }
// 给epoll注册需要关注的fd以及对应的事件
void addfd(int epollfd, int fd, bool hup) {
struct epoll_event event;
event.data.fd = fd;
event.events |= EPOLLIN;
if (hup) {
event.events |= EPOLLRDHUP;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
// setnonblocking(fd);
}
int main(int argc, char* argv[]) {
if (argc <= 2) {
printf("usage: %s ip_address port_numbern", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
// 设置服务器地址 以及将ip和端口字段的存储字节序转化成网络序
struct sockaddr_in server_address;
bzero(&server_address, sizeof(server_address));
server_address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &server_address.sin_addr);
server_address.sin_port = htons(port);
// 创建连接socket
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);
// 同步阻塞connect直到成功或者失败
if (connect(sockfd, (struct sockaddr*) &server_address, sizeof(server_address)) < 0) {
printf("connecttion failedn");
close(sockfd);
return 1;
}
struct epoll_event events[2];
// 创建epoll文件描述符 参数在2.6之后的版本已经不需要了,内核会自动调整这个最大的关注文件描述符数量,目前要写大于0是为了兼容之前的内核版本
int epollfd = epoll_create(2);
assert(epollfd != -1);
// 注册标准输入文件描述符
addfd(epollfd, 0, false);
// 注册连接socket文件描述符
addfd(epollfd, sockfd, true);
// 程序空间用来存储socket上从服务器返回的数据的读缓冲区
char read_buf[BUFFER_SIZE];
// 给splice函数使用的管道
int pipefd[2];
int ret = pipe(pipefd);
assert(ret != -1);
// 服务器关闭了连接的flag
bool ended = false;
while(1) {
// 等待所关注的2个文件描述符上的事件触发 会同步阻塞在这里等待
int number = epoll_wait(epollfd, events, 2, -1);
// 程序收到信号后会终止如epoll_wait这样的系统调用,且不会自动重启被中断的系统调用,这时需要我们自己判断number的返回值(-1)且errno等于 EINTR,我们自己来让程序继续执行等待的逻辑,如果这个错误不是被信号打断引起的,则按出错处理
if (number < 0) {
if (number < 0 && errno != EINTR) {
printf("epoll failturen");
break;
}
}
// number指示这轮wait得到的结果中所有有响应的文件描述符
for(int i=0;i<number; i) {
int active_sockfd = events[i].data.fd;
if (active_sockfd == 0 && (events[i].events & EPOLLIN)) {
// 标准输入有数据可以读(用户输入了数据)
// splice函数把 文件描述符0 中的数据移动到 文件描述符 pipefd[1] 中,不在程序空间中拷贝一次
ret = splice(0, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
// splice函数把 pipefd[0] 中的数据移动到 文件描述符 sockfd 中,不在程序空间中拷贝一次
// 实现了把用户输入的内容直接发送给socket
ret = splice(pipefd[0], NULL, sockfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
} else if (active_sockfd == sockfd && (events[i].events & EPOLLIN)) {
// socket上有数据可读 (服务器发来了数据)
memset(read_buf, '