笨办法学C 练习45:一个简单的TCP/IP客户端
练习45:一个简单的TCP/IP客户端
原文:Exercise 45: A Simple TCP/IP Client
译者:飞龙
我打算使用RingBuffer来创建一个非常简单的小型网络测试工具,叫做netclient。为此我需要向Makefile添加一些工具,来处理bin/目录下的小程序。
扩展Makefile
首先,为程序添加一些变量,就像单元测试的TESTS和TEST_SRC变量:
PROGRAMS_SRC=$(wildcard bin/*.c)PROGRAMS=$(patsubst %.c,%,$(PROGRAMS_SRC))
之后你可能想要添加PROGRAMS到所有目标中:
all: $(TARGET) $(SO_TARGET) tests $(PROGRAMS)
之后在clean目标中向rm那一行添加PROGRAMS:
rm -rf build $(OBJECTS) $(TESTS) $(PROGRAMS)
最后你还需要在最后添加一个目标来构建它们:
$(PROGRAMS): CFLAGS += $(TARGET)
做了这些修改你就能够将.c文件扔到bin中,并且编译它们以及为其链接库文件,就像测试那样。
netclient 代码
netclient的代码是这样的:
# undef NDEBUG# include # include # include # include # include # include # include # include # include # include # include # include struct tagbstring NL = bsStatic("\n");struct tagbstring CRLF = bsStatic("\r\n");int nonblock(int fd) { int flags = fcntl(fd, F_GETFL, 0); check(flags >= 0, "Invalid flags on nonblock."); int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK); check(rc == 0, "Can't set nonblocking."); return 0;error: return -1;}int client_connect(char *host, char *port){ int rc = 0; struct addrinfo *addr = NULL; rc = getaddrinfo(host, port, NULL, &addr); check(rc == 0, "Failed to lookup %s:%s", host, port); int sock = socket(AF_INET, SOCK_STREAM, 0); check(sock >= 0, "Cannot create a socket."); rc = connect(sock, addr->ai_addr, addr->ai_addrlen); check(rc == 0, "Connect failed."); rc = nonblock(sock); check(rc == 0, "Can't set nonblocking."); freeaddrinfo(addr); return sock;error: freeaddrinfo(addr); return -1;}int read_some(RingBuffer *buffer, int fd, int is_socket){ int rc = 0; if(RingBuffer_available_data(buffer) == 0) { buffer->start = buffer->end = 0; } if(is_socket) { rc = recv(fd, RingBuffer_starts_at(buffer), RingBuffer_available_space(buffer), 0); } else { rc = read(fd, RingBuffer_starts_at(buffer), RingBuffer_available_space(buffer)); } check(rc >= 0, "Failed to read from fd: %d", fd); RingBuffer_commit_write(buffer, rc); return rc;error: return -1;}int write_some(RingBuffer *buffer, int fd, int is_socket){ int rc = 0; bstring data = RingBuffer_get_all(buffer); check(data != NULL, "Failed to get from the buffer."); check(bfindreplace(data, &NL, &CRLF, 0) == BSTR_OK, "Failed to replace NL."); if(is_socket) { rc = send(fd, bdata(data), blength(data), 0); } else { rc = write(fd, bdata(data), blength(data)); } check(rc == blength(data), "Failed to write everything to fd: %d.", fd); bdestroy(data); return rc;error: return -1;}int main(int argc, char *argv[]){ fd_set allreads; fd_set readmask; int socket = 0; int rc = 0; RingBuffer *in_rb = RingBuffer_create(1024 * 10); RingBuffer *sock_rb = RingBuffer_create(1024 * 10); check(argc == 3, "USAGE: netclient host port"); socket = client_connect(argv[1], argv[2]); check(socket >= 0, "connect to %s:%s failed.", argv[1], argv[2]); FD_ZERO(&allreads); FD_SET(socket, &allreads); FD_SET(0, &allreads); while(1) { readmask = allreads; rc = select(socket + 1, &readmask, NULL, NULL, NULL); check(rc >= 0, "select failed."); if(FD_ISSET(0, &readmask)) { rc = read_some(in_rb, 0, 0); check_debug(rc != -1, "Failed to read from stdin."); } if(FD_ISSET(socket, &readmask)) { rc = read_some(sock_rb, socket, 0); check_debug(rc != -1, "Failed to read from socket."); } while(!RingBuffer_empty(sock_rb)) { rc = write_some(sock_rb, 1, 0); check_debug(rc != -1, "Failed to write to stdout."); } while(!RingBuffer_empty(in_rb)) { rc = write_some(in_rb, socket, 1); check_debug(rc != -1, "Failed to write to socket."); } } return 0;error: return -1;}
代码中使用了select来处理stdin(文件描述符0)和用于和服务器交互的socket中的事件。它使用了RingBuffer来储存和复制数据,并且你可以认为read_some和write_some函数都是RingBuffer中相似函数的原型。
在这一小段代码中,可能有一些你并不知道的网络函数。当你碰到不知道的函数时,在手册页上查询它来确保你理解了它。这一小段代码可能需要让你研究用于小型服务器编程的所有C语言API。
你会看到什么
如果你完成了所有构建,测试的最快方式就是看看你能否从learncodethehardway.org上得到一个特殊的文件:
$$ ./bin/netclient learncodethehardway.org 80GET /ex45.txt HTTP/1.1Host: learncodethehardway.orgHTTP/1.1 200 OKDate: Fri, 27 Apr 2012 00:41:25 GMTContent-Type: text/plainContent-Length: 41Last-Modified: Fri, 27 Apr 2012 00:42:11 GMTETag: 4f99eb63-29Server: Mongrel2/1.7.5Learn C The Hard Way, Exercise 45 works.^C$
这里我所做的事情是键入创建/ex45.txt的HTTP请求所需的语法,在Host:请求航之后,按下ENTER键来输入空行。接着我获取相应,包括响应头和内容。最后我按下CTRL-C来退出。
如何使它崩溃
这段代码肯定含有bug,但是当前在本书的草稿中,我会继续完成它。与此同时,尝试分析代码,并且用其它服务器来击溃它。一种叫做netcat的工具可以用于建立这种服务器。另一种方法就是使用Python或Ruby之类的语言创建一个简单的“垃圾服务器”,来产生垃圾数据,随机关闭连接,或者其它异常行为。
如果你找到了bug,在评论中报告它们,我会修复它。
附加题
像我提到的那样,这里面有一些你不知道的函数,去查询他们。实际上,即使你知道它们也要查询。
在valgrind下运行它来寻找错误。
为函数添加各种防御性编程检查,来改进它们。
使用getopt函数,运行用户提供选项来防止将\n转换为\r\n。这仅仅用于需要处理行尾的协议例如HTTP。有时你可能不想执行转换,所以要给用户一个选择。
关键字:c, lxthw
版权声明
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!