分别用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。
具体的待查询吧。。。