Linux网络编程基础API

Linux网络编程基础API

主机字节序和网络字节序

现代CPU的累加器一次都能装载(至少)4字节(这里考虑32位机),即一个整数.那么这4字节在内存中排列的顺序将影响它被累加器装载成的整数的值.

1. 字节序的分类

  • 大端字节序(big endian)

    大端字节序是指一个整数的高位字节(23-31bit)存储在内存的低地址处,低位字节(0~7bit)存储在内存的高地址处.

  • 小端字节序(little endian)

    小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处.

2. 主机字节序和网络字节序

  • 现代PC大多采用小端字节序,因此小端字节序又被称为网络字节序
  • 现在网络传输中的字节序都采用大端字节序,所以大端字节序又被称为网络字节序

3. 字节序转换的作用

解决不同主机不同字节序间数据传输的问题. 发送数据时将本机字节序转化为网络字节序,接收数据时将网络字节序转化为主机字节序.

4. 字节序之间的转化函数

#include<netinet/in.h>

//host to network long  将长整形(32bit)的主机字节序数据转化为网络字节序
unsigned long int htonl(unsigned long int hostlong);

//host to network short 将短整形(16bit)的主机字节序数据转化为网络字节序
unsigned short int htons(unsigned short int hostshort);

//network to host long 将长整形(32bit)的网络字节序数据转化为主机字节序
unsigned long int ntohl(unsigned long int netlong);

unsigned short int ntohs(unsigned short int netshort);

5. 判断机器字节序

#include<cstdio>

void byteorder(){
    union{
        short value;
        char union_bytes[sizeof(short)];
    }test;
    test.value = 0x0102;
    if((test.union_bytes[0] == 1) && (test.union_bytes[1]) == 2){
        puts("big endian\n");
    }
    else if((test.union_bytes[0] == 2) && (test.union_bytes[1]) == 1){
        puts("little endian\n");
    }
    else{
        puts("unknown...\n");
    }
}

int main(){
    byteorder();
}

socket地址

1. 通用socket地址

sockaddr
#include<bits/socket.h>
struct sockaddr
{
    sa_family_t sa_family;
    char sa_data[14];
}
  • sa_family成员是地址族类型(sa_family_t)的变量,地址族类型通常与协议族类型对应

    • 常见的协议族

      协议族地址值含义和长度地址族描述
      PF_UNIX文件的路径名,长度可达到108字节AF_UNIXUNIX本地域协议族
      PF_INET16bit端口号和32bitIPv4地址,共5字节AF_INETTCP/IPv4协议族
      PF_INET616bit端口号,32bit流标识,126bitIPv6地址,32bit范围ID,共26字节AF_INET6TCP/IPv6协议族
      • PF_*AF_*都定义在bits/socket.h头文件中,且后者与前者具有完全相同的值,所以二者通常混用
  • sa_data成员用于存放socket地址值. 由上表可见,14字节的sa_data根本无法完全容纳多数协议族的地址值,因此Linux定义了下面这个通用的socket地址结构体

sockaddr_storage
#include<bits/socket.h>
struct sockaddr_storage
{
    sa_family_t sa_family;
    unsigned long int __ss_align;
    char __ss_padding[128-sizeof(__ss_align)];
}
// 不仅提供了更大的空间用于存放地址值,而且是内存对齐的(__ss_align)

2. 专用socket地址

sockaddr_un
#include<sys/un.h>
struct sockaddr_un
{
    sa_family_t sin_family;  // 地址族:AF_UNIX
    char sun_path[108];      // 文件路径名
};
sockaddr_in
#include<sys/socket.h>
struct sockaddr_in
{
    sa_family_t sin_family;    // 地址族:AF_INET
    u_int16_t sin_port;        //端口号,要使用网络字节序表示
    struct in_addr sin_addr;   //IPv4地址结构第
    char sin_zero[8];          //不使用
}
struct in_addr
{
    u_int32_t s_addr;   //IPv4地址,要用网络字节序表示
}

//sockaddr_in结构体的赋值
struct sockaddr_in addr;
char * serv_port = "9999";
char * serv_ip = "127.0.0.1";
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;   //设置地址族
// addr.sin_addr.s_addr = htonl(INADDR_ANY);  //设置ip地址,服务器通常采用INADDR_ANY
addr.sin_addr.s_addr = inet_addr(serv_ip);    //客户端
addr.sin_port = htons(atoi(serv_port));    // 设置端口

//INADDR_ANY
//自动分配服务器端的IP,不必输入,服务器有限考虑这种方式
  • 通用sockaddr不好用,比如设置与获取IP地址和端口号就需要执行繁琐的位操作,所以使用sockaddr_in进行赋值,再转化为sockaddr

    struct sockaddr_in addr;
    ...
    if(bind(serv_sock,(struct sockaddr*)&addr),sizeof(addr) == -1);
    	error_handling("bind() error");
    ...
    
sockaddr_in6
#include<sys/socket.h>
struct sockaddr_in6
{
    sa_family_t sin5_family;   //地址族,要用AF_INET6
    u_int16_t sin6_port;       //端口号,使用网络字节序
    u_int32_t sin6_flowinfo;   //流信息,应设置为0
    struct in6_addr sin6_addr; //IPv6结构体
    u_int32_t sin6_scope_id;   //scope ID,尚处于实验阶段
}
struct in6_addr
{
    unsigned char sa_addr[16];  //IPv6地址,网络字节序表示
}

3. IP地址转换函数

#include<arpa/inet.h>
in_addr_t inet_addr(const char * strptr);
int inet_aton(const char * cp,struct in_addr* inp);
char * inet_ntoa(struct in_addr_in);
int inet_pton(int af,const char * src,void * dst);
const char * inet_ntop(int af,const void * src,char * dst,socklen_t cnt);
inet_addr

inet_addr将点分十进制字符串标示的IPv4地址转化为用网络字节序整数表示的IPv4地址.失败时返回INADDR_NONE.

inet_aton

inet_aton将点分十进制字符串标示的IPv4地址转化为用网络字节序整数表示的IPv4地址存储到参数inp指向的地址结构中.成功时返回1,失败时返回0

inet_ntoa

inet_ntoa将用网络字节序整数标示的IPv4地址转化为用点分十进制字符串标示的IPv4地址. 但inet_ntoa函数是不可重入的,该函数内部使用一个静态变量存储转化结果,函数的返回值指向该静态内存

#include<arpa/inet.h>
...
struct sockaddr_in addr1,addr2;
addr1.sin_addr.s_addr = htonl(0x1020304);
addr2.sin_addr.s_addr = htonl(0x1010101);

char * str_ptr1,str_ptr2;
str_ptr1 = inet_ntoa(addr1.sin_addr);  //str_ptr1: 1.2.3.4
//str_ptr2 = inet_ntoa(addr2.sin_addr);  //str_ptr1: 1.1.1.1, str_ptr2: 1.1.1.1
//第二次调用修改了函数中的静态变量

//为了防止之前的字符串被修改,应该将之前的字符串保存到其他内存空间
char str_arr[20];
strcpy(str_arr,str_ptr1); // str_arr: 1.2.3.4
//str_ptr2 = inet_ntoa(addr2.sin_addr);  //str_ptr2: 1.1.1.1, str_arr:1.2.3.4

inet_pton
  • inet_pton函数将用字符串表示的IP地址src(用点分十进制字符串标示的IPv4地址或用十六进制字符串表示的IPv6地址)转换成用网络字节序表示的IP地址,并把转换结果存储与dst指向的内存中.

  • af:指定地址族,可以是AF_INET或者AF_INET6

  • 成功返回1,失败返回0并设置errno

inet_ntop
  • inet_ntop函数进行和inet_pton相反的转换.前三个参数的含义与inet_pton相同

  • cnt:制定目标存储单元的大小,使用下面两个宏

    #include<netinet/in.h>
    #define INET_ADDRSTRLEN 16
    #define INET6_ADDRSTRLEN 46
    
  • 成功时返回目标存储单元的地址,失败返回NULL并设置errno

函数使用
/*
 * @Descripttion: 
 * @Version: 
 * @Author: zsj
 * @Date: 2020-05-03 09:59:49
 * @LastEditors: zsj
 * @LastEditTime: 2020-05-03 13:48:51
 */
#include<sys/socket.h>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<cstring>
#include<cstdio>
#include<netinet/in.h>

int main()
{
    sockaddr_in addr;
    addr.sin_addr.s_addr = htonl(0x1020304);

    char * str_addr = inet_ntoa(addr.sin_addr);
    printf("%s\n",str_addr);    // 1.2.3.4

    in_addr_t addr_t = inet_addr(str_addr);
    printf("%X\n",ntohl(addr_t));  //1020304

    sockaddr_in temp;
    inet_aton(str_addr,&temp.sin_addr);
    printf("%X\n",ntohl(temp.sin_addr.s_addr));  //1020304

    sockaddr_in temp2;
    inet_pton(AF_INET,str_addr,(void*)&temp2.sin_addr.s_addr);
    printf("%X\n",ntohl(temp2.sin_addr.s_addr));  //1020304

    // char str_arr[INET_ADDRSTRLEN];
    // char * adr = inet_ntop(AF_INET,&addr.sin_addr,const_cast<char*>(str_arr),sizeof(str_arr));

}

socket核心函数

#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>

int socket(int domain,int type,int protocl);
int bind(int sockfd,const struct sockaddr*my_addr,socklen_t addrlen);
int listen(int sockfd,int backlog);
int accept(int sockfd,struct sockaddr*addr,socklen_t*addlen);
int connect(int sockfd,const struct sockaddr*serv_addr,socklen_t addrlen);
int close(int fd);

1. 服务器端

socket

创建一个socket.

  • domain:使用的是哪个底层协议族,有三个值
    • PF_INET, IPv4
    • PF_INET6, IPv6
    • PF_UNIX, UNIX本地协议族
  • type:指定服务类型,
    • SOCK_STREAM,流服务,对应TCP协议
    • SOCK_DGRAM,数据报服务,对应UDP协议
    • SOCK_NONBLOCK ,与上述1,2个值相与,表示将新创建的socket设置为非阻塞的(Linux内核版本>2.6.17)
    • SOCK_CLOEXEC,与上述1,2个值相与,表示当用fork创建子进程时在子进程中关闭该socket(Linux内核版本>2.6.17)
  • protocol:在前两个参数构成的协议集合下,再选择一个具体的协议.在多数情况下,都应该设置为0
  • 函数成功时返回一个socket描述符,失败则返回-1,并设置errno
bind

命名socket. 将一个socket与socket地址绑定称为给socket命名.

  • sockfd:要分配地址信息的套接字文件描述符
  • my_addr:存有地址信息的结构体变量地址值
  • addrlen:第二个结构体变量的长度
  • 成功返回0,失败返回-1并设置errno,其中常见的两种errno是:
    • EACCES:被绑定的地址是收保护的地址,仅超级用户能够访问
    • EADDRINUSE:被绑定的地址正在使用中.
listen

socket被命名之后不能马上接受客户连接,需要使用listen系统调用来创建一个监听队列以存放待处理的客户.

  • sockfd:制定被监听的socket
  • backlog:提示内核监听队列的最大长度,典型值是5
  • 成功时返回0,失败时返回-1并设置errno
accept

从listen监听队列中接收一个连接.调用此函数后会阻塞

  • sockfd:是执行过listen系统调用的监听socket
  • addr:用来获取被接受连接的远端socket地址
  • addrlen:指出addr接收的socket地址的长度
  • 成功时返回一个新的连接socket,失败时返回-1并设置errno
close

关闭一个socket连接

  • fd:要关闭的socket的描述符
  • close系统调用并不是立即关闭一个连接,而是将fd的引用计数减一.只有当fd的引用计数为0时,才真正的关闭连接.在多进程程序中fork()默认使父进程中打开的socket引用计数加1,只有父子进程中都对该socket执行close才能完全关闭

2. 客户端

socket

同服务器端

connect

主动与服务器建立连接.调用函数后会阻塞

  • sockfd:客户端套接字文件描述符

  • serv_addr:保存目标服务端地址信息的变量地址值

  • addrlen:以字节为单位传递以传递给第二个结构体参数serv_addr的变量地址的长度

  • 成功时返回0,失败返回-1并设置errno,如下两种errno:

    • ECONNREFUSED:目标端口不存在,连接被拒绝
    • ETIMEDOUT:连接超时.
close

同服务器端

3.数据读写

TCP数据读写
#include<sys/types.h>
#include<sys/socket.h>

ssize_t read (int fd, void *buf, size_t nbytes);
ssize_t recv(int sockfd,void*buf,size_t len,int flags);

ssize_t write(int fd, const void *buf, size_t n);
ssize_t send(int sockfd,const void * buf,size_t len,int flags);
read

接受或者输入数据

  • fd:显示数据接受对象的文件描述符
  • buf:要保存接收数据的缓冲区地址
  • nbytes:要接收数据的最大字节数
  • 成功时返回接收到的字节数,接收到文件尾或者连接被关闭返回0,失败返回-1
write

向文件或者套接字传递数据.

  • fd:显示数据传输对象的文件描述符
  • buf:保存要传输数据的缓冲地址值
  • n:要传输数据的字节数
  • 成功时返回写入的字节数,失败时返回-1
recv

接受或者输入数据

  • sockfd:表示数据接收对象的连接的套接字文件描述符
  • buf:保存接收数据的缓冲地址值
  • len:可接收的最大字节数
  • flags传输数据时制定的可选项信息
  • 成功时返回接受的字节数(收到EOF返回0),失败时返回-1;
send

向文件或者套接字传递数据

  • sockfd:表示与数据传输对象的连接的套接字文件描述符
  • buf:保存带传输数据的缓冲地址值
  • len:待传输的字节数
  • flags传输数据时制定的可选项信息
  • 成功时返回发送的字节数,失败时返回-1
UDP数据读写
#include<sys/socket.h>

ssize_t sendto(int sock,void * buff,size_t nbytes,int flags,struct sockaddr* to,socklen_t addrlen);

ssize_t recvfrom(int sock,void * buff,size_t nbytes,int flags,struct sockaddr*from.socklen_t * addrlen);
sendto
  • sock:用于传输数据的UDP套接字文件描述符
  • buff:保存待传输数据的缓冲区地址值
  • nbytes:待传输的数据长度,以字节为单位
  • flags:可选项参数,若没有则传递0
  • to:存有目标地址信息的sockaddr结构体变量的地址值
  • addrlen:传递给参数偷得地址值结构体变量长度
  • 成功时返回传输的字节数,失败时返回-1
recvfrom
  • sock:用于接收数据的UDP套接字文件描述符
  • buff:保存接收数据的缓冲区地址值
  • nbytes:可接受的最大字节数,故无法超过参数buff所指的缓冲区大小
  • flags:可选项参数,若没有则传入0
  • from:存有发送端地址信息的sockaddr结构体变量的地址值
  • addrlen:保存参数from的结构体变量长度的变量地址值

socket编程实践(1)

TCP

服务器端
/**
 * @File:serv_1.cpp
 * @Descripttion: socket编程服务器端,简单版本(TCP)
 *  只接受一个客户端连接,并接收客户端发送的信息显示后就退出
 * @Version: 
 * @Author: zsj
 * @Date: 2020-05-03 15:38:31
 * @LastEditors: zsj
 * @LastEditTime: 2020-05-03 16:20:39
 */
#include<cstdio>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<cstring>

void show_error(char * info){
    printf(info);
    exit(-1);
}

int main(int argc,char * argv[])
{
    //参数数量不对直接返回
    if(argc < 2){
        printf("Usage: %s <port>\n",argv[0]);
        exit(-1);
    }

    // 创建一个socket
    int listenfd = socket(PF_INET,SOCK_STREAM,0);
    if(listenfd == -1){
        show_error("socket() error!");
    }

    // 设置socket地址
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[1]));

    //分配地址信息,socket命名
    int ret = bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret == -1){
        show_error("bind() error!");
    }

    //创建一个监听队列
    ret = listen(listenfd,5);
    if(ret == -1){
        show_error("listen() error!");
    }

    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_sz = sizeof(clnt_addr);
    //取出一个连接
    int clnt_sock = accept(listenfd,(struct sockaddr*)&clnt_addr,&clnt_addr_sz);
    if(clnt_sock == -1){
        show_error("accept() error!");
    }
    else{
        printf("get a connection %d\n",clnt_sock);
    }

    char buffer[128];
    //读取数据
    ret = read(clnt_sock,buffer,sizeof(buffer));
    if(ret < 0){
        show_error("read() error!");
    }
    else if(ret == 0){
        printf("client close the connection!");
    }
    else{
        printf("from %d data %d bytes: %s\n",clnt_sock,ret,buffer);
    }

    close(clnt_sock);
    close(listenfd);
}
客户端
/**
 * @File:clnt_1.cpp
 * @Descripttion: socket编程,客户端简单版本,(TCP)
 *  连接服务器之后,发送数据到服务器之后就退出
 * @Version: 
 * @Author: zsj
 * @Date: 2020-05-03 16:06:36
 * @LastEditors: zsj
 * @LastEditTime: 2020-05-03 16:19:46
 */
#include<cstdio>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<cstring>


void show_error(char * info){
    printf(info);
    exit(-1);
}

int main(int argc,char * argv[])
{
    if(argc < 3){
        printf("Usage: %s <ip> <port>\n",argv[0]);
        exit(-1);
    }

    //创建一个socket
    int clnt_sock = socket(PF_INET,SOCK_STREAM,0);
    if(clnt_sock == -1){
        show_error("socket() error!");
    }

    //客户端不需要绑定和监听,直接连接服务器
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_port = htons(atoi(argv[2]));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);

    int ret = connect(clnt_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret == -1){
        show_error("connect() error!");
    }
    else{
        printf("connect to %s success!\n",argv[1]);
    }

    //发送数据
    char buffer[128] = "hello server!!!";
    ret = write(clnt_sock,buffer,sizeof(buffer));
    if(ret < 0){
        show_error("write() error!");
    }
    else{
        printf("send data success!\n");
    }

    //关闭连接
    close(clnt_sock);

}

UDP

服务器端
/*
 * @File: serv_udp_1.cpp
 * @Descripttion: socket编程服务器端,简单版本(UDP)
 *  只接受一个客户端连接,并接收客户端发送的信息显示后就退出
 * @Version: 
 * @Author: zsj
 * @Date: 2020-05-03 17:05:25
 * @LastEditors: zsj
 * @LastEditTime: 2020-05-03 17:14:33
 */
#include<cstdio>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<cstring>

void show_error(char * info){
    printf(info);
    exit(-1);
}

int main(int argc,char * argv[])
{
    //参数数量不对直接返回
    if(argc < 2){
        printf("Usage: %s <port>\n",argv[0]);
        exit(-1);
    }

    // 创建一个socket
    int serv_sock = socket(PF_INET,SOCK_DGRAM,0);
    if(serv_sock == -1){
        show_error("socket() error!");
    }

    // 设置socket地址
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[1]));

    //分配地址信息,socket命名
    int ret = bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret == -1){
        show_error("bind() error!");
    }


    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_sz = sizeof(clnt_addr);

    char buffer[128];
    ret = recvfrom(serv_sock,buffer,sizeof(buffer),0,(sockaddr*)&clnt_addr,&clnt_addr_sz);
    if(ret < 0){
        show_error("read() error!");
    }
    else if(ret == 0){
        printf("client close the connection!");
    }
    else{
        printf("from %s data %d bytes: %s\n",inet_ntoa(clnt_addr.sin_addr),ret,buffer);
    }
    close(serv_sock);
}
客户端
/*
 * @File:clnt_udp_1.cpp
 * @Descripttion: socket编程,客户端简单版本(UDP)
 *  连接服务器之后,发送数据到服务器之后就退出
 * @Version: 
 * @Author: zsj
 * @Date: 2020-05-03 16:06:36
 * @LastEditors: zsj
 * @LastEditTime: 2020-05-03 17:16:48
 */
#include<cstdio>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<cstring>


void show_error(char * info){
    printf(info);
    exit(-1);
}

int main(int argc,char * argv[])
{
    if(argc < 3){
        printf("Usage: %s <ip> <port>\n",argv[0]);
        exit(-1);
    }

    //创建一个socket
    int clnt_sock = socket(PF_INET,SOCK_DGRAM,0);
    if(clnt_sock == -1){
        show_error("socket() error!");
    }

    //客户端不需要绑定和监听,直接连接服务器
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_port = htons(atoi(argv[2]));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);

    

    //发送数据
    char buffer[128] = "hello server!!!";
    int ret = sendto(clnt_sock,buffer,sizeof(buffer),0,(sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret < 0){
        show_error("sendto() error!");
    }
    else{
        printf("send data success!\n");
    }

    //关闭连接
    close(clnt_sock);

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值