网络编程套接字

之前说了多路转接,那么今天我来谈谈我对于网络编程套接字的理解。

初识ip地址和端口号
ip地址
ip地址有两个版本:ipv4和ipv6,我在这里介绍ipv4:
1.ip地址是在ip协议中,用来标识网络中不同主机的地址,它分为两部分:
这里写图片描述
2.对于ipv4来说,ip地址是一个4字节32位的整数;
3.人们通常用点分十进制的字符串表示ip地址,比如127.0.0.1(每一个用点分隔的数字代表一字节)。

端口号
可是说,网络通信的本质也是进程间通信,所以虽然ip地址能够把数据发到对端机器上,但还需要有一个标识来区分出这个数据要给那个进程来解析,这个标识就是端口号。

1.端口号是个2字节16为整数,是传输层协议的内容;
2.用来标示一个进程,告诉内核数据要交给哪个进程处理;
3.ip地址+端口号能标识网络上唯一主机的唯一一个进程;
4.一个端口号只能被一个进程占据。

在这里需要注意的一点是,进程id也表示了一个唯一的进程,要把端口号与进程id区分清楚:进程应用于网络才有端口号。

举个例子,我们打10086就相当于是ip地址,而转人工客服就相当于端口号。

初识传输层的两个协议
这两个协议我在后面的文章将要详细的说明,在这里先简单说一下,便于后面实现代码:
UDP协议:无连接,不可靠,面向数据报。
TCP协议:有连接,很可靠,面向字节流。

在这里需要注意面向数据报和面向字节流的区别:
UDP协议是面向数据报的,也就是说读的时候只能整包整包的读,不能读半包,一包半之类的;
而TCP协议是面向字节流的,想读多少就读多少。

通过这些特点,可以看出来TCP明显功能上是优于UDP的,但是UDP有一点就是简单。

网络字节序
内存中多字节数据相对于内存地址有大小端之分;磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分;同样,网络数据流同样有大小端之分。
为了统一,TCP/IP规定,网络数据流应采用大端,即低地址高字节。
具体细节就是,如果发送端是小端,就需要先将数据转为大端再发送;如果发送端是大端,直接发送。

可以调用下面的库函数进行网络字节序和主机字节序的转换:
这里写图片描述
函数名是见名知意的,拿第一个举例:
这里写图片描述
我们在编写套接字时,常常利用htonl函数将端口号的主机序转为网络字节序。

常见的socket接口
这里写图片描述
这些函数具体的介绍,有一个大神写得很好,附上链接:
https://blog.csdn.net/y396397735/article/details/50655363

sockaddr结构
sockaddr和 void* 类似,是为了泛型处理不同的套接,适用于各种底层不同地址格式的网络协议,比如ipv4,ipv6等。
这里写图片描述
如图,不同的网络协议的底层不同;
对于ipv4,地址类型为AF_INET,对于ipv6,地址类型为AF_INET6,只要取得某sockaddr结构体的16为地址类型,不需要知道到底是哪个,就能根据地址类型确定结构体的内容。
在使用socket API的时候,需要强转为sockaddr,这样做增强了程序的通用性。

sockaddr结构
这里写图片描述

sockaddr_in结构
这里写图片描述
基于ipv4编程,使用的正是sockaddr_in,这个结构体主要三部分:地址类型,端口号,ip地址。

地址转换函数
这里写图片描述

基于以上的认知,实现一个简单UDP网络通信:
1.对于UDP,socket的参数使用SOCK_DGRAM;
2.UDP不进行连接,直接使用sendto和recvfrom来收发数据。

服务器:

int main(int argc,char* argv[])
{
    //采用./server [ip] [port]的形式传入ip地址和端口号
    if(argc != 3){
        printf("./server [ip] [port]\n");
        return 1;
    }

    //                 ipv4      UDP
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock < 0){
        perror("socket");
        return 2;
    }

    //构造出地址协议加ip地址加端口号
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(argv[1]);
    local.sin_port = htons(atoi(argv[2]));

    //绑定端口号
    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
        perror("bind");
        return 3;
    }

    while(1){
        char buf[1024];
        struct sockaddr_in client;
        socklen_t len = sizeof(client);

        //UDP不建立连接,直接对数据进行读写
        ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
        if(s > 0){
            buf[s] = 0;
            printf("client> :%s\n",buf);
            sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client));
        }
    }

    return 0;
}

客户端:

int main(int argc,char* argv[])
{
    if(argc != 3){
        printf("./client [ip] [port]\n");
        return 1;
    }

    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock < 0){
        perror("socket");
        return 2;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    //UDP无需建立连接,直接进行数据传输
    while(1){
        char buf[1024] = {0};
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s > 0){
            sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&addr,sizeof(addr));
            ssize_t s2 = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
            if(s2 > 0){
                buf[s2] = 0;
                printf("#> :%s\n",buf);
            }
        }
    }

    return 0;
}

来看看运行效果:
这里写图片描述
这里写图片描述
服务器收到来自客户端的数据,输出并回显给客户端。

编写简单的TCP网络通信
服务器:

int main(int argc,char* argv[])
{
    if(argc != 3){
        printf("./server [ip] [port]\n");
        return 1;
    }

    int listen_sock = socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock < 0){
        perror("listen_sock");
        return 2;
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(argv[1]);
    local.sin_port = htons(atoi(argv[2]));

    if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0){
        perror("bind");
        return 3;
    }

    if(listen(listen_sock,5) < 0){
        perror("listen");
        return 4;
    }

    while(1){
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
        if(new_sock < 0){
            perror("accept");
            return 5;
        }

        while(1){
            char buf[1024] = {0};
            ssize_t s = read(new_sock,buf,sizeof(buf));
            if(s < 0){
                perror("read");
                break;
            }
            else if(s == 0){
                printf("客户端已退出,可以关闭该连接");
                close(new_sock);
                break;
            }
            else{
                printf("client: %s\n",buf);
            }
        }
    }

    return 0;
}

客户端:

int main(int argc,char* argv[])
{
    if(argc != 3){
        printf("./client [ip] [port]\n");
        return 1;
    }

    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        perror("socket");
        return 2;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock,(struct sockaddr*)&addr,sizeof(addr)) < 0){
        perror("connect");
        return 3;
    }

    while(1){
        char buf[1024] = {0};
        ssize_t s = read(0,buf,sizeof(buf));
        if(s > 0){
            ssize_t s2 = write(sock,buf,strlen(buf));
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值