TCP/IP网络编程-4~5章学习笔记

博客介绍了C语言网络编程相关知识。包含stdin、stdout、stderr等前置小知识点,通过echo小案例进行编码实践。还讲解了gethostbyname函数获取IP地址、setsockopt设置socket选项,以及Nagle算法,说明了其原理、使用场景和禁用方法。

前置C语言小知识点

stdin,stdout,stderr

名称全称含义
stdinstandard input标准输入流
stdoutstandard out标准输出流
stderrstandard error标准错误输出

我们来看下面几个函数

#include <stdio.h>

#define BUF_SIZE 5

int main(int argc, char *argv[])
{
    char message[BUF_SIZE];

    fputs("请向输入流一个字符串:", stdout); //printf
    fgets(message, BUF_SIZE, stdin); //scanf
    fputs(message,stderr); //output: message
}
复制代码

上面我们使用到了stdout、 stdin, 并且最后还写入到 stderr流, 输出到了控制台.

stdout和stderr都能输出到控制台, 除了语义上区别外, stderr是没有缓冲的,他立即输出,而stdout默认是行缓冲,也就是它遇到‘\n’,才向外输出内容,如果你想stdout也实时输出内容,那就在输出语句后加上fflush(stdout),这样就能达到实时输出的效果

fputs、fgets指定到流的操作(文件流), 对应的直接输入输出还有 puts、gets,这里不再推荐使用puts、gets了, 他们之间也有区别

gets()丢弃输入中的换行符,但是puts()在输出中添加换行符。另一方面,fgets()保留输入中的换行符,fputs()不在输出中添加换行符,因此,puts()应与gets()配对使用,fputs()应与fgets()配对使用。

编码实践 echo 小案例

echo_server.c

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define BUF_SIZE 5

int main(int argc, char *argv[])
{
    char message[BUF_SIZE];
    int str_len, i;

    struct sockaddr_in serv_addr, clnt_addr;

    int serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
        printf("socket() error");
        exit(1);
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(9600);

    if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        printf("bind() error");
        exit(1);
    }

    if (listen(serv_sock, 5) == 1)
    {
        printf("listen() error");
        exit(1);
    }

    int clnt_addr_sz = sizeof(clnt_addr);
    for (i = 0; i < 5; i++)
    {
        int clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_sz);
        if (clnt_sock == -1)
        {
            printf("accept() error");
            exit(1);
        }

        while (str_len = read(clnt_sock, message, BUF_SIZE) > 0)
        {
            write(clnt_sock, message, str_len);
        }

        close(clnt_sock);
    }

    close(serv_sock);
    return 0;
}
复制代码

echo_client.c

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define BUF_SIZE 5

int main(int argc, char *argv[])
{
    char message[BUF_SIZE];
    int str_len, i;

    struct sockaddr_in serv_addr, clnt_addr;

    int serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
        printf("socket() error");
        exit(1);
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(9600);

    if (connect(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        printf("connect() error");
        exit(1);
    }

    while (1)
    {
        fputs("请输入您的信息,按Q键退出\n", stdout);
        fgets(message, 1024, stdin);

        //因为fgets会保留输入中换行符,故判断加\n
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
        {
            break;
        }
        
        write(serv_sock, message, sizeof(message));
        read(serv_sock, message, BUF_SIZE - 1);
        printf("Message from server: %s\n", message);
    }

    close(serv_sock);
    return 0;
}
复制代码

上面代码简单完成了 echo 的操作(我们输入什么,服务端返回什么)

我们发现当数据超过5个字符时候(\n也默认为一个字符), 将会截断发送, 我们可以使用下面方式。

str_len = write(serv_sock, message, strlen(message));

recv_len = 0;
while (recv_len < str_len)
{
    recv_cnt = read(serv_sock, &message[recv_len], BUF_SIZE - 1);
    if (recv_cnt == -1)
    {
        printf("read() error");
        exit(1);
    }
    recv_len += recv_cnt;
}
复制代码

上面将是循环接收数据, 直到接收完毕退出循环体

gethostbyname 函数 根据域名获取IP地址

#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    struct hostent *host;

    host = gethostbyname("www.xueba100.com");

    printf("h_name=%s\n", host->h_name);
    printf("h_addrtype=%d\n", host->h_addrtype);

    int i;
    for (i = 0; host->h_addr_list[i]; i++)
    {   
        //将IP指针转换为 in_addr 结构体, 再调用inet_ntoa转换为字符串形式
        printf("Ip addr: %s\n", inet_ntoa(*(struct in_addr *)host->h_addr_list[i]));
    }
}
复制代码

setsockopt 设置socket选项

这里举例说明 设置 SO_REUSEADDR 选项

当我们主动关闭服务端时候, 将会产生TIME_OUT, 这样会导致端口地址无法重用,规范中规定等待 2MSL 时间才可以使用。 我们可以使用 setsockopt 设置地址重用。

socklen_t option;
int optlen = sizeof(option);
option = 1;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&option, optlen);
复制代码

与之对应的 getscokopt 函数, 获取选项

Nagle 算法

只有收到前一数据的 ACK 消息时, Nagle 算法才发送下一数据。

TCP 套接字默认使用的 Nagle 算法交换数据, 因此最大限度地进行缓冲, 直到收到 ACK。

如果不使用 Nagle 无需等待 ACK 的前提下连续传输, 大大提高传输速度.

使用 Nagle 交互图

把图画残了。。。

当我们传输大文件, 注重传输速度时候可以禁用 Nagle 算法, 如果考虑到传输内容很小, 头部信息就有可能几十个字节, 可以使用 Nagle 算法, 减少网络传输次数。

禁用 Nagle 算法

socklen_t option;
int optlen = sizeof(option);
option = 1;
setsockopt(serv_sock, IPPROTO_TCP, TCP_NODELAY, (void *)&option, optlen);
复制代码

转载于:https://juejin.im/post/5ccc3d80f265da038364d55c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值