网络编程它来了!!

1.网络体系结构

    OSI 七层模型 
    
    TCP/IP 四层模型

    网络体系结构:网络的分层结构及每层使用的协议集合
    
    (1)OSI(Open System Interconnection)七层模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
    
        7.应用层 //FTP、E-mail
        
        6.表示层 //数据格式定义,数据转换加密
        
        5.会话层 //建立通信进程的逻辑名字与物理名字之间的联系
        
        4.传输层 //差错处理、恢复,流量控制,提供可靠的数据传输
        
        3.网络层 //数据分组,路由选择
        
        2.数据链路层 //数据组成可发送和接收的帧
        
        1.物理层 //传输物理信号、接口、信号形式、速率

    
    http  //超文本传输协议  浏览网页
    ftp   //文件传输协议
    dns   //域名解析协议  www.baidu.com  //域名    110.242.68.3  //服务器IP地址  域名和地址 对应 

    www.net.cn  //注册域名的网址
    
    dhcp   //动态获取IP地址
    smtp   //简单邮件传输协议
    tcp
    udp
    icmp   //ping命令 使用的就是icmp协议
    ip         
    arp    //将ip地址解析电脑的物理地址 MAC地址 

    
    (2)TCP/IP四层模型(应用层、传输层、网络层、网络接口层)
    
    4.应用层

        ftp (File Transfer Protocol)  //文件传输协议  
        http(Hyper Text Transfer Protocol) //超文本传输协议 
        DNS (Domain Name Server)  //域名解析协议
        DHCP(Dynamic Host Configuration Protocol) //动态获取IP地址 
        SMTP(Simple Mail Transfer Protocol) //简单邮件传输协议

    3.传输层
    
        实现了应用程序间端对端的通信
        
        TCP
        UDP 
        
    2.网络层
    
        在传输数据的过程为数据包选择路由
        
        IP
        ICMP   //ping命令使用的icmp协议 
   ///
    1.网络接口
     
        ARP  //将IP解析为对应物理地址MAC
        
        将二进制流转换成数据帧,进行发送,数据帧是网络传输的最资本单元

    网络管理相关命令(重点)***********
    
        //如何查看自己windows电脑的IP地址
        
        ipconfig //查看windows IP地址 
        
        那么,我们Linux系统里面,如何查看自己的IP地址,命令 ifconfig
        
    
        1) ifconfig : 查看或者设置ip地址
        
            ifconfig   查看ip地址    
            
            sudo ifconfig eth0 192.168.31.3        //设置ip地址
            
            sudo service network-manager restart //重启网络服务

        2) ping  查看对方主机是否在线 
            
            //TTL 初始化值 64 128
            
            TTL ?  计算通信过程经过的路由的个数, 每经过一个路由ttl减1, 直到减到0, 数据发送失败
                
            TTL 值是 50  ----> 中间经过的路由个数 64 - 50 = 14 个路由
                
        3) nc (nc是netcat的简写,有着网络界的瑞士军刀美誉)  模拟 tcp服务器 tcp客户端  udp 服务器、客户端
        
            如果没有安装的话(sudo apt-get install netcat)
            主要功能是网络测试(测试端口使用情况,测试网络)
            启动tcp服务器端, 使用端口号 6666
            
                nc -l 6666     (6666 端口号)    -l 启动tcp服务器
    
            启动tcp客户端
            
                nc 127.0.0.1 6666  (要写服务器的ip地址和端口号)
                127.0.0.1  (环回地址  专门用来做网络程序测试用的  自发自收)    

            网络通信(两方  收  发)
            
                启动udp接收端
                
                    nc -ul 9999   (1 - 65535 之间)
                    
                启动udp发送端
                
                    nc -u 127.0.0.1 4444

///
2.linux下网络通信又称为socket编程,socket通信


2.1 IP地址(网络中的地址)

    //dos端查看自己的IP地址 
    
    Windows查看自己的IP地址:windows + r ---> cmd ----> ipconfig 
    
    Ubuntu中查看IP地址:ifconfig
    
    ping 192.168.0.5//查看对方主机是否在线
    
    IP地址是主机要与其他机器通信必须具有的一个IP地址
     
    1、 IP地址的两种表示方式

    1) 点分制 ("192.168.1.21"  字符串)
    
    2) 整型   0xC0A80115   //4个字节
        
        一个十六进制位 ---->  4个二进制位  4bit 
        
        8*4 ---> 32位 
        
        1个字节 8bit  
        
        32 / 8 = 4 ---> 4个字节就可以存储整型的IP地址


    网络通信中使用整型IP地址

    //两个函数,实现点分形式 和 整型形式 IP地址相互转换 
    
    点分形式的IP地址转为整型 inet_addr 
    整型的IP地址转换为点分 inet_ntoa 

    2、 inet_addr函数  //将点分形式的ip地址转换成整型的ip地址
    
  

 typedef unsigned int in_addr_t;
    
 in_addr_t inet_addr(const char *cp);

    功能:将点分制的ip地址转成整型ip地址
    
    const char *cp //保存的是 "192.168.1 21" 字符串的首地址,被转换的点分IP地址
    
    返回值:返回值就是整型的IP地址
    
inet_addr例子    

#include "my.h"

    //in_addr_t == unsigned int
    int main(int argc, const char *argv[])
    {
        //将点分形式的IP地址转换为整型的IP地址 "192.168.1.21" ---> 0x1501A8C0
        in_addr_t addr = inet_addr("192.168.1.21");
        printf("整型IP地址是%X\n",addr);//%X以十六进制的格式输出
        return 0;
    }

    //打印输出
    整型IP地址是1501A8C0 


    3、 inet_ntoa函数
    
  

 char *inet_ntoa(struct in_addr in);
    
    struct in_addr 
    {
        in_addr_t s_addr; //结构体成员变量就是 整型的IP地址 
    };

    
    功能:将整型的IP地址转换成点分格式的IP地址)
    
    返回值:char* 点分形式IP地址字符串的首地址"192.168.1.21"
    
inet_ntoa例子
//打印输出:

#include "my.h"

//in_addr_t == unsigned int
int main(int argc, const char *argv[])
{
    //将点分形式的IP地址转换为整型的IP地址 "192.168.1.21" ---> 0x1501A8C0
    in_addr_t addr = inet_addr("192.168.1.21");
    printf("整型IP地址是%X\n",addr);//%X以十六进制的格式输出
    //将整型的IP地址转换为点分形式的IP地址
    struct in_addr in; //定义一个结构体保存整型的IP地址
    in.s_addr = addr;//结构体变量的成员变量 来保存整型的IP地址
    char* p =(char*)inet_ntoa(in); //函数的参数,需要的是结构体
    printf("点分形式IP地址是%s\n",p);
    return 0;
}

4、 字节序转序
        
字节序

    大端模式:高位字节存储在低位地址中,低位字节存储在高位地址中(高对低,低对高)    
    小端模式:高位字节存储在高位地址中,低位字节存储在低位地址中(高对高,低对低)
    

高位字节 
低位字节 
高位地址 
低位地址 
int a = 0x12345678

0x12 高位字节 
0x78 低位字节 

0xaaaa0000 低位地址 
0xaaaa0003 高位地址 
    

---测试当前PC字节序 (通常为小端模式)

#include "my.h"

int main(int argc, const char *argv[])
{
    int a = 0x12345678;
    char* p = (char*)&a;
    printf("*(p+0) is %x\n",*(p+0)); //也可以不用循环,直接判断p+0 低位地址中存的内容是0x12 还是 0x78来判断
    int i;
    //用字符指针,将整型变量a每个字节存储的内容打印输出
    for(i = 0; i < 4; i++)
    {
        printf("*(p+%d) is %X\n",i,*(p+i));
    }

    return 0;
}

//打印输出结果:

*(p+0) is 78  // p+0 低位地址   0x78 低位字节
*(p+1) is 56  // p+1
*(p+2) is 34
*(p+3) is 12  // p+3 高位地址   0x12 高位字节 

所以当前主机字节序是小端模式 

(*****)网络字节序和主机字节序不同点,及相关转换函数。    

主机字节序:  通常为小端模式,高位字节存储在高位地址中,低位字节存储在低位地址中(高对高,低对低)

网络字节序:大端模式,高位字节存储在低位地址中,低位字节存储在高位地址中(高对低,低对高)    

n network 网络 
h host 主机
s short 
l long 
to //转 


//主机字节序转为网络字节序
htons() //将主机字节序的短整型转换为网络字节序 
htonl() //将主机字节序的长整型转换为网络字节序 

//网络转为主机字节序 
ntohs() //将网络字节序短整型转换为主机字节序 
ntohl() //将网络字节序的长整型转换为主机字节序ntohl()例子 
 

#include "my.h"

int main(int argc, const char *argv[])
{
    int a = 0x12345678;
    char* p = (char*)&a;
    printf("*(p+0) is %X\n",*(p+0));// 0xaaaa0000 - 0xaaaa0003  0x78 0x56 0x34 0x12
    //打印结果是 78

    int b = htonl(a);//将主机字节序的变量a转为网络字节序存储在变量b中
    char* q = (char*)&b;
    printf("*(q+0) is %X\n",*(q+0));// 0xbbbb0000 - 0xbbbb0003  0x12 0x34 0x56 0x78
    //打印结果是 12

    return 0;
}


///
3. UDP通信

    3.1 关于UDP通信
    
    (1)什么是UDP?
        
        udp是一个面向无连接、不可靠的传输层协议 
                    
    (2)UDP通信协议位于哪一层?
        
        传输层 
     
    (3)UDP通信的优点和缺点
    
        优点: 传输速度快         
        缺点: 传输过程中,不可靠,容易丢失数据

    (4)应用场合:
    
        UDP通常用于传输较大的文件
        
    3.2 UDP通信,接收端和发送端
    
分为接收端  和 发送端

接收端过程(4歩) 

(1) 创建一个socket数据报套接字SOCK_DGRAM
(2) 绑定自己的IP地址和端口号
(3) 接收数据 
(4) 关闭套接字 close(sockfd);


1 接收端

+++++++++++++++++++++++++++++++++++++++++++++++++

(1)创建socket数据报套接字

    socket函数头文件及函数原型

    ##功能:创建一个socket套接字   

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

   int socket(int domain, int type, int protocol);
   
   //调用:
   int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    参数说明:
    int domain //网络的通信协议 AF_INET  IPv4
    int type  //套接字的类型 
                SOCK_DGRAM 数据报套接字
    int protocol  //0 代表自动默认匹配相关协议

    返回值:

        成功:非负套接字描述符  ,socket套接字是一个特殊的文件描述符
        失败: -1 
    
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(2) 绑定自己的ip地址和端口号(将进程与IP地址和端口号关联)

    端口号:是用来识别应用程序的,如果A程序绑定55555端口号,
            那么网络里面来了一包55555的端口号数据,就发给A程序

  

 int bind(int sockfd, struct sockaddr *addr, int addrlen);
 #功能:绑定自己的IP地址和端口号
    
 struct sockaddr_in myaddr = { 0 };//用来保存自己的IP地址和端口号 
    
 myaddr.sin_family = AF_INET;//family协议选择IPv4
 myaddr.sin_port = htons(4444);//端口号 
 myaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //"127.0.0.1"IP地址是主机环回地址

    myaddr 类型是 struct sockaddr_in 
    &myaddr 类型是 struct sockaddr_in*
    函数的参数需要的是 struct sockaddr * 
    所以函数的参数需要将&myaddr 强制类型转换 (struct sockaddr*)&myaddr                        
    //为什么我们定义的结构体类型和函数参数上的类型不一样
    bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr))

    参数说明:

        int sockfd //上一步,socket函数的返回值 
        struct sockaddr *addr //用来保存自己的IP地址和端口号的结构体的首地址
        int addrlen //IP地址和端口号的长度
        
    返回值:

        成功: 0 
        失败: -1

    //struct sockaddr 此结构体在网络通信中主要保存 ip地址 和 端口号 

    //此结构体对IP和端口号赋值很麻烦    

struct sockaddr
    {
        short sa_family;     //AF_INET (tcp/ip通信)
        char sa_data[14];    //字符数组有14个字节  1-2字节 端口号
                               3-6     ip地址       7-14    预留
    };

    因为  struct sockaddr  存储数据不方便,所以,linux又定义了下面这个结构体    

    struct sockaddr_in
    {
        short sin_family; //AF_INET  //2
        short sin_port; //端口号     //2
        struct in_addr sin_addr;    //struct in_addr   4  s_addr //IP地址
        unsigned char sin_zero[8];    //预留
    };

    struct in_addr {
                   in_addr_t s_addr;
               };

    myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    因为struct scokaddr 赋值麻烦,所以又定义了一个 struct sockaddr_in ,专门用来赋值的,网络通信时,强制
    将struct sockaddr_in转换成struct sockaddr
    端口号:是用来识别应用程序的,如果A程序绑定55555端口号,那么网络里面来了一包55555的端口号数据,就发给A程序

    有些应用的端口号是固定的

    ftp    21        File Transfer Protocol 
    http   80        HyperText Transfer Protocol)
    DNS    53        Domain Name System
    DHCP   67        Dynamic Host Configuration Protocol

    我们写的应用程序尽量用 > 1023的端口号, 

    127.0.0.1   环回地址(自发自收,用于网络程序测试用的)
 
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(3) 接收数据(阻塞的方式接收数据)

  

 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

    #功能:阻塞 接收数据
    
    //调用 
    
  

 //不想保存对方的IP地址和端口号
    char buf[100] = { 0 };//用来保存接收到的数据
    recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
    //想要保存对方的IP地址和端口号
    struct sockaddr_in youaddr = { 0 };//用来保存对方的IP地址和端口号 
    int len = sizeof(youaddr);
    recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&youaddr, &len);

    参数说明:
 
        int sockfd //第一步socket函数的返回值
        void *buf  //接收到的数据存放的位置
        size_t len  //接收数据的最大字节数
        int flags //0 阻塞接收

struct sockaddr *src_addr  //用来保存对方的IP地址和端口号 
socklen_t *addrlen //对方iP地址和端口号的长度

    返回值:

        成功: 实际接收字节数 
        失败: -1

(4) 关闭socket套接字    

close(sockfd);

///
2 发送端

    1. 创建一个数据报套接字socket()   SOCK_DGRAM

        (发送的前提条件是你已经知道了接收方的电话号码(也就是接收方的IP地址和端口号))
    2. 发送数据 sendto() 
    3. 关闭套接字close() 
    

(1)创建一个socket数据包套接字 

    同接收端 
    
(2)指定接收方的IP地址和端口号     

struct sockaddr_in toaddr; //用来保存接收方的IP地址和端口号 

//发送方是当前的他,   toaddr里面存的是对方的手机号即 她的手机号
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(6666);
//toaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
toaddr.sin_addr.s_addr = inet_addr("192.168.31.179");

(3) 发送数据    

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

char buf[100] = "hello";
//在调用sendto函数的时候,数据发送给谁,取决于 toaddr里面装的是谁的IP地址和端口号
sendto(sockfd, buf, strlen(buf)+1, 0, (struct sockaddr*)&toaddr, sizeof(toaddr));

    功能:发送数据 

    函数说明:

        int sockfd   //socket函数的返回值  
        const void *buf  //发送数据存放的位置 
        size_t len //实际发送的字节数 
        int flags // 通常为0 
        const struct sockaddr *dest_addr  //接收方的IP地址和端口号 
        socklen_t addrlen //IP地址和端口号的长度

    返回值:

        成功:实际发送的字节数 
        
        失败: -1

(4)关闭套接字

    close(sockfd)
recv.c

#include "my.h"

int main(int argc, const char *argv[])
{
    char buf[100] = { 0 };//用来保存接收到的数据
    //1.创建一个数据报套接字 AF_INET IPv4   SOCK_DGRAM 数据报套接字 0 默认匹配相关协议
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        perror("sockfd failed!!\n");
        exit(-1);
    }
    printf("sockfd is %d\n",sockfd);
    //2.绑定自己的IP地址和端口号
    struct sockaddr_in myaddr = { 0 };//用来保存接收端自己的IP地址和端口号
    myaddr.sin_family = AF_INET;//IPv4
    myaddr.sin_port = htons(6666);//将主机字节序的端口号转为网络字节序
    myaddr.sin_addr.s_addr = inet_addr("192.168.31.173");//127.0.0.1主机环回地址
    int ret = bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr));
    if(ret == -1)
    {
        perror("bind failed!!\n");
        exit(-1);
    }
    //3.阻塞等待接收数据
    recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);//第四个参数0代表,阻塞接收数据
    printf("recv is %s\n",buf);

    //4.关闭套接字
    close(sockfd);
    return 0;
}

用nc命令来模拟发送端 
nc -u 127.0.01 6666
nc -u 192.168.31.173 6666

//send.c

#include "my.h"

int main(int argc, const char *argv[])
{
    char buf[100] = { 0 };
    struct sockaddr_in toaddr = { 0 };//用保存接收方的IP地址和端口号
    //toaddr里面存的是谁的IP地址和端口号,就是给谁发送数据
    //1.创建一个数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        perror("socket failed");
        exit(-1);
    }
    //发送之前,提前知道接收当的IP地址和端口号 
    toaddr.sin_family = AF_INET;
    toaddr.sin_port = htons(6666);
    toaddr.sin_addr.s_addr = inet_addr("192.168.31.173"); //toaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //2.发送数据,但是发送数据之前,你要提前知道接收方的IP地址和端口号
    
    gets(buf);
    sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&toaddr,sizeof(toaddr));

    //3.关闭套接字
    close(sockfd);
    return 0;
}


gcc -o send send.c 
//修改后的recv.c

recv.c 通过recvfrom的第四个参数,获取到发动方的IP地址和端口号

#include "my.h"

int main(int argc, const char *argv[])
{
    char buf[100] = { 0 };//用来保存接收到的数据
    //1.创建一个数据报套接字 AF_INET IPv4   SOCK_DGRAM 数据报套接字 0 默认匹配相关协议
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        perror("sockfd failed!!\n");
        exit(-1);
    }
    printf("sockfd is %d\n",sockfd);
    //2.绑定自己的IP地址和端口号
    struct sockaddr_in myaddr = { 0 };//用来保存接收端自己的IP地址和端口号
    myaddr.sin_family = AF_INET;//IPv4
    myaddr.sin_port = htons(6666);//将主机字节序的端口号转为网络字节序
    myaddr.sin_addr.s_addr = inet_addr("192.168.31.179");//127.0.0.1主机环回地址
    //myaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 这个宏,可以自动获取本机的IP地址
    
    //接收端,指定本机自己的IP地址有两种方法
    //(1) 直接用inet_addr函数指定具体的IP地址myaddr.sin_addr.s_addr = inet_addr("192.168.31.179");//127.0.0.1主机环回地址
    //(2) 用INADDR_ANY这个宏,自动获取本机的IP地址 myaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 这个宏,可以自动获取本机的IP地址
    
    int ret = bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr));
    if(ret == -1)
    {
        perror("bind failed!!\n");
        exit(-1);
    }
    //3.阻塞等待接收数据
    struct sockaddr_in youaddr = { 0 };//用来保存发送方的IP地址和端口号
    int len = sizeof(youaddr);
    while(1)
    {
        //如果将第四个参数和第五个参数赋值为NULL,那么我的心里,不想知道给发送消息的那个人的IP地址和端口号
        //recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);//第四个参数0代表,阻塞接收数据
        recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&youaddr, &len);//第四个参数0代表,阻塞接收数据
        //inet_ntoa 为了将youaddr里面的保存的整型的IP地址转换为点分
        //ntohs为了将youaddr里面的网络字节序的端口号转换为主机字节序的端口号进行打印
        printf("fromIP:%s-%d %s\n",(char*)inet_ntoa(youaddr.sin_addr),ntohs(youaddr.sin_port),buf);
        memset(buf, 0, sizeof(buf));
    }
    //4.关闭套接字
    close(sockfd);
    return 0;
}

/send.c//

#include "my.h"

int main(int argc, const char *argv[])
{
    char buf[100] = "hello";//即将发送的数据
    //1.创建一个数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        perror("scoket failed");
        exit(-1);
    }
    //2.给接收端发送数据,发送数据的前提条件,是已经知道了接收方的IP地址和端口号
    struct sockaddr_in toaddr = { 0 };//用来保存接收端的IP地址和端口号
    toaddr.sin_family = AF_INET;
    toaddr.sin_port = htons(6666);
    toaddr.sin_addr.s_addr = inet_addr("192.168.31.179");
    while(1)
    {
        printf("请输入要发送的话:\n");
        gets(buf);
        sendto(sockfd, buf, strlen(buf)+1, 0, (struct sockaddr*)&toaddr,sizeof(toaddr));

    }
    //3.关闭套接字
    close(sockfd);
    return 0;
}

华清远见的学习是由浅入深,让我这样一个小白也渐渐熟练了起来!加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值