日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

笨办法学C 练习45:一个简单的TCP/IP客户端

發布時間:2023/11/29 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 笨办法学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 <stdlib.h> #include <sys/select.h> #include <stdio.h> #include <lcthw/ringbuffer.h> #include <lcthw/dbg.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/uio.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <fcntl.h>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 80 GET /ex45.txt HTTP/1.1 Host: learncodethehardway.orgHTTP/1.1 200 OK Date: Fri, 27 Apr 2012 00:41:25 GMT Content-Type: text/plain Content-Length: 41 Last-Modified: Fri, 27 Apr 2012 00:42:11 GMT ETag: 4f99eb63-29 Server: 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 练习45:一个简单的TCP/IP客户端的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。