前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >网络编程套接字(一)

网络编程套接字(一)

作者头像
二肥是只大懒蓝猫
发布2023-04-09 10:02:55
7120
发布2023-04-09 10:02:55
举报
文章被收录于专栏:热爱C嘎嘎

学习任务: 我们先来认识端口号,区分好主机IP和端口号的区别,以及涉及到进程PID和端口号的区别。 然后简单认识一下TCP协议和UDP协议,这两个协议都是传输层的。接着了解什么是网络字节序,它有什么作用。然后是网络编程的一些接口。最后写代码简单实践一下。

目录

1、认识端口号,区分IP/port,PID/port

2、认识TCP协议,认识UDP协议

3、认识网络字节序

4、socket编程接口

5、代码示例


1、认识端口号,区分IP/port,PID/port

IP地址(公网IP)是用来唯一标识互联网中的一台主机,一台主机一个IP。而IP分源IP和目的IP,源IP和目的IP对一个报文来讲,是起从哪里来,到哪里去的作用,其最大的意义是指导报文该如何进行路径的选择,而路径中,每一个“站点”就是MAC地址的变化。

认识端口号port

数据从计算机A到达计算机B,并不是真正的目的,而是到达计算机B的某一个进程,提供数据处理的服务,才是网络传输数据最终的目的。

数据本身并不是由计算机产生的,而是由人,即用户通过特定的客户端等等输入进去的,因此本质上,所有的网络通信,站在人的角度上,就是人与人之间的通信,这是一个比较好的理解方向,站在计算机角度上,是进程间通信!只不过通信的进程不在一台计算机上。就比如抖音的app客户端,它是一个进程,抖音的服务器,也是一个进程。我们通过抖音客户端达到网络通信,在抖音的服务器上获取信息,便是进程间通信。

而IP地址,仅仅是解决了两台物理机器之间的相互通信的识别问题,我们还要解决是在这两台计算机之间的进程间的通信,就是怎么知道计算机A发出的信息是要传给计算机B中的某个进程呢?这就需要端口号了!

因此,端口号的作用是唯一标识一台机器上的唯一一个进程!通过IP+端口号port,就能够标识互联网中的唯一一个进程!

我们可以将整个网络看成是一个大的OS,所有的网络行为,几乎都是在这一个大的OS进行进程间通信!

既然说端口号port是进程的一个身份,那么进程的PID按理论上来说,也能通过PID来进行网络上的进程间通信,那么为什么还需要一个port呢?

区分IP/PORT,PID/PORT

上面我们已经很清楚了,IP的标识物理机器的,port是标识进程的。而PID也是用来标识进程的,也是唯一性的!其实PID跟port,都属于进程的身份,就好像学生由身份证,也有他的学生证,一句话来说,将进程的PID和port分开来使用,是为了解耦!

一个进程可以关联多个端口号,而一个端口号不能关联多个进程。

网络是一份共享资源

要在网络上进行进程间通信,我们首先需要找到目标主机,然后找到该主机上的服务(进程),完成进程间通信。我们可以说网络世界,是一个进程间通信的世界。而进程要通信的话,由于进程具有独立性,因此不同的进程必须看到同一份资源,即共享资源!所有,网络便是一份共享资源!

2、认识TCP协议,认识UDP协议

这里先简单得对TCP和UDP来一个直观的认识:

TCP协议和UDP协议都是传输层的控制协议,以下是两种协议的特定,我们需要根据它们的特定,在不同场景下,权衡使用哪种协议。

TCP协议:

*传输层协议  *有连接  *可靠传输  *面向字节流

YDP协议:

*传输层协议  *无连接  *不可靠传输  *面向数据报

3、认识网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

网络数据流觉得这样分来分去太麻烦了,这样吧!我就使用大端的形式吧!如果你的数据流本来就是大端,那你就直接传输,如果你的数据流是小端,那么麻烦你先转换成大端,再来传输!

因此,网络字节序指的就是在网络上的采用的大端形式,先发出的数据是低地址,后发出的数据是高地址。

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

总结一下网络字节序:

⭐发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出。 ⭐接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。 ⭐因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。 ⭐TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。 ⭐不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据。 ⭐如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。

4.socket编程接口

socket的意思是套接字,即

socket 常见API

代码语言:javascript
复制
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

我们逐一来理解一下这些接口:

代码语言:javascript
复制
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

第一个参数:domain:协议域。就是需要用哪种协议,我们最常用的就两种->AF_INET (IPV4协议),AF_INET6 (IPV6协议)。

第二个参数:套接字的类型,即SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)。

第三个参数:这个我们置为0即可,它是用来制定某个协议的特定类型,即type类型中的某个类型。通常一种协议只有一种类型,那样该参数可以直接被设置为0;如果协议有多种类型,则需要指定协议类型。 返回值:返回一个文件描述符。

代码语言:javascript
复制
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);

 第一个参数:socket函数返回的文件描述符。 第二个参数:指定想要绑定的IP和端口。下面将分析sockadder结构体。 第三个参数:address的长度。 返回值:成功为0,失败-1

sockaddr结构:

网络通信的方式有很多种,比如基于网IP的网络通信,AF_INET,原始套接字,域间套接字等等。有那么多方式,那么在绑定IP和端口的时候,就需要很多种方法了,因此系统需要将其统一一下结构,就有了sockadder。

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址。 IPv4、 IPv6地址类型分别定义为常数AF_INET、 AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。 socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

sockaddr 结构

 sockaddr_in 结构

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。

我们简化看看表示IPV4的结构体:

代码语言:javascript
复制
struct sockaddr_in
{
    sa_family_t       sin_family;//地址族
    uint16_t          sin_port;//TCP/UDP端口号,16位整型
    struct in_addr    sin_addr;//IP地址,32位整型
    char              sin_sero[8];//别管它了
};

其中sin_famile:

地址族

含义

AF_INET

IPV4网络协议中的使用的地址族

AF_INET6

IPV6网络协议中使用的地址族

AF_LOCAL

本地通信中采用的UNIX协议的地址族

in_addr结构

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。

我们使用这两个函数,再补充两个函数:recvfrom和sendto就可以写一个示例了(UDP的)。

 recvfrom:适用于UDP协议

代码语言:javascript
复制
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
            struct sockaddr *src_addr, socklen_t *addrlen);

 本函数用于从(已连接)套接口上接收数据,并捕获数据发送源的地址

第一个参数:套接字文件描述符 第二个参数:指明一个缓冲区,该缓冲区用来存放recvfrom函数接收到的数据 第三个参数:buf的长度 第四个参数:一般置0,即false。 第五个参数:是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。 第六个参数:第五个参数的sizeof 返回值:成功返回接收到的字节数。失败返回-1。

sendto:适用于UDP协议

代码语言:javascript
复制
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

·第一个参数:套接字文件描述符。 第二个参数:指明一个存放应用程序要发送数据的缓冲区。 第三个参数:buf的长度 第四个参数:置为0吧。 第五个参数:dest_addr表示目地机的IP地址和端口号信息 第六个参数:dest_addr的长度 返回值:成功返回接收到的字节数。失败返回-1。

示例代码:

客户端client代码:

代码语言:javascript
复制
#include <iostream>
#include <string>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void Usage(std::string proc)
{
    std::cout<<"Usage: \n\t"<<proc<<" server_ip server_port"<<std::endl;
}

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 0;
    }

    //1.创建套接字,打开网络文件
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        std::cerr << "socket error : " << errno << std::endl;
        return 1;
    }

    //客户端不需要显示bind。
    //首先,客户端必须也要有IP和port
    //但是,客户端不需要显示的bind。因为一旦显示bind,就必须明确客户端client
    //要和哪个端口port关联。
    //而如果客户端client指明了端口号,那么在客户端client不一定会有用,因为
    //这个端口号有可能被占用了,比如我们在联网的时候,一边打LOL,一边斗地主
    //被占用就会导致client无法使用
    //server要的是port必须明确,而且不变,但client只要有就行!一般是由OS自动给你bind()
    // 就是client正常发送数据的时候,OS会自动给你bind,采用的是随机端口的方式!

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

    //使用服务
    while(1)
    {
        //数据从我们键盘输入
        std::string message;
        std::cout<<"输入# ";
        std::cin>>message;

        sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server, sizeof(server));

        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp, &len);
        std::cout<<"server echo# "<<buffer<<std::endl;
    }
    return 0;
}

服务器server代码:

代码语言:javascript
复制
#include<iostream>
#include<string>
#include<cerrno>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

const uint16_t port = 8080;

int main()
{
    //1.创建套接字,打开网络文件
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        std::cerr<<"socket create error: "<<errno<<std::endl;
        return 1;
    }

    //2.给该服务器绑定端口和ip
    struct sockaddr_in local;
    //填充字段
    local.sin_family = AF_INET;
    local.sin_port = htons(port);//此处的port是端口号,是计算机上的变量
    //是属于主机序列,说明需要主机转网络的操作htons();
    //需要将人识别的点分十进制,字符串风格IP地址,转化成为4字节整数IP 
    //需要考虑大小端,因此使用in_addr_t inet_addr(const char *cp);
    //local.sin_addr.s_addr = inet_addr("43.139.32.198");//点分十进制【0-255】
    //我们不能像上面这行代码一样,直接绑定bind某个IP,因为如果指定绑定一个IP,那么
    //只有发送到该IP主机上的数据才会交给你的网络进程
    //但是,服务器一般会配置很多个网卡,有很多个IP。
    //因此,作为服务器,我们需要的不是某个IP上面的数据,
    //而是需要所有发送到该服务器主机上的某个端口的数据!
    //使用INADDR_ANY,不绑定指定IP
    local.sin_addr.s_addr = INADDR_ANY;

    //绑定IP和端口
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        std::cerr<<"bind error: "<<errno<<std::endl;
        return 2;
    }

    //3.提供服务
    bool quit = false;
    #define NUM 1024
    char buffer[NUM];
    while(!quit)
    {
        struct sockaddr_in peer;//保存接受到的数据的空间
        socklen_t len = sizeof(peer);//空间的大小

        recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
        std::cout<<"client# "<<buffer<<std::endl;

        std::string echo_hello = "hello";//服务器发送回给用户,表示收到消息了
        sendto(sock,echo_hello.c_str(),echo_hello.size(),0,(struct sockaddr*)&peer,len);
    }

    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、认识端口号,区分IP/port,PID/port
    • 认识端口号port
      • 区分IP/PORT,PID/PORT
        • 网络是一份共享资源
        • 2、认识TCP协议,认识UDP协议
        • 3、认识网络字节序
        • 4.socket编程接口
          • socket 常见API
            • sockaddr结构:
            • 示例代码:
        相关产品与服务
        NAT 网关
        NAT 网关(NAT Gateway)提供 IP 地址转换服务,为腾讯云内资源提供高性能的 Internet 访问服务。通过 NAT 网关,在腾讯云上的资源可以更安全的访问 Internet,保护私有网络信息不直接暴露公网;您也可以通过 NAT 网关实现海量的公网访问,最大支持1000万以上的并发连接数;NAT 网关还支持 IP 级流量管控,可实时查看流量数据,帮助您快速定位异常流量,排查网络故障。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档