两种网络模式简单实现Netcat

分别用thread-per-connection和IO复用模式实现Netcat的基本功能:从stdin读,写到sockfd;从sockfd读写到stdout.

thread-per-connection

先看第一种,thread-per-connection

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/socket.h>  
#include <netinet/in.h> 
#include <unistd.h>
#include <arpa/inet.h>
#include <error.h>
#include <strings.h>
#include <sys/types.h>
#include <pthread.h>
#include <fcntl.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <linux/tcp.h>
#include <signal.h>
#include <arpa/inet.h>
#define MAX 8192
#define SIZE 10

void* readsock(void *arg)
{
        int *sock = (int *)arg;
        char buf[MAX];
        int nr;
        while ( (nr = recv(*sock, buf, sizeof(buf), 0)) > 0)
        {
                int nw = write(STDOUT_FILENO, buf, nr);
                if (nr < nw)
                {
                        break;
                }
        }
        printf("connection closed by peer.\n");
        exit(0); //should somehow nofity main thread
}

void readstdin(int sockfd)
{
        char buf[MAX];
        int nr;
        while ( (nr = read(STDIN_FILENO, buf, sizeof(buf))) > 0)
        {
                int nw = send(sockfd, buf, nr, 0);
                if (nw < nr)
                {
                        break;
                }
        }
}

void run(int sockfd)
{
        //read from sock to stdout
        pthread_t pid;
        pthread_create(&pid, NULL, readsock, (void*)&sockfd);

        //read from stdin to sockfd
        readstdin(sockfd);

        //close, fixme
        close(sockfd);
        pthread_join(pid,NULL);
}

int main(int argc, char **argv)
{
        //sigpipe
        signal(SIGPIPE, SIG_IGN);
        if (argc < 3)
        {
                printf("Usage:\n  %s hostname port\n  %s -l port\n", argv[0], argv[0]);
                return 0;
        }

        int port = atoi(argv[2]);

        if (strcmp(argv[1], "-l") == 0)
        {
                struct sockaddr_in address;
                bzero(&address, sizeof(address));
                address.sin_family = AF_INET;
                address.sin_addr.s_addr = INADDR_ANY;
                address.sin_port = htons(port);

                int listenfd = socket(AF_INET, SOCK_STREAM, 0);
                assert(listenfd >= 0);

                //address reuse and no nagle
                int reuse = 1;
                setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
                setsockopt(listenfd,  IPPROTO_TCP, TCP_NODELAY, &reuse, sizeof(reuse));

                int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
                assert(ret >= 0);

                ret = listen(listenfd,5);
                assert(ret >= 0);
                //printf("Listening...\n"); 
                struct sockaddr_in client;
                int len = sizeof(client);
                int sockfd = accept(listenfd, (struct sockaddr *)(&client), &len);
                assert(sockfd > 0);

                run(sockfd);
        }
        else
        {
            int sockfd;
            struct sockaddr_in ser;
            bzero(&ser,sizeof(ser));
            sockfd = socket(AF_INET,SOCK_STREAM,0);

            ser.sin_family = AF_INET;
            ser.sin_addr.s_addr = inet_addr(argv[1]);
            ser.sin_port = htons(12345);
            connect(sockfd, (struct sockaddr*)&ser, sizeof(ser));

            run(sockfd);
        }

        return 0;
}

说明三点:
1. 首先一般情况下必做的三件事:(1)忽略SIGPIPE信号,(2)监听地址端口复用(3)关闭Nagle算法(TCP_NODELAY)
2. 程序两个线程,1个线程从stdin读,写到sockfd;一个线程从sockfd读,写到stdout(此线程称为网络线程);
3. run函数的直接close()不正确,稍后再分析,目前先这样
4. 程序还有个问题(自己太懒了),没有错误判断,这样出现错误了,你都不知道是哪的问题。
5. 目前程序只能接受一个客户端,nc好像也是,可以修改支持多个客户端吗?(待续。。。)
6. 程序退出是个问题需要注意,见下分析

退出条件有两个:读sockfd读到0,读stdin读到0;
先看从主线程退出,也是就是函数readstdin(),可以看出是read读到了0,while循环退出,程序执行到close(sockfd),关闭链接,对方也关闭链接,则网络线程read返回0,主线程这里pthread_jion等待其退出,整个程序结束;
再看从网络线程退出,也就是函数readsock(),退出的时候这里用的是exit()(而不是return,可以换成return试试),因为这里需要通知主线程退出,因为主线程正阻塞在read上,若不通知,程序结束不了。

thread-per-connection适用于链接数不多,线程非常廉价的情况下。

另外采用此程序测试带宽

server端:nc -l 12345 | pv -W > /dev/null
client端:nc 127.0.0.1 12345 < /dev/zero

其中nc(可以换成上诉程序)

IO-multiplexing

此程序是IO复用+阻塞IO,这样其实是不对的,会有一篇文章来说这个事;不过此也算是写完了,记着吧。
先看程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/socket.h>  
#include <netinet/in.h> 
#include <unistd.h>
#include <arpa/inet.h>
#include <error.h>
#include <strings.h>
#include <sys/types.h>
#include <pthread.h>
#include <fcntl.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <linux/tcp.h>
#include <signal.h>
#include <arpa/inet.h>
#define MAX 8192
#define SIZE 1024

int setnonblocking(int fd) 
{
    int old = fcntl(fd, F_GETFL);
    int new = old | O_NONBLOCK;
    if (fcntl(fd, F_SETFL, new) < 0)
    {   
            perror("FCNTL : ");
            return -1; 
    }   

    return old;
}

void addfd(int epollfd, int fd, bool enable_et)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN;
    if (enable_et)
    {
        ev.events |= EPOLLET;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
    //setnonblocking(fd);
}

void run(int sockfd)
{
    struct epoll_event events[SIZE];
    int epollfd = epoll_create1(0);
    assert(epollfd != -1);
    addfd(epollfd, sockfd, false);
    addfd(epollfd, STDIN_FILENO, false);

    char buf[MAX];
    bool done = false;
    while (!done)
    {
        int ret = epoll_wait(epollfd, events, SIZE, -1);
        for (int i=0; i<ret; ++i)
        {
            int fd = events[i].data.fd;
            if (fd == STDIN_FILENO)
            {
                int nr = read(STDIN_FILENO, buf, sizeof(buf));
                if (nr > 0)
                    send(sockfd, buf, nr, 0);
                else
                {
                    //fix me 
                    close(fd);
                    //shutdown(write)
                    //unrigister stdin 
                }
            }
            else if (fd == sockfd)
            {
                int nr = recv(sockfd, buf, sizeof(buf), 0);
                if (nr > 0)
                    write(STDOUT_FILENO, buf, nr);
                else
                    done = true;
            }
        }
    }
}

int main(int argc, char **argv)
{
        //sigpipe
    signal(SIGPIPE, SIG_IGN);
    if (argc < 3)
    {
        printf("Usage:\n  %s hostname port\n  %s -l port\n", argv[0], argv[0]);
        return 0;
    }

    int port = atoi(argv[2]);

    if (strcmp(argv[1], "-l") == 0)
    {
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = INADDR_ANY;
        address.sin_port = htons(port);

        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        assert(listenfd >= 0);

        //address reuse and no nagle
        int reuse = 1;
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
        setsockopt(listenfd,  IPPROTO_TCP, TCP_NODELAY, &reuse, sizeof(reuse));

        int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
        assert(ret >= 0);

        ret = listen(listenfd,5);
        assert(ret >= 0);
        //printf("Listening...\n"); 

        struct sockaddr_in cli;
        int len = sizeof(cli);
        int sockfd = accept(listenfd, (struct sockaddr*)&cli, &len);    
        run(sockfd);
    }
    else
    {
        int sockfd;
        struct sockaddr_in ser;
        bzero(&ser,sizeof(ser));
        sockfd = socket(AF_INET,SOCK_STREAM,0);

        //server

        ser.sin_family = AF_INET;
        ser.sin_addr.s_addr = inet_addr(argv[1]);
        ser.sin_port = htons(12345);
        connect(sockfd, (struct sockaddr*)&ser, sizeof(ser));

        run(sockfd);         
    }

    return 0;
}

上诉程序按理也可以和thread-per-connection一样用来测试带宽
可是,如下测试出现问题

server端:nc -l 12345 | pv -W /dev/null
client端:./a,out 127.0.0.1 12345 < /dev/zero

查找了很多原因(后悔偷懒没有加上错误判断),后来用strace来看,发现addfd()函数中epoll_ctl()出错误,如下Operation not permitted,网上说的是epoll_ctl不支持文件fd。
具体的待查询吧。。。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值