分别用thread-per-connection和IO复用模式实现Netcat的基本功能:从stdin读,写到sockfd;从sockfd读写到stdout.
先看第一种,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复用+阻塞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。 具体的待查询吧。。。