<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 王调科 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 王调科 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 19:37:13 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/wang-tiao-ke/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 用 C 语言来实现一个简单的网络客户端 ]]>
                </title>
                <description>
                    <![CDATA[ 当输入网址敲下回车的那一刻浏览器为我们做了什么呢？不妨今天就来看看用 C 语言怎么做到下载一个 HTML 页面的。 当我们在浏览器输入网址按下回车，浏览器向服务器发送一些数据。 GET / HTTP/1.1 Host: localhost 譬如说上面这样的，服务器端的程序接收到了之后就会做出一些回应，有可能是 HTML 页面，或者说其他的数据。 那怎么用 C 语言来写个程序发送这个数据呢？在 Unix 里面一切都是文件，每个进程都有一张表来记录打开的文件，这张表记录了文件的指针和一个数字。 那其实向网络的另一端发送数据就是往某个文件里头写数据。我们用 C 语言往文件里头写数据读数据就行了咯。我们怎么知道要往什么地方写，从什么地方读呢？ getaddrinfo() 获取域名的地址 既然要向服务器发送数据，得需要知道服务器的地址。我们需要知道 IP 地址啦，端口，协议什么的。到了今天这些当然不需要手动来填了。我们有一个函数  getaddrinfo()  ，它会帮我们填好IP，端口那些参数。 #include <sys/types.h> #include <sys/so ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/c-simple-network-client/</link>
                <guid isPermaLink="false">5e9ac078db4be8080eb70989</guid>
                
                    <category>
                        <![CDATA[ C语言 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 浏览器 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 客户端 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 王调科 ]]>
                </dc:creator>
                <pubDate>Wed, 09 Sep 2020 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/04/0_ZoBttIsLXw2VVC4o.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>当输入网址敲下回车的那一刻浏览器为我们做了什么呢？不妨今天就来看看用 C 语言怎么做到下载一个 HTML 页面的。</p>
<p>当我们在浏览器输入网址按下回车，浏览器向服务器发送一些数据。</p>
<pre><code>GET / HTTP/1.1
Host: localhost
</code></pre>
<p>譬如说上面这样的，服务器端的程序接收到了之后就会做出一些回应，有可能是 HTML 页面，或者说其他的数据。</p>
<p>那怎么用 C 语言来写个程序发送这个数据呢？在 Unix 里面一切都是文件，每个进程都有一张表来记录打开的文件，这张表记录了文件的指针和一个数字。</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/1-2.png" alt="descriptor table from &quot;head first c" width="1542" height="802" loading="lazy"></p>
<p>那其实向网络的另一端发送数据就是往某个文件里头写数据。我们用 C 语言往文件里头写数据读数据就行了咯。我们怎么知道要往什么地方写，从什么地方读呢？</p>
<h2 id="getaddrinfo">getaddrinfo() 获取域名的地址</h2>
<p>既然要向服务器发送数据，得需要知道服务器的地址。我们需要知道 IP 地址啦，端口，协议什么的。到了今天这些当然不需要手动来填了。我们有一个函数 <code>getaddrinfo()</code> ，它会帮我们填好IP，端口那些参数。</p>
<pre><code>#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;

int
getaddrinfo(const char *hostname,           // e.g. "www.example.com" or IP
            const char *servname,           // e.g. "http" or port number
            const struct addrinfo *hints, 
            struct addrinfo **res);
</code></pre>
<p>后面两个参数一个叫做 hints，一个叫做 res。顾名思义，我们要给它一点提示，暗示它应该怎么去拿到地址信息。然后会把回应的 response 放到 res 里面去。</p>
<p>不妨先看一个 showip 的例子。这个程序根据域名，去查询这个域名对应的 IP 地址是多少，然后我们把 IP 地址输出。当然计算机里面的东西都是二进制的，我们需要调用一些函数来帮我们把数据转换成需要的样子。函数的用法都可以用 man 命令去查询。</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;
#include &lt;string.h&gt;
#include &lt;arpa/inet.h&gt;

int main(int argc, char const *argv[])
{
    struct addrinfo hints, *res;

    memset(&amp;hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    int status = getaddrinfo("www.example.com", "http", &amp;hints, &amp;res);

    if (status != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        return 1;
    }

    char ipstr[INET6_ADDRSTRLEN];
    for (struct addrinfo *p = res; p != NULL; p = p-&gt;ai_next) {
        void *addr = NULL;
        if (p-&gt;ai_family == AF_INET) {
            struct sockaddr_in *sa = (struct sockaddr_in *)p-&gt;ai_addr;
            addr = &amp;(sa-&gt;sin_addr);
        } else {
            struct sockaddr_in6 *sa = (struct sockaddr_in6 *)p-&gt;ai_addr;
            addr = &amp;(sa-&gt;sin6_addr);
        }
        inet_ntop(p-&gt;ai_family, addr, ipstr, INET6_ADDRSTRLEN);
        printf("%s\n", ipstr);
    }
    freeaddrinfo(res);
    return 0;
}

</code></pre>
<p>getaddrinfo() 会把一个包含地址信息的链表交给 res，res 是链表的头。然后我们可以写一个循环去遍历链表，输出得到的 IP 地址。我们的重点不是这个，重点是 getaddrinfo() 是个好帮手，能让我们不必再手动填结构的数据了。</p>
<h2 id="socket">socket() 获取文件描述符</h2>
<p>socket 中文叫做套接字，在 Unix/Linux 里头其实就是一个文件描述符（file descriptor)，而所谓的文件描述符就是一个数字。套接字是双向的数据流，你可以往里头写数据，也可以从这里读数据。</p>
<pre><code>#include &lt;sys/socket.h&gt;

int
socket(int domain, int type, int protocol);
</code></pre>
<p>我们用 getaddrinfo() 得到的 res 来填这三个参数就好了。譬如可能是这样的。</p>
<pre><code>int s;
struct addrinfo hints, *res;

getaddrinfo("www.example.com", "http", &amp;hints, &amp;res);

s = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);
</code></pre>
<p>socket() 返回的是一个 int，这个数字就是文件描述符。得到了文件描述符那就能对文件描述符所代表的数据流写数据了。</p>
<h2 id="connect">connect() 把套接字连接到远程端口</h2>
<pre><code>#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;

int
connect(int socket, const struct sockaddr *address, socklen_t address_len);
</code></pre>
<p>把套接字连接到远程端口我们就可以对套接字读数据写数据了。</p>
<pre><code>struct addrinfo hints, *res;
int sockfd;

// 首先用 getaddrinfo() 加载地址的结构

memset(&amp;hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo("www.example.com", "http", &amp;hints, &amp;res);

// 套接字

sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);

// 连接!

connect(sockfd, res-&gt;ai_addr, res-&gt;ai_addrlen);
</code></pre>
<p>connect() 第一个参数是 socket() 返回的那个文件描述符，然后后面的 address 和 address_len 用 res 里头的成员来填。</p>
<h2 id="sendrecv">send() 和 recv()</h2>
<p>有了 socket 就要用 send() 和 recv() 来发送数据和接收数据了。socket 是一个文件描述符，既然是一个文件描述符那为什么不直接用 read() 和 write() 呢？当然可以用，但是 send() 和 recv() 能更好的控制数据传输。</p>
<pre><code>#include &lt;sys/socket.h&gt;

ssize_t
send(int socket, const void *buffer, size_t length, int flags);

ssize_t
recv(int socket, void *buffer, size_t length, int flags);
</code></pre>
<p>连上网络服务器后至少需要发送三样东西</p>
<pre><code>GET / HTTP/1.0
Host: www.example.com

</code></pre>
<p>最后有个空行。那来看看最终的程序的样子吧。</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;

int main(int argc, char const *argv[])
{
    struct addrinfo hints, *res;
    int sockfd, recvd;

    memset(&amp;hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    getaddrinfo("www.example.org", "http", &amp;hints, &amp;res);

    sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);
    connect(sockfd, (struct sockaddr *)res-&gt;ai_addr, res-&gt;ai_addrlen);

    send(sockfd, "GET / HTTP/1.0\r\n"
                 "Host: www.example.com\r\n\r\n", 47, 0);

    char buffer[1024];
    while ((recvd = recv(sockfd, buffer, sizeof(buffer) -1, 0)) != 0) {
        buffer[recvd] = '\0';
        printf("%s", buffer);
    }
    freeaddrinfo(res);
    close(sockfd);
    return 0;
}
</code></pre>
<p>send() 函数的第二个参数就是要发送的数据，每行以 <code>\r\n</code> 结尾，意思就是回车换行。recv() 函数可能不能一次性接收完所有的数据，所以我们应该写个循环来不断的从网络上接收数据。recv() 函数会返回接收了多少个字节的数据，在接收的数据后面加个 <code>\0</code> 让它成为一个 C 语言的字符串，然后用 printf() 输出。</p>
<p>必须记得要检查是否发生错误，这里没处理只是想让这个程序看起来不那么可怕。下面是处理了错误的版本。</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;
#include &lt;string.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;

int main(int argc, char const *argv[])
{
    struct addrinfo hints, *p, *res;
    int sockfd, recvd, status;

    memset(&amp;hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo("www.example.org", "http", &amp;hints, &amp;res)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    for (p = res; p != NULL; p = p-&gt;ai_next) {
        sockfd = socket(p-&gt;ai_family, p-&gt;ai_socktype, p-&gt;ai_protocol);
        if (sockfd == -1) {
            perror("socket");
            continue;
        }
        if (connect(sockfd, (struct sockaddr *)p-&gt;ai_addr, p-&gt;ai_addrlen) == -1) {
            perror("connect failed. retrying...");
            continue;
        }
        break;
    }
    freeaddrinfo(res);

    if (p == NULL) {
        fprintf(stderr, "failed!");
        exit(1);
    }

    if (send(sockfd, "GET / HTTP/1.0\r\n"
                 "Host: www.example.com\r\n\r\n", 47, 0) == -1) {
        perror("send");
        exit(1);
    }

    char buffer[1024];
    while ((recvd = recv(sockfd, buffer, sizeof(buffer) -1, 0)) != 0) {
        buffer[recvd] = '\0';
        printf("%s", buffer);
    }
    close(sockfd);
    return 0;
}

</code></pre>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
