Linux —— 网络编程套接字

目录

一,网络字节序

二,socket编程接口

sockaddr结构


源IP地址、目的IP地址,在IP数据包头部,有此两个IP地址;

端口号,是传输层协议的内容;

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程,告诉操作系统当前数据要交给哪个进程来处理;
  • IP地址+端口号能标识网络上的某台主机的某个进程;
  • 一个端口号只能被一个进程占用;

一个进程可以绑定多个端口号,但一个端口号不能被多个进程绑定;

源端口号、目的端口号,传输层协议(TCP、UDP)的数据段中有此两个端口号,描述“数据是谁发的,要发给谁”;

TCP协议(Transmission Control Protocol 传输控制协议)

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

UDP协议(User Datagram Protocol 用户数据报协议)

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

一,网络字节序

        内存中的多字节数据相当于内存地址有大小端之分,磁盘文件中的多字节数据相当于文件中的偏移也有大小端之分,网络数据流同样也有大小端之分;

如何定义网络数据流的地址:

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

为使网络程序具有可移植性,同样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);
  • h,表示host,n表示network,l表示32位长整数,s表示16位短整数;
  • 如htonl表示32位长整数从主机字节序转换为网络字节序;
  • 如主机是小端字节序,这些函数将参数做相应的大小端转换,然后返回;
  • 如主机是大端字节序,这些函数不做转换,然后返回;

二,socket编程接口

socket常见API

//创建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 sockid, const struct sockaddr* addr, socklen_t addlen);

sockaddr结构

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

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

Linux —— 网络编程套接字_第1张图片

//socketaddr 结构
struct sockaddr{
    __SOCKADDR_COMMON (sa_);
    char sa_data[14];
};
//sockadddr_in 结构
struct sockaddr_in{
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;
    struct in_addr_ sin_addr;

    unsigned char sin_zero[sizeof (struct sockaddr) -
        __SOCKADDR_COMMON_SIZE - 
        sizeof (in_port_t) -
        sizeof (struct in_addr)];
};

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

//in_addr 结构
typedef uint32_t in_addr_t;
struct in_addr{
    in_addr_t s_addr;
};

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

//UDP server.cc
#include     
#include     
#include     
#include     
#include     
#include     
    
#define PORT 8081    
    
int main()    
{    
    int sock = socket(AF_INET, SOCK_DGRAM, 0);    
    if(sock < 0){    
        std::cerr << "socket error" << std::endl;    
        return 1;    
    }    
    std::cout << "sock: " << sock << std::endl;    
    
    struct sockaddr_in local;    
    memset(&local, 0, sizeof(local));    
    local.sin_family = AF_INET;    
    local.sin_port = htons(PORT); //    
    local.sin_addr.s_addr = htonl(INADDR_ANY);    
    
    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){    
      std::cout << "bind error " << std::endl;    
      return 1;    
    }    
    
    char message[1024];    
    for(; ;){    
      struct sockaddr_in peer;    
      socklen_t len = sizeof(peer);    
      ssize_t s = recvfrom(sock, message, sizeof(message)-1, 0, (struct sockaddr*)&peer, &len);    
      if(s > 0){    
        message[s] = '\0';    
        std::cout << "client# " << message << std::endl;    
        sendto(sock, message, strlen(message), 0, (struct sockaddr*)&peer, len);                                                               
      }    
      else{ 
        //todo
      }    
    }    
    
    close(sock);    
    return 0;    
}

你可能感兴趣的:(操作系统,Linux)