【Linux】网络编程套接字——UDP协议

一.认识ip地址

  1. IP地址是在IP协议中,用来表示网络中不同主机的地址
  2. IP协议有两个版本,IPv4和IPv6,但是通常我们使用的都是IPv4;
  3. 对于IPv4,IP地址是一个4字节,32位的整数。我们通常用“点分十进制”的字符串表示IP地址,比如:192.168.2.250
  4. 在IP数据包头部中,有两个IP地址,分别是源IP地址和目的IP地址。

我们要怎么理解这个地址呢?举一例子:就像唐僧取经,他从东土大唐而来,去往西天拜佛取经,那么东土大唐就是源IP地址,而西天就是目的IP地址。

二.认识端口号

  有了IP地址,我们可以把数据发送到对方的机器上,但是却还需要一个其他的标识来区分,这个数据是要给哪个进程的。我们的端口号就是这个作用。

  1. 端口号是一个2字节16位的整数;
  2. 端口号用来表示一个进程一个端口号只能被一个进程占用
  3. IP地址+端口号能标识网络上的某一台主机的某一个进程,所以本质上也相当于进程间通信;
  4. IP地址+端口号就是套接字(socket)

三.网络字节序

  内存中的多字节数据相对于内核地址都有大端小端之分,网络数据流同样也有大小端之分。

  1. 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  2. 接受主机把从网络上街道的字节一次保存在接受缓冲区中,也是按内存地址从低到高的顺序保存;
  3. TCP/IP协议规定,网络数据流采用大端字节序,即低地址高字节
  4. 所有机器都按照规定的网络字节序发送接收数据,如果当前发送主机是小端,则需要先将数据转换成大端;否则,就忽略,直接发送。

调用以下库函数做网络字节序和主机字节序的转换:

#include <arpa/inet.h>
unint32_t htonl(unint32_t hostlong);
unint16_t htons(unint16_t hostshort);
unint32_t ntohl(unint32_t netlong);
unint16_t htons(unint32_t netshort);
  1. h表示host主机,n表示network网络,l表示32位长整数,s表示16位短整数
  2. htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后发送。

四.传输层协议

  1. UDP协议:无连接、不可靠、面向数据报
  2. TCP协议:有连接、可靠传输、面向字节流

五.socket编程接口

1. 常见API

// 创建 socket ⽂件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
  1. 参数:domain表示域,当前要使用的协议簇,type表示使用哪种服务,protocol设置为0
  2. 返回值:调用成功返回文件描述符,失败返回-1
// 绑定端⼝号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
  1. 客户端不是不允许绑定,只是没有必要固定一个端口号,而且如果在同一台机器上启动多个客户端,就会出现端口号被占用而导致不能建立连接;
  2. 服务器也不是必须要绑定,但如果不绑定端口号,内核会自动给服务器分配一个,每次启动服务器时端口都不一样,客户端连接服务器就会遇到麻烦。
// 监听socket (TCP服务器)
int listen(int socket, int backlog);
//参数:backlog等待队列,一般设置为5,一方面保证服务器性能,一方面节省资源,由操作系统中的网络部分维护

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

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

2. sockaddr结构

  1. socket API是一层抽象的网络编程接口,适用于底层各种网络协议,IPv4、IPv6等,而每一种网络协议的地址格式并不相同。
  2. IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址。IPv4、IPv6的地址类型为AF_INET、AF_INET6,这样只要得到某种sockaddr结构体的首地址,不需要知道是具体哪种类型的sockade结构一条,就可以根据地址类型字段确定结构体中的内容。
  3. socket API中的参数都是struct sockaddr*类型,在使用时需要强制类型转换成sockaddr_in;这样的好处是程序的通用性。

简单的UDP网络程序

  这段程序是这样的:从客户端上sendto一条消息到服务器端,然后服务器端将收到的消息回显到客户端。
Makefile文件

.PHONY:all
all:udp_client udp_server

udp_client:udp_client.c
    gcc -o $@ $^
udp_server:udp_server.c
    gcc -o $@ $^

.PHONY:
clean:
    rm -f udp_client udp_server

服务器端

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

int createsock(const char* ip, int port)
{
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0)
    {
        perror("socket");
        return 2;
    }
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = inet_addr(ip);

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

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printf("Usage:%s [ip] [port] \n", argv[0]);
        return 1;
    }
    int sock = createsock(argv[1], atoi(argv[2]));
    char buf[1024];
    struct sockaddr_in local;
    while(1)
    {
        socklen_t len = sizeof(local);
        ssize_t s = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&local, &len);
        if(s > 0)
        {
            buf[s] = 0;
            printf("[%s:%d]: %s\n", inet_ntoa(local.sin_addr), ntohs(local.sin_port), buf);
            sendto(sock, buf, sizeof(buf), 0, (struct sockaddr*)&local, len);
        }
    }
    return 0;
}

客户端

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


int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s [ip] \n", argv[0]);
        return 1;
    }
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0)
    {
        perror("socket");
        return 2;
    }

    struct sockaddr_in peer;
    peer.sin_family = AF_INET;
    peer.sin_port = htons(8080);
    peer.sin_addr.s_addr = inet_addr(argv[1]);

    char buf[1024];
    struct sockaddr_in server;
    while(1)
    {
        socklen_t len = sizeof(server);
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf)-1);
        if(s > 0)
        {
            buf[s-1] = 0;
            sendto(sock, buf, sizeof(buf), 0, (struct sockaddr*)&peer, sizeof(peer));
            ssize_t _s = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&server, &len);
            if(_s > 0)
            {
                buf[_s] = 0;
                printf("server echo: %s\n", buf);
            }
        }
    }
    return 0;

}

猜你喜欢

转载自blog.csdn.net/wei_cheng18/article/details/80296215