服务端-客户端-版本1

服务端-客户端-版本1

服务端代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>
#include <sys/epoll.h>
#include <netinet/tcp.h>

// 设置非阻塞的IO。
/*
这样,调用 setnonblocking(fd) 函数后,文件描述符 fd 就被设置为了非阻塞模式。
在非阻塞模式下,文件操作函数(如读、写、接收等)会立即返回,不会阻塞程序执行,即使没有数据可读或者缓冲区已满。
*/
void setnonblocking(int fd)
{
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        printf("Usage: ./tcpepoll ip port\n");
        printf("example: ./tcpepoll 192.168.18.132 5085\n\n");
        return -1;
    }

    // 创建服务端用于监听的listenfd
    int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenfd < 0)   
    {
        perror("socket() failed");
        return -1;
    }

    // 设置listenfd的属性
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, static_cast<socklen_t>(sizeof opt));
    setsockopt(listenfd, SOL_SOCKET, TCP_NODELAY, &opt, static_cast<socklen_t>(sizeof opt));
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, static_cast<socklen_t>(sizeof opt));
    setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, &opt, static_cast<socklen_t>(sizeof opt));

    // 把服务端的listenfd设置为非阻塞的
    setnonblocking(listenfd);

    // 服务端地址、接口设置
    struct sockaddr_in servaddr;                    // 服务端地址的结构体
    servaddr.sin_family = AF_INET;                  // IPv4网络协议的套接字类型
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);  // 服务端用于监听的IP地址
    servaddr.sin_port = htons(atoi(argv[2]));       // 服务端用于监听的端口

    if (bind(listenfd, (struct sockaddr* )&servaddr, sizeof(servaddr)) < 0)
    {
        perror("bind() failed");
        close(listenfd);
        return -1;
    }

    if (listen(listenfd, 128) != 0)
    {
        perror("listen() failed");
        close(listenfd);
        return -1;
    }

    // 创建epoll句柄(红黑树)
    int epollfd = epoll_create(1);

    // 为服务端的listenfd准备读事件
    struct epoll_event ev;              // 声明事件的数据结构
    ev.data.fd = listenfd;              // 指定事件的自定义数据,会随着epoll_wait()返回的事件一并返回
    ev.events = EPOLLIN;                // 让epoll监视listenfd的读事件,采用水平触发
    // 把需要监视的listenfd和它的事件加入epollfd中
    epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);

    // 存放epoll_wait()返回事件的数组
    struct epoll_event evs[10];

    while (true)            // 事件循环
    {
        int infds = epoll_wait(epollfd, evs, 10, -1);       // 等待监视的fd有事件发生

        // 返回失败的情况
        if (infds < 0)
        {
            perror("epoll_wait() failed");
            break;
        }

        // 超时的情况
        if (infds == 0)
        {
            printf("epoll_wait() timeout.\n");
            continue;
        }

        // 如果infds>0,表示有事件发生的fd的数量
        for (int ii = 0; ii < infds; ii++)          // 遍历epoll返回的数组evs
        {
            // 如果是listenfd有事件,表明有新的客户端连上来
            if (evs[ii].data.fd == listenfd)
            {
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                int clientfd = accept(listenfd, (struct sockaddr* )&clientaddr, &len);
                setnonblocking(clientfd);       // 客户端连接的fd必须设置为非阻塞的

                printf("accept client(fd=%d, ip=%s, port=%d) ok.\n", 
                        clientfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
                // 为新客户端连接准备读事件,并添加到epoll中
                ev.data.fd = clientfd;
                ev.events = EPOLLIN|EPOLLET;        // 边缘触发
                epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &ev);
            }
            else        // 如果是客户端连接的fd有事件
            {
                if (evs[ii].events & EPOLLRDHUP)    // 对方已关闭,有些系统检测不到,可以使用EPOLLIN,recv()返回0。
                {
                    printf("client(eventfd=%d) disconnected.\n", evs[ii].data.fd);
                    close(evs[ii].data.fd);         // 关闭客户端的fd
                }
                else if (evs[ii].events & (EPOLLIN | EPOLLPRI)) // 接收缓冲区有数据可读
                {
                    char buffer[1024];
                    while (true)    // 由于使用非阻塞IO,一次读取buffer大小数据,知道全部的数据读取完毕
                    {
                        bzero(&buffer, sizeof(buffer));
                        ssize_t nread = read(evs[ii].data.fd, buffer, sizeof(buffer));
                        if (nread > 0)      // 成功的读取到了数据。
                        {
                            // 把接收到的报文内容原封不动的发回去
                            printf("recv(eventfd=%d):%s\n", evs[ii].data.fd, buffer);
                            send(evs[ii].data.fd, buffer, strlen(buffer), 0);
                        }
                        else if (nread == -1 && errno == EINTR) // 读取数据的时候被信号中断,继续读取。
                        {
                            continue;
                        }
                        else if (nread == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))  // 全部的数据已读取完毕。
                        {
                            break;
                        }
                        else if (nread == 0)            // 客户端连接已断开。
                        {
                            printf("client(eventfd=%d) disconnected.\n", evs[ii].data.fd);
                            close(evs[ii].data.fd);     // 关闭客户端的fd
                            break;
                        }
                    }
                }
                else if (evs[ii].events & EPOLLOUT)             // 有数据需要写
                {

                }
                else                                            // 其它事件,都视为错误。
                {
                    printf("client(eventfd=%d) error.\n", evs[ii].data.fd);
                    close(evs[ii].data.fd);                     // 关闭客户端的fd
                }
            }
        }
    }

    return 0;
}

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        printf("usage: ./client ip port\n");
        printf("example: ./client 192.168.18.132 5085\n\n");
        return -1;
    }

    int sockfd;
    struct sockaddr_in servaddr;
    char buf[1024];

    if ((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) 
    { 
        printf("socket() failed.\n"); 
        return -1; 
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);
    servaddr.sin_port = htons(atoi(argv[2]));

    if (connect(sockfd, (struct sockaddr* )&servaddr, sizeof(servaddr)) != 0)
    {
        printf("connect(%s:%s) failed.\n", argv[1], argv[2]);
        close(sockfd);
        return -1;
    }

    printf("connect ok.\n");

    for (int ii = 0; ii < 200000; ii++)
    {
        // 从命令行输入内容
        memset(buf, 0, sizeof(buf));
        printf("please input: ");
        scanf("%s", buf);
        /*
        发送数据:在发送数据时,通常是通过 send 函数发送字符数组(或字符串)。
        strlen(buf) 函数用于获取字符串的长度,因为在 C/C++ 中字符串以 null 结尾,
        所以 strlen 函数会计算从指定地址开始到 null 终止符的字符数。
        这样,通过使用 strlen(buf) 作为发送数据的长度,你确保了只发送了实际有效的数据,而不是整个缓冲区的大小。
        */
        if (send(sockfd, buf, strlen(buf), 0) <= 0) // 把命令行输入的内容发送给服务端。
        {
            printf("write() failed.\n");
            close(sockfd);
            return -1;
        }
        /*
        接收数据:在接收数据时,使用 sizeof(buf) 是为了确保接收缓冲区足够大,
        可以容纳从服务器端发送过来的任何数据。sizeof(buf) 返回的是 buf 数组的大小,
        这样可以保证接收缓冲区足够大,不会发生缓冲区溢出的情况。
        */
        memset(buf, 0, sizeof(buf));
        if (recv(sockfd, buf, sizeof(buf), 0) <= 0)
        {
            printf("read() failed.\n");
            close(sockfd);
            return -1;
        }

        printf("recv:%s\n",buf);
    }

    return 0;
}

makefile文件

all:client tcpepoll

client:client.cpp
	g++ -g -o client client.cpp
tcpepoll:tcpepoll.cpp
	g++ -g -o tcpepoll tcpepoll.cpp

clean:
	rm -f client tcpepoll
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值