网络编程基础知识


现代CPU的累加器一次都能装载4字节(32位机),即一个整数。那么这4字节在内存中的排列顺序将影响它被累加器装载成的整数值,这就是字节序,大端字节序是指一个整数的高位字节(21-23)存储在内存的低地址出,低位字节(0-7)存储在内存高的地址处。
代码判断:

#include <iostream>
#include <stdio.h>
using namespace std;

void byteorder()
{
    union
    {
        short value;
        char union_bytes[sizeof(short)];
    }test;
    test.value = 0x0102;
    if((test.union_bytes[0] == 1) && (test.union_bytes[1] == 2))
    {
        printf("big endian\n");
    }
    else if((test.union_bytes[0] == 2) && (test.union_bytes[1] == 1))
    {
        printf("little endian\n");
    }
    else
    {
        printf("unknown.../n");
    }
}

int main()
{
    byteorder();
}

主机字节序与网络字节序的转换函数:
在这里插入图片描述
通用socket地址:
在这里插入图片描述
sa_family成员是地址族类型sa_family_t的变量,地址族类型通常与协议族类型对应,常见协议族(protocol family也成domain)和对应的地址族如下
在这里插入图片描述

在这里插入图片描述
sa_data成员用于存放socket地址值,不同协议族的地址值具有不同的含义和长度如下
在这里插入图片描述
由于sa_data无法完全容纳多数协议族地址值,因此定义了下面这个新的通用socket地址结构体。
在这里插入图片描述
这个结构体占128字节,能提供足够大的空间用于存放地址值。

专用socket地址
UNIX专用socket地址结构体:
在这里插入图片描述
在这里插入图片描述
IP地址转换函数:
在这里插入图片描述
inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址,它失败时返回INADDR_NONE。
inet_aton函数完成和inet_addr同样的功能,但是将转化结果存储于参数inp指向的地址结构中。它成功返回1,失败返回0。
inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。但需要注意的是,该函数内部用一个静态存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的。

创建socket
在unix/linux中所有的东西都是文件,socket也不例外,它是可读,可写,可控制,可关闭的文件描述符。
在这里插入图片描述
domain代表协议族,type参数指定服务类型例如SOCK_STREAM(流服务)和SOCK_DGRAM(数据报),自linux内接版本2.6.17起,type参数可以接受上述服务类型与SOCK_NONBLOCK和SOCK_CLOEXEC相与的值,它们分别表示将新创建的socket设为非阻塞的,以及用fork调用创建子进程时在子进程中关闭该socket。
protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议。几乎在所有的情况下,我们都用该把它设置为0,表示使用默认的协议。
socket系统调用成功返回一个socket文件描述符,失败返回-2并设置errno。

在这里插入图片描述
bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数值出socket地址的长度,

bind成功时返回0,失败时返回-1并设置errno,其中两种常见errno是EACCES和EADDRINUSE,
EACCES,被绑定的地址是受保护的,仅超级用户能够访问比如,普通用户将socket绑定到0-1023上时,bind将返回EACCES错误。
EADDRINUSE,被绑定的地址正在使用中,比如讲socket绑定到一个TIME_WAIT状态的socket地址,这种情况的地址更确切的说是端口号。

在这里插入图片描述
sockfd参数指定被监听的socket,backlog参数提示内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不受理新的客户连接,客户端也将接受到ECONNREFUSED错误消息。这个是全连接队列和半连接队列的问题,可以参考:
https://blog.csdn.net/qq_43812167/article/details/113837358

在这里插入图片描述
sockfd参数是执行过listen系统调用的监听socket。addr参数用来获取被接收连接的源端socket,该socket的地址长度由addrlen参数指出。accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。accept失败时返回-1,并设置errno。
如果监听队列中处于ESTABLISHED状态的连接对应的客户端出现网络异常或者提前退出,那么服务器执行accept调用是否成功?

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(int argc, char* argv[])
{
    if(argc <= 2)
    {
        printf("usage: %s ip_address port_number backlog\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sock, 5);
    assert(ret != -1);

    sleep(20);
    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
    if(connfd < 0)
    {
        printf("errno is: %d\n", errno);
    }
    else
    {
        char remote[INET_ADDRSTRLEN];
        printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client.sin_port));
        close(connfd);
    }
    sleep(100);
    close(sock);
    return 0;
}

测试过程参考linux高性能服务器80页,结果,accept只是从监听队列中取出连接,无论连接处于何种状态。

关闭连接
关闭连接实际上就是关闭连接对应的socket,关闭普通文件描述符的系统调用来完成:
在这里插入图片描述
close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1,只有当fd的引用计数为0时,才真正关闭连接,多进程程序中,一次fork系统调用默认将使父进程和子进程中都对该socket的引用计数加1,因此我们必须在父进程和子进程都对该socket执行close调用才将连接关闭。
如果要立即终止连接可以使用以下系统调用
在这里插入图片描述
在这里插入图片描述
shotdowm能够分别关闭socket上的读或者写,或者都关闭,而close在关闭连接时只能将socket上的读和写同时关闭

tcp数据读写

在这里插入图片描述
在这里插入图片描述
recv成功时返回实际读取到的数据长度,出错时返回-1并设置errno,send成功时返回实际写入的数据长度,失败时返回-1并设置errno。
flag参数为数据收发提供了额外的控制,他可以取上表所示选项中的一个或几个的逻辑或。

udp数据读写

在这里插入图片描述
recvfrom/sendto系统调用也可以用于面向连接(stream)的socket的数据读写,只需要把最后两个参数都设置为NULL以忽略发送端/接收端的socket地址,因为tcp双方已经知道了socket地址。

通用数据读写

在这里插入图片描述
上面的socket系统调用接口既可以用于tcp数据流,也能用于udp数据报。msghdr结构体定义如下。
在这里插入图片描述
msg_iov成员是iovec结构体类型的指针,iovec结构体的定义如下
在这里插入图片描述
msg_flags成员无需设定,它会复制recvmsg/sendmsg的flags参数的内容以影响数据读写过程,recvmsg/sendmsg的flags参数以及返回值的含义均与send/recv的flags参数及返回值相同。

获取本端的socket地址和远端的socket地址:
在这里插入图片描述
如果实际socket地址的长度大于address锁指的内存区的大小,那么socket地址将被截断。getname成功时返回0,失败返回-1,并设置errno。

socket选项

在这里插入图片描述
在这里插入图片描述
有部分socket选项只能在listen系统调用前针对监听socket设置才有效,对于客户端而言,这些socket选项应该在调用connect函数之前设置,因为connect调用成功返回之后,tcp三次握手已完成。

SO_REUSEADDR选项
经过setsockopt的设置之后,即使sock处于TIME_WAIT状态,与之绑定的socket地址也可以立即被重用,此外我们也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收被关闭的socket。使得tcp连接根本就不进入TIME_WAIT状态,进而允许应用程序立即重用本地socket地址。

SO_RECVBUF和SO_SNDBUF选项
SO_RECVBUF和SO_SNDBUF选项分别表示tcp接收缓存区大小和发送缓存区的大小

SO_RECVLOWAT和SO_SNDLOWAT选项
SO_RECVLOWAT和SO_SNDLOWAT选分别表示tcp接收缓冲区和发送缓冲区的低水平位,它们一般被I/O复用系统调用用来判断socket是否可读或可写,默认情况下,tcp接收缓冲区的低水平位标记和tcp发送缓冲区的低水平位标记均为1字节

gethostbyname和gethostbyaddr
gethostbyname函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息,如果没有找到,再去访问DNS服务器
在这里插入图片描述
hostent结构体的定义如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值