Linux网络编程

在学习网络编程之前,先了解一下OSI模型,以及TCP/IP协议和一些基础的知识

 

OSI模型(Open System Interconnection model 开放系统互联模型)

 

 这是一个理想化的模型,实际上的TCP/IP协议跟这个模型还不太一样。

分别简单的理解一下这七层模型的意思

物理层:以二进制数据形式在物理媒体上传输数据,也就是最底层的硬件。

    比如各种接口 RS-232 RJ-45 都属于该层

数据链路层:通过物理网络链路提供数据传输,可以理解为硬件和硬件之间的通信

    比如IIC SPI就是属于该层

网络层:负责在源和终点之间建立连接,一般包括网络寻径,选择路由

传输层:提供端对端的网络数据流服务。后面将提到的TCP/IP协议也是属于这两层

会话层:解除或建立与别的接点的联系

表示层:提供多种功能用于应用层数据编码和转化,以确保应用层发送的消息能被其他人识别

    包括数据格式转化和加密 压缩。

应用层:这里的应用层并非我们pc上所使用的软件,而是向应用服务提供网络资源的API

 

 

在TCP/IP协议中,并没有完全照般OSI模型,而是对他进行了整合和拆分

一般我们使用的是一个比较通用的四层的模型

和刚才的模型对比一下:

 

 在实际使用中,我们把他分为:应用层、传输层、网络层、和网络接口层(硬件层)

网络接口层:提供TCP/IP协议的数据结构和实际的硬件之间的接口

网络层:IP协议、RIP协议,负责数据包装 寻址和路由

传输层:TCP可靠的数据流运输服务 UDP不可靠的数据报服务

应用层:FTP文件传输协议、HTTP超文本传输协议、Telent远程终端协议、等

 

 

IP地址

Internet Protocol Address  互联网协议地址

每台联网的设备都有一个IP地址,IP地址是一个32位数,常被分成4个4字节的数

ip地址被分为下面几类:

 

 

a.b.c.d 的形式,其中abcd都是 0-255的整数

那么问题就来了,一串这样的整数,早晚有一天会用完,毕竟255*255*255*255 = 4228250625

40亿,且不说因为格式等方面的问题,实际分配出去不足40亿。。

光是目前的手机电脑等能联网的设备,也肯定超出了40亿了。

而事实上在2011年IPV4的地址就已经用尽了。

为了解决这个问题,就诞生了IPV6。

IPV6也就是第六版的互联网协议。其地址长度为128位,比32位的IPV4拥有更多的可分配资源。

我们可以使用 ifconfig 查看机器的IP地址

 

 

 

 

端口号

前面说的IP地址是你计算机的地址,你一台计算机里面肯定运行了很多程序。

每个应用程序就对应了不同的端口号

同时制定了正确的IP地址和端口号,才能准确的发送数据

UNIX操作系统因具有运行稳定、系统要求低、安全性高,而得到广泛应用。其伯克利套接字,发展较早,具有鲜明特点,例如:UNIX系统有保留端口号的概念。只有具有超级用户特权的进程才允许给它自己分配一个保留端口号,这些端口号介于1~1023之间,一些应用程序将它作为客户与服务器之间身份认证的一部分。大多数TCP/IP实现给临时端口分配1024~5000之间的端口号。大于5000的端口与是为其他服务器预留的(Internet上并不常用的服务)  。

 

 

TCP、UDP协议

TCP  Transmission Control Protocol  传输控制协议

UDP  User Datagram Protocol     用户数据报协议

TCP和UDP都可以使用IPV4或IPV6。

UDP不保证数据包会到达目的地,不保证数据先后顺序跨网络之后保持不变,也不保证只到达一次。

TCP也不能保证发送的数据一定会被对方端接收,但是他会不断的重试。

 

TCP在连接时需要进行三次握手,前面的博客有动图演示讲解

TCP三次握手四次挥手

形象一点说,就是这样一个过程:

1、屌丝(客户端)处于自闭状态,女神(服务器)处于等待状态

2、屌丝问女神”我要跟你处对象,是否同意“,同时屌丝变成发送状态

3、女神收到消息,回复屌丝“我同意,那么我要跟你处对象,你是否同意?”,然后变为接收状态

4、屌丝收到消息,变为“恋爱状态”,回复女神“我同意,开始处对象吧

5、女神收到消息,也变为恋爱状态,于是开始了一段不可描述的故事。。。。

有一天。。。他们要分手了。。

1、屌丝付出了自己所有的爱,然后跟女神说“我要跟你分手”,并把自己变为等待状态

2、女神收到爱和消息,回复“同意分手”,然后又把自己所有的爱给了屌丝,又一次确认“我要和你分手

3、屌丝收到爱和消息,回复“同意分手”,后进入自闭状态。

4、女神收到应答,也进入自闭状态。。。

 

上面例子中用用下划线标注出来的,就是建立连接的三次握手和数据发送完成后的四次挥手。

 

 

 

下面终于进入了网络编程的正题:套接字API的使用

创建套接字

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

       domain:

       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       AF_IPX              IPX - Novell protocols
       AF_NETLINK          Kernel user interface device     netlink(7)
       AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
       AF_AX25             Amateur radio AX.25 protocol
       AF_ATMPVC           Access to raw ATM PVCs
       AF_APPLETALK        AppleTalk                        ddp(7)
       AF_PACKET           Low level packet interface       packet(7)
       AF_ALG              Interface to kernel crypto API


       protocol:

       SOCK_STREAM     TCP

       SOCK_DGRAM      UDP

       SOCK_SEQPACKET  为最大长度固定的数据报提供有序、可靠、基于双向连接的数据传输路径

       SOCK_RAW        原始套接字

       SOCK_RDM        提供不保证排序的可靠数据报层。

成功返回非负的套接字描述符

失败返回 -1

 

绑定套接字和服务器地址

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);


       sockfd    套接字文件描述符

       addr     服务器地址信息
               struct sockaddr {
                   sa_family_t sa_family;
                   char        sa_data[14];
               }

               但实际使用中对于IPV4我们常用这个结构
               struct sockaddr_in {
                    sa_family_t    sin_family;         IPV4对应AF_INET
                    u_int16_t      sin_port;           端口号
                    struct in_addr sin_addr;           IP地址
                };

                /* Internet address. */
                struct in_addr {
                    u_int32_t      s_addr;              IP地址
                };

       addrlen addr的长度 sizeof(struct sockaddr)

成功返回 0  失败返回 -1

主要用与在TCP中的连接

 

端口号为0时,自动分配一个临时端口

IP地址填入 INADDR_ANY,自动选择通配地址

 

 

监听模式

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

       sockfd        套接字文件描述符

       backlog        监听队列长度(等待连接的客户端的个数)缺省值20

 

 

等待客户端连接

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

        int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);   

        sockfd        服务器套接字文件描述符

        addr         客户端信息地址

        addrlen     addr的长度    

成功返回连接的客户端的套接字文件描述符

失败返回 -1

 

接收数据

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

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
       //告诉调用者是谁发来的数据
       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
       //无连接的套接字
       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);


       sockfd            套接字文件描述符

       buf                存放接收的数据

       len                期望接收的数据长度

       flags            0

       src_addr            源机的IP地址端口号

       addrlen            addr的长度

       msg
                   struct iovec {                    /* Scatter/gather array items */
                       void  *iov_base;              /* Starting address */
                       size_t iov_len;               /* Number of bytes to transfer */
                   };

                   struct msghdr {
                       void         *msg_name;       /* optional address */
                       socklen_t     msg_namelen;    /* size of address */
                       struct iovec *msg_iov;        /* scatter/gather array */
                       size_t        msg_iovlen;     /* # elements in msg_iov */
                       void         *msg_control;    /* ancillary data, see below */
                       size_t        msg_controllen; /* ancillary data buffer len */
                       int           msg_flags;      /* flags on received message */
                   };

成功返回接到数据的实际长度,失败返回 -1

recvfrom() 在参数中添加了源机的地址返回,可以得到是从哪里接收到的数据地址信息。

如果把此函数的第五个参数置空NULL,则和recv() 函数相同

 

 

发送数据

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

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

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

       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

send函数参数和recv完全一致,此处不再赘述。

成功返回发送数据的实际长度,失败返回 -1

sendto() 指定地址发送数据,

sendto()  recvfrom() 主要用于UDP的连接

 

 

 

客户端连接服务器

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数为服务器地址信息

成功返回 0  失败返回 -1

connect() 函数不阻塞,所以在使用之前要确保服务器已经进入等待连接状态。

 

除此之外还有一些工具函数,地址格式的转化 大小端的转化

这里就涉及到一个字节序的问题。

比如我现在要存入一个数据 0x12345678.

有两种存入方式(假设地址从左到右变高):

1、| 12 | 34 | 56 | 78 |  这种存入方式称为大端序

2、| 78 | 56 | 34 | 12 |  这种存入方式称为小端序

网络传输中使用的是网络字节序,也就是大端序。

而在ARM或X86上都是小端序,所以要进行一个转换

       #include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);    将整型变量从主机字节顺序转变成网络字节顺序

       uint16_t htons(uint16_t hostshort);    将主机的无符号短整形数转换成网络字节顺序

       uint32_t ntohl(uint32_t netlong);    将一个无符号长整形数从网络字节顺序转换为主机字节顺序

       uint16_t ntohs(uint16_t netshort);    将一个16位数由网络字节顺序转换为主机字节顺序

       h     host         主机
       n    network       网络

 

 

 

使用TCP协议的流程图

 

 

UDP协议流程图

 

 

 

试用一下:

服务器代码:

/*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> 
#include <netinet/ip.h>

#define SERVERPOST     1234



int main(int argc, char const *argv[])
{
    struct sockaddr_in serverAddr,clientAddr;
    int serverSockfd,clientSockfd;
    char recvBuf[50];
    char sendBuf[50];
    int ret;
    socklen_t  addrLen;

    //创建服务器套接字
    serverSockfd = socket(AF_INET,SOCK_STREAM,0);
    if(serverSockfd == -1)
    {
        perror("socket");
        exit(-1);
    }
    printf("server socketfd is %d \n",serverSockfd);

    //给定端口号和IP地址,绑定套接字
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVERPOST);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = bind(serverSockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    if(ret == -1)
    {
        perror("bind");
        exit(-1);
    }
    printf("bind successed\n");


    //进入监听状态
    ret = listen(serverSockfd,2);
    if(ret == -1)
    {
        perror("linsten");
        exit(-1);
    }

    //接受客户端的连接请求,返回值为客户端套接字描述符,后面就使用这个描述符作为参数传递数据
    clientSockfd = accept(serverSockfd,(struct sockaddr*)&clientAddr,&addrLen);
    if(clientSockfd == -1)
    {
        perror("accept");
        exit(-1);
    }
    printf("connect successed\n");


    close(clientSockfd);
    close(serverSockfd);

    return 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> 
#include <netinet/ip.h>

#define SERVERPOST     1234


int main(int argc, char const *argv[])
{
    struct sockaddr_in serverAddr,clientAddr;
    int serverSockfd,clientSockfd;
    char recvBuf[50];
    char sendBuf[50];
    int ret;

    //创建客户端的套接字
    clientSockfd = socket(AF_INET,SOCK_STREAM,0);
    if(clientSockfd == -1)
    {
        perror("socket");
        exit(-1);
    }
    printf("client socketfd is %d \n",clientSockfd);


    //给定端口号和IP,使用端口号、IP、套接字描述符  连接服务器
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVERPOST);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = connect(clientSockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    if (ret == -1)
    {
        perror("connect");
        exit(-1);
    }
    printf("connect successed\n");

    close(clientSockfd);

}

 

观察得到现象:

 

 

 

然后我们再加入发送接收数据函数

服务器:

/*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> 
#include <netinet/ip.h>

#define SERVERPOST     1234



int main(int argc, char const *argv[])
{
    struct sockaddr_in serverAddr,clientAddr;
    int serverSockfd,clientSockfd;
    char recvBuf[50];
    char sendBuf[50];
    int ret;
    socklen_t  addrLen;
    int recvDataLen,sendDataLen;
    int i = 5;

    //
    serverSockfd = socket(AF_INET,SOCK_STREAM,0);
    if(serverSockfd == -1)
    {
        perror("socket");
        exit(-1);
    }
    printf("server socketfd is %d \n",serverSockfd);

    //
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVERPOST);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = bind(serverSockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    if(ret == -1)
    {
        perror("bind");
        exit(-1);
    }
    printf("bind successed\n");


    //
    ret = listen(serverSockfd,2);
    if(ret == -1)
    {
        perror("linsten");
        exit(-1);
    }

    //
    clientSockfd = accept(serverSockfd,(struct sockaddr*)&clientAddr,&addrLen);
    if(clientSockfd == -1)
    {
        perror("accept");
        exit(-1);
    }
    printf("connect successed\n");

    strcpy(sendBuf,"hello i am server");

    while(i>0)
    {
        recvDataLen = recv(clientSockfd,recvBuf,sizeof(recvBuf),0);
        printf("receive data : %s\n", recvBuf);
        printf("receive data len : %d\n", recvDataLen);

        printf("-----------------------------------------\n");
        sleep(1);

        sendDataLen = send(clientSockfd,sendBuf,sizeof(sendBuf),0);
        printf("send data : %s\n", sendBuf);
        printf("send data len : %d\n", sendDataLen);

        i--;
    }


    close(clientSockfd);
    close(serverSockfd);

    return 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> 
#include <netinet/ip.h>

#define SERVERPOST     1234


int main(int argc, char const *argv[])
{
    struct sockaddr_in serverAddr,clientAddr;
    int serverSockfd,clientSockfd;
    char recvBuf[50];
    char sendBuf[50];
    int ret;
    int recvDataLen,sendDataLen;
    int i = 5;

    //
    clientSockfd = socket(AF_INET,SOCK_STREAM,0);
    if(clientSockfd == -1)
    {
        perror("socket");
        exit(-1);
    }
    printf("client socketfd is %d \n",clientSockfd);


    //
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVERPOST);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = connect(clientSockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    if (ret == -1)
    {
        perror("connect");
        exit(-1);
    }
    printf("connect successed\n");

    strcpy(sendBuf,"hello i am clinet");

    while(i > 0)
    {
        sendDataLen = send(clientSockfd,sendBuf,sizeof(sendBuf),0);
        printf("send data : %s\n", sendBuf);
        printf("send data len : %d\n", sendDataLen);

        printf("-----------------------------------------\n");
        sleep(1);

        recvDataLen = recv(clientSockfd,recvBuf,sizeof(recvBuf),0);
        printf("receive data : %s\n", recvBuf);
        printf("receive data len : %d\n", recvDataLen);

        i--;
    }

    close(clientSockfd);

}      

 

观测结果如下:

服务器:

 

 

 客户端:

 

 

 

这样看来网络通信成功。

套接字的出现 就是为了解决不同设备上不同进程之间的通信。

 

接下来试一下UDP:

和上面不同的,UDP也有绑定,但是没有监听等待和连接过程,因为他是指定地址发送和接收数据。

那我们改一下刚才的代码。。

服务器:

/*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> 
#include <netinet/ip.h>

#define SERVERPOST     1234



int main(int argc, char const *argv[])
{
    struct sockaddr_in serverAddr,clientAddr;
    int serverSockfd,clientSockfd;
    char recvBuf[50];
    char sendBuf[50];
    int ret;
    socklen_t  addrLen = sizeof(clientAddr);//**这里注意**必须这样初始化 否则报错
    int recvDataLen,sendDataLen;
    int i = 5;

    //创建套接字,以UDP的方式
    //serverSockfd = socket(AF_INET,SOCK_STREAM,0);
    serverSockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(serverSockfd == -1)
    {
        perror("socket");
        exit(-1);
    }
    printf("server socketfd is %d \n",serverSockfd);

    //绑定,和TCP相同
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVERPOST);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = bind(serverSockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    if(ret == -1)
    {
        perror("bind");
        exit(-1);
    }
    printf("bind successed\n");

/*
    //UDP中没有监听和等待连接的过程 所以listen accept 不需要
    ret = listen(serverSockfd,2);
    if(ret == -1)
    {
        perror("linsten");
        exit(-1);
    }

    //
    clientSockfd = accept(serverSockfd,(struct sockaddr*)&clientAddr,&addrLen);
    if(clientSockfd == -1)
    {
        perror("accept");
        exit(-1);
    }
    printf("connect successed\n");

*/

    strcpy(sendBuf,"hello i am server");

    while(1)
    {
        recvDataLen = recvfrom(serverSockfd,recvBuf,sizeof(recvBuf),0,(struct sockaddr*)&clientAddr,&addrLen);
        printf("receive data : %s\n", recvBuf);
        printf("receive data len : %d\n", recvDataLen);
        printf("-----------------------------------------\n");
        
        sendDataLen = sendto(serverSockfd,sendBuf,sizeof(sendBuf),0,(struct sockaddr*)&clientAddr,addrLen);
        printf("send data : %s\n", sendBuf);
        printf("send data len : %d\n", sendDataLen);
        if (sendDataLen == -1)
        {
            perror("sever send");
        }

    }

    close(clientSockfd);
    close(serverSockfd);

    return 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> 
#include <netinet/ip.h>

#define SERVERPOST     1234


int main(int argc, char const *argv[])
{
    struct sockaddr_in serverAddr,clientAddr;
    int serverSockfd,clientSockfd;
    char recvBuf[50];
    char sendBuf[50];
    int ret;
    int recvDataLen,sendDataLen;
    int i = 5;
    socklen_t  addrLen = sizeof(serverSockfd);//**注意** 这里必须这样初始化

    //创建客户端套接字
    //clientSockfd = socket(AF_INET,SOCK_STREAM,0);
    clientSockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(clientSockfd == -1)
    {
        perror("socket");
        exit(-1);
    }
    printf("client socketfd is %d \n",clientSockfd);


    //UDP服务器没有监听等待,所以也不需要客户端发送连接请求,直接发数据过去即可
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVERPOST);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*    ret = connect(clientSockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    if (ret == -1)
    {
        perror("connect");
        exit(-1);
    }
    printf("connect successed\n");
*/
    strcpy(sendBuf,"hello i am clinet");

    while(1)
    {
        sendDataLen = sendto(clientSockfd,sendBuf,sizeof(sendBuf),0,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
        printf("send data : %s\n", sendBuf);
        printf("send data len : %d\n", sendDataLen);

        printf("-----------------------------------------\n");
        

        recvDataLen = recvfrom(clientSockfd,recvBuf,sizeof(recvBuf),0,(struct sockaddr*)&serverAddr,&addrLen);
        printf("receive data : %s\n", recvBuf);
        printf("receive data len : %d\n", recvDataLen);

    }

    close(clientSockfd);

}      

 

UDP的连接是通过客户端发送数据,这样服务器接收数据的同时也接到了客户端的地址信息,继而通过这个地址信息进行后面的数据交流。

这里的 sendto 和 recvfrom 函数,在使用时要注意,接收就填写接收端的sockfd,发送就填写发送端的sockfd

后面参数中的地址,recvfrom从谁那里收数据就填谁,sendto数据要发给谁就填谁

 

posted @ 2020-06-03 17:05  祁峰_1024  阅读(536)  评论(0编辑  收藏  举报