嵌入式Linux网络编程——网络基础编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q1449516487/article/details/81546103

一、socket概述

1.socket定义

        socket是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。每一个socket都用一个半相关描述符{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个类似于打开文件的函数调用,该函数返回一个整形的socket描述符,随后的连接建立、数据传输等操作都通过socket来实现。

2.socket类型

        常见的socket有3中类型:

流式socket(SOCK_STREAM) 流式套接字提供可靠地、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
数据报socket(SOCK_DGRAM) 数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并不保证是可靠、无差错的。它使用数据报协议UDP。
原始socket 原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。

二、地址及顺序处理

1.地址结构相关处理

(1)用来保存socket信息的两个数据结构:sockaddr和sockaddr_in:

struct sockaddr
{
    unsigned short sa_family; /*地址族*/
    char sa_data[14];         /*14字节的协议地址,包含该socket的IP地址和端口号*/
};
struct sockaddr_in
{
    short int sa_family;         /*地址族*/
    unsigned short int sin_port;  /*端口号*/
    struct in_addr sin_addr;      /*IP地址*/
    unsigned char sin_zero(8);    /*填充0以保持与struct sockaddr同样大小*/
};

(2)结构字段

sa_family可选值
结构定义头文件 #include <netinet/in.h>
sa_family AF_INET:IPv4协议
AF_INET6:IPv6协议
AF_LOCAL:UNIX协议
AF_LINK:链路地址协议
AF_KEY:密钥套接字(socket)

2.数据存储优先顺序

         计算机数据存储有两种字节优先顺序:大端(高位字节优先)和小端(低位字节优先)。Internet上采用大端传输,因此需要对两个字节存储的优先顺序进行转化。Linux提供了以下四个函数:

扫描二维码关注公众号,回复: 4092247 查看本文章
htons等函数语法要点
所需头文件 #include <netinet/in.h>
函数原型

uint16_t htons(uint16_t host16bit)

uint32_t htonl(uint32_t host32bit)

uint16_t ntohs(uint16_t net16bit)

uint32_t ntohl(uint32_t net32bit)

函数传入值 host16bit:主机字节序的16位数据
host32bit:主机字节序的32位数据
net16bit:网络字节序的16位数据
net32bit:网络字节序的32位数据
函数返回值 成功:返回要转换的字节序
出错:-1
注:h表示host,n表示network,s表示short,l表示long,通常16位端口号用s表示,32位IP地址用l表示。

3.地址格式转化

(1)函数说明

        通常用户在表达地址时采用的是点分十进制表示的数值(或者是以冒号分开的十进制IPv6地址),而在通常使用的socket编程中所使用的则是二进制值,这就需要将这两个数值进行转换。这里在IPv4中用到的函数有inet_aton()、inet_addr()和inet_ntoa(),而IPv4和IPv6兼容的函数有inet_pton()和inet_ntop()。其中inet_pton()函数是将点分十进制地址映射为二进制地址,inet_ntop()是将二进制地址映射为点分十进制地址。

(2)函数格式

inet_pton函数语法要点
所需头文件 #include <arpa/inet.h>
函数原型 int inet_pton(int family,const char *strptr,void *addrptr)
函数传入值 family AF_INET:IPv4协议
AF_INET6:IPv6协议
strptr:要转化的值
addrptr:转化后的地址
函数返回值 成功:0
出错:-1
inet_ntop函数语法要点
所需头文件 #include <arpa/inet.h>
函数原型 int inet_ntop(int family,void *addrptr,char *strptr,size_t len)
函数传入值 family AF_INET:IPv4协议
AF_INET6:IPv6协议
addrptr:转化后的地址
strptr:要转化的值
len:转化后值的大小
函数返回值 成功:0
出错:-1

4.名字地址转化

(1)函数说明

       在Linux中,同样有一些函数可以实现主机名和地址的转化,最为常见的有gethostbyname()、gethostbyaddr()和getaddrinfo()等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化。其中gethostbyname()是将主机名转化为IP地址,gethostbyaddr()是将IP地址转化为主机名,getaddrinfo()实现自动识别IPv4地址和IPv6地址。

          gethostbyname()和gethostbyaddr()都涉及一个hostent的结构体,如下所示:

struct hostent
{
    char *h_name;         /*正式主机名*/
    char **h_aliases;     /*主机别名*/
    int h_addrtype;       /*地址类型*/
    int h_length;         /*地址字节长度*/
    char **h_addr_list;   /*指向IPv4或IPv6的地址指针数组*/   
}

         getaddrinfo()函数涉及一个addrinfo的结构体,如下所示:

struct addrinfo
{
    int ai_flags;              /*AI_PASSIVE,AI_CANONNAME*/
    int ai_family;             /*地址族*/
    int ai_socktype;           /*socket类型*/
    int ai_protocol;           /*协议类型*/
    size_t ai_addrlen;         /*地址字节长度*/
    char *ai_canonname;        /*主机名*/
    struct sockaddr *ai_addr;  /*socket结构体*/
    struct addrinfo *ai_next;  /*下一个指针链表*/
}

(2)函数格式

gethostbyname函数语法要点
所需头文件 #include <netdb.h>
函数原型 struct hostent *gethostbyname(const char *hostname)
函数传入值 hostname:主机名
函数返回值 成功:hostent类型指针
出错:-1
注:调用该函数首先对hostent结构体中的h_addrtype和h_length进行设置,若为IPv4可设置为AF_INET和4;若为IPv6可设置为AF_INET6和16;若不设置则默认为IPv4地址类型。
getaddrinfo()函数语法要点
所需头文件 #include <netdb.h>
函数原型 int getaddrinfo(const char *node,const char *service,const struct addrinfo *hints,struct addrinfo **result)
函数传入值 node:网络地址或者网络主机名
service:服务名或十进制的端口号字符串
hints:服务线索
result:返回结果
函数返回值 成功:0
出错:-1
addrinfo结构体常见常数
结构体头文件 #include <netdb.h>
ai_flags: AI_PASSIVE:该套接口是用作被动地打开
AI_CANONNAME:通知getaddrinfo函数返回主机的名字
ai_family: AF_INET:IPv4协议
AF_INET6:IPv6协议
AF_UNSPEC:IPv4或IPv6均可
ai_socktype: SOCK_STREAM:字节流套接字socket(TCP)
SOCK_DGRAM:数据报套接字socket(UDP)
ai_protocol: IPPROTO_IP:IP协议
IPPROTO_IPV4:IPv4协议
IPPROTO_IPV6:IPv6协议
IPPROTO_UDP:UDP
IPPROTO_TCP:TCP

注:

(1)通常服务器端在调用getaddrinfo()之前,ai_flags设置AI_PASSIVE,用于bind()函数(用于端口和地址的绑定),主机名               nodename通常会设置为NULL。

(2)客户端调用getaddrinfo()时,ai_flags一般不设置AI_PASSIVE,但是主机名nodename和服务名servname(端口)则应该不为空。

(3)即使不设置ai_flags为AI_PASSIVE,取出的地址也可以被绑定,很多程序中ai_flags直接设置为0,即3个标志位都不设置,这种情况下只要hostname和servname设置的没有问题就可以正确绑定。

(3)使用实例

/* getaddrinfo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main()
{
    struct addrinfo hints,*res = NULL;
    int rc;

    memset(&hints,0,sizeof(hints));   
    hints.ai_flags = AI_CANONNAME;    /*通知getaddrinfo函数返回主机的名字*/
    hints.ai_family = AF_UNSPEC;      /*IPv4和IPv6均可*/
    hints.ai_socktype = SOCK_DGRAM;   /*数据报套接字socket*/
    hints.ai_protocol = IPPROTO_UDP;  /*UDP协议*/

    rc = getaddrinfo("localhost1",NULL,&hints,&res);

    if(rc != 0)
    {
        perror("getaddrinfo");
        exit(1);
    }
    else
    {
        printf("Host name is %s\n",res->ai_canonname);  /*主机名*/
    }
    exit(0);
}

三、socket基础编程

(1)函数说明

        socket编程的基本函数有socket()、bind()、listen()、accept()、send()、sendto()、recv()以及recvfrom()等,函数说明如下:

socket() 该函数用于建立一个socket连接,可指定socket类型等信息。在建立了socket连接之后,可对sockaddr或sockaddr_in结构进行初始化,以保存所建立的socket地址信息。
bind() 该函数是用于将本地IP地址绑定到端口号,若绑定其他IP地址则不能成功。另外,它主要用于TCP的连接,而在UDP的连接中则无必要。
listen() 在服务端程序成功建立套接字和与地址进行绑定之后,还需要准备在该套接字上接收新的连接请求。此时调用listen()函数来创建一个等待队列,在其中存放未处理的客户端连接请求。
accept() 服务端程序调用listen()函数创建等待队列之后,调用accept()函数等待并接收客户端的连接请求。它通常从由bind()所创建的等待队列中取出第一个未处理的连接请求。
connect() 该函数在TCP中是用于bind()的之后的client端,用于与服务器端建立连接,而在UDP中由于没有了bind()函数,因此用connect()有点类似bind()函数的作用。
send()和recv() 这两个函数分别用于发送和接收数据,可以用在TCP中,也可以用在UDP中。当用在UDP时,可以在connect()函数建立连接之后再用。
sendto()和recvfrom() 这两个函数的作用与send()和recv()函数类似,也可以用在TCP和UDP中。当用在TCP时,后面的几个与地址有关参数不起作用,函数作用等同于send()和recv();当用在UDP时,可以用在之前没有使用connect()的情况下。这两个函数可以自动寻找指定地址并进行连接。


(2)函数格式

socket()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int socket(int family,int type,int protocol)
函数传入值

family:

协议族

AF_INET:IPv4协议

AF_INET6:IPv6协议
AF_LOCAL:UNIX域协议
AF_ROUTE:路由套接字(socket)
AF_KEY:密钥套接字(socket)

type:

套接字类型

SOCK_STRAM:字节流套接字socket
SOCK_DGRAM:数据报套接字socket
SOCK_RAW:原始套接字socket
protocl:0(原始套接字除外)
函数返回值 成功:非负套接字描述符
出错:-1
bind()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int bind(int sockfd,struct sockaddr *my_addr,int addrlen)
函数传入值 sockfd:套接字描述符
my_addr:本地地址
addrlen:地址长度
函数返回值 成功:0
出错:-1
listen()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int listen(int sockfd,int backlog)
函数传入值 sockfd:套接字描述符
backlog:请求队列中允许的最大请求数,大多数系统默认值为5
函数返回值 成功:0
出错:-1
accept()函数
所需头文件 #include <sys/socket.h>
函数原型 int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
函数传入值 sockfd:套接字描述符
addr:客户端地址

addrlen:地址长度

函数返回值 成功:客户端文件描述符
出错:-1
connect()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int connect(int sockfd,struct sockaddr *serv_addr,int addrlen)
函数传入值 socket:套接字描述符
serv_addr:服务器端地址
addrlen:地址长度
函数返回值 成功:0
出错:-1
send()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int send(int sockfd,const void *msg,int len,int flags)
函数传入值 sockfd:套接字描述符
msg:指向要发送数据的指针
len:数据长度
flags:一般为0
函数返回值 成功:发送的字节数
出错:-1
recv()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int recv(int sockfd,void *buf,int len,unsigned int flags)
函数传入值 sockfd:套接字描述符
buf:存放接收数据的缓冲区
len:数据长度
flags:一般为0
函数返回值 成功:接收的字节数
出错:-1
sendto()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int sendto(int sockfd,const void *msg,int len,unsigned int flags,const struct sockaddr *to,int tolen)
函数传入值 sockfd:套接字描述符
msg:指向要发送数据的指针
len:数据长度
flags:一般为0
to:目的机的IP地址和端口号信息
tolen:地址长度
函数返回值 成功:发送的字节数
出错:-1
recvfrom()函数语法要点
所需头文件 #include <sys/socket.h>
函数原型 int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen)
函数传入值 sockfd:套接字描述符
buf:存放接收数据的缓冲区
len:数据长度
flags:一般为0
from:源主机的IP地址和端口号信息
tolen:地址长度
函数返回值 成功:接收的字节数
出错:-1

(3)使用实例

服务端:

/* server.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT             4321  //端口号
#define BUFFER_SIZE      1024
#define MAX_QUE_CONN_NM  5

int main()
{
        struct sockaddr_in server_sockaddr,client_sockaddr;
        int sin_size,recvbytes;
        int sockfd,client_fd;//sockfd=文件描述符
        char buf[BUFFER_SIZE];

        /*建立socket连接*/
        if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//创建TCP套接字
        {
                perror("socket");
                exit(1);
        }
        printf("Socket id = %d\n",sockfd);

        /*设置sockaddr_in结构体中相关参数*/
        server_sockaddr.sin_family = AF_INET;  //sin_family=地址族,AF_INET=IPv4
        server_sockaddr.sin_port = htons(PORT);//sin_port=端口号,htons()主机字节序转化
        server_sockaddr.sin_addr.s_addr = INADDR_ANY;//s_addr=IP地址,INADDR_ANY指0.0.0.0,表示本地地址
        bzero(&(server_sockaddr.sin_zero),8);//填充0以保持与struct sockaddr同样大小
        int i=1;//允许重复使用本地地址与套接字进行绑定
        if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) < 0)//用于任意类型、任意状态套接口的设置选项值
        {
                perror("setsockopt");
        exit(1);
        }
        /*sockfd = 标识一个套接口的描述字
          level  = 选项定义的层次:支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
          optname = 需设置的选项
          optval  = 指针,指向存放选项待设置的新值的缓冲区
          optlen  = optval缓冲区长度*/

        /*绑定函数bind(),server_sockaddr = 本地地址,sizeof(struct sockaddr) = 地址长度*/
        if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) ==-1)
        {                                                                                                                                                                         
                perror("bind");
                exit(1);
        }
        printf("Bind success!\n");

        /*调用listen()函数,创建未处理请求的队列,MAX_QUE_CONN_NM=请求队列中允许
的最大请求数,大多数系统默认值为5*/
        if(listen(sockfd,MAX_QUE_CONN_NM) == -1)
        {
                perror("listen");
                exit(1);
        }
        printf("Listening...\n");
        /*调用accept()函数,等待客户端的连接,client_sockaddr = 客户端地址,地址长度*/
      if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) ==-1)
        {                                                                                                                                                                   
                perror("accept");
                exit(1);
        }

        /*调用recv()函数接收客户端的请求*/
        memset(buf,0,sizeof(buf));
        if((recvbytes = recv(client_fd,buf,BUFFER_SIZE,0)) == -1)//返回接收的字>节数
        {
                perror("recv");
                exit(1);
        }
        printf("Received a message:%s\n",buf);
        close(sockfd);
        exit(0);

}

客户端:

/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define PORT        4321
#define BUFFER_SIZE 1024

int main(int argc,char *argv[])
{
        int sockfd,sendbytes;
        char buf[BUFFER_SIZE];
        struct hostent *host;
        struct sockaddr_in serv_addr;

        if(argc < 3)
        {
                fprintf(stderr,"USAGE:./client Hostname(or ip address) Test\n");
                exit(1);
        }

        /*地址解析函数*/
        if((host = gethostbyname(argv[1])) == NULL)
        {
                perror("gethostbyname");
                exit(1);
        }

        memset(buf,0,sizeof(buf));
        sprintf(buf,"%s",argv[2]);

        /*创建socket*/
        if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
        {
                perror("socket");
                exit(1);
        }

        /*设置sockaddr_in 结构体中相关参数*/
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(PORT);
        serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
        bzero(&(serv_addr.sin_zero),8);

        /*调用connet函数主动发起对服务器端的连接*/
        if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr)) == -1)//serv_addr = 服务器端地址
        {                                                                                                                                                          //sizeof(struct sockaddr) = 地址长度
                perror("connect");
                exit(1);
        }

        /*发送消息给服务器端*/
        if((sendbytes = send(sockfd,buf,strlen(buf),0)) == -1)
        {
                perror("send");
                exit(1);
        }

        sleep(5);

        close(sockfd);
        exit(0);
}

猜你喜欢

转载自blog.csdn.net/q1449516487/article/details/81546103