简单的UDP网络程序(Socket API)

预备知识:

  • 本文的目的是写回显服务器,在撸代码之前,先来个热身。
IP地址是什么?

IP地址有IPV4、IPV6之分,一般不特俗说明,默认就是IPV4。

IP地址是用来标识不同的主机,每个主机都有唯一的IP地址;
对于IP4来说,IP地址是一个4字节,32位整数;
IP地址用“点分”制表示,如:192.168.1.11(用点分割的每个数范围0~255)。

源IP地址&目的IP地址?

很容易理解,都是地址,和寄快递的收发地址一样,从上海发往西安的快递,源IP就是上海,目的IP就是西安。

端口号是什么?
  • 端口号是传输层协议内容

端口号是2字节196位整数;
端口号用来标识一个进程,告诉操作系统,当前数据要交给哪一个进程来处理;
一个进程可以绑定多个端口号,但是一个端口号不可以绑定多个进程。

源端口号&目的端口号?

在源IP&目的IP中,我们用的寄快递的例子帮助理解,在这里,还是用发快递帮助理解。源IP与目的IP标识了发件人地址和收件人的地址,地址有了,那么包裹就会交给快递员运送每个,快递员都有一个工号,工号是唯一的。这就对应了数据传输过程中,由哪个进程来处理数据。再来到寄快递问题上,有的快递包裹比较大,这就要多个快递员来运输,那么一个包裹由多个快递员运输,记在物流信息上就是这样的格式:一个包裹的目的地 + 多个快递员工号;这家公司接的都是大包裹,一个快递员只能送一个包裹。对应到网络传输中,就是一个进程可以绑定多个端口号,但一个端口号不可以绑定多个进程。


函数介绍:

  • 为后面撸代码介绍几个函数。
网络字节序:

在C语言中我们知道,内存中的数据存储有大小端之分;数据在磁盘中存储也有大小端之分,在这里我还想啰嗦一个C语言问题,怎样判断自己的计算机内存是大端字节序还是小端字节序存储方式?[假装思索……]

  • 大小端判断很有可能成为你将来的面试题。以前我总结过这样的问题。
  • 附上链接:

  • (只想引入下面一句话)在网络数据流中同样有大小端之分,那么如何定义网络数据流的地址呢?

【看图理解】

简单的UDP网络程序(Socket API)_第1张图片

【看图说话】

这里写图片描述

  • 如果发送主机是小端,就要准换成大端再发送,如果是大端,直接发送即可。

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

#include
   uint32_t htonl(uint32_t hostlong);
   uint16_t htons(uint16_t hostshort);
   uint32_t ntohl(uint32_t netlong);
   uint16_t ntohs(uint16_t netshort);

调用函数就能解决存储字节序不统一的问题

socket编程常见API:

这部分只把函数列出来,详细介绍请戳作者下面博客:

socket套接字

  //创建socket文件描述符  (TCP/UDP,客户端+服务器)
  int socket(int domain, int type, int protocol);

  ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

  ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

  //关闭套接字
  int close(int fd);
sockaddr结构:

socket API是一 层抽象的网络编程接口 ,适用于各种底层网络协议,如IPv4、IPv6.然而, 各种网络协议的地址格式各不相同。

简单的UDP网络程序(Socket API)_第2张图片

  • 注意:socket API可以都用 struct sockaddr *类型表 , 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

UDP协议:

  • 通过上面的学习对UDP有个直观的认识,再详细讨论以下几点:
  • 无连接

知道目的端的IP和端口号就能传输,不需要建立连接。

  • 不可靠传输

没有确认机制,没有重传机制,如果因为网络故障无法发送到对方,UDP协议层也不会给应用层返回任何错误信息。

  • 面向数据报

不能够灵活的控制读写数据的次数和数量。

  • 以上几点在代码中还能得到学习和理解。
服务器和客户端是怎么运行起来的?
  • 服务器:

1、创建socket

2、绑定端口

3、循环的读取数据

4、针对读取到的数据进行计算和处理

5、把处理后的结果发回客户端

  • 客户端:

1、创建socket文件

2、给服务器发送请求

3、从服务器中读取结果。

  • 有了步骤,实现起来就只需要把步骤翻译成Code了:
服务器实现:
#include
#include
#include
#include
#include
#include

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockSer = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer;  //创建一个记录地址信息的结构体 
    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip

    //将套接字地址与所创建的套接字号联系起来,并检测是否绑定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区
    struct sockaddr_in addrCli;
    while(1)  //服务器一直循环接受客户端的请求
    {
        recvfrom(sockSer,recvbuf,256,0,(struct  sockaddr*)&addrCli, &addrlen);     //从指定地址接收客户端数据
        printf("Cli:>%s\n",recvbuf);

        printf("Ser:>");    
        scanf("%s",sendbuf);
        sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen);    //向客户端发送数据
    }
    return 0;
}
客户端实现:
#include
#include
#include
#include
#include
#include

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockCli == -1){
        perror("socket");
    }

    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip
    socklen_t addrlen = sizeof(struct sockaddr);


    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区

    while(1){
        //向客户端发送数据
        printf("Cli:>");
        scanf("%s",sendbuf);
        sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen);   
        接收来自客户端的数据
        recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen);
        printf("Ser:>%s\n", recvbuf);
    }
    return 0;
}

你可能感兴趣的:(Linux/Unix)