五、Linux——网络

五、Linux——网络

5.1相关概念

IP地址

IP地址主要用于标识网络主机、其他网络设备(如路由器)的网络地址。简单说,IP地址用于定位主机的网络地址。

端口号

在网络通信中,IP地址用于标识主机网络地址,端口号可以标识主机中发送数据、接收数据的进程。简单说:端口号用于定位主机中的进程,

Socket 套接字
1.TCP : 面向连接 如:A 打电话 B (可靠)
2.UDP: 面向报文 如:A 发短信给 B 数据量大 (不可靠)

TCP/UDP协议对比

  • TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前 不需 要建立连接。
  • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
  • TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP电话,实时视频会议等)。
  • 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
  • TCP首部开销20字节;UDP的首部开销小,只有8个字节。
  • TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。

字节序

字节序是指多字节数据在计算机内存中存储或者网络传输时各自字节的存储顺序。

常见字节序分类

  • Little endian(小端字节序) :将低序字节存储在起始地址
  • Big endian (大端字节序):将高序字节存储在起始地址

例子:在内存中双字0x01020304(DWORD)的存储方式

内存地址4000&4001&4002&4003

​ LE 04 03 02 01

​ BE 01 02 03 04

5.2 Socket编程步骤

在这里插入图片描述

  • 服务器首先启动,稍后某个时刻客户启动,它试图连接到服务器。
  • 客户通过 send() 函数给服务器发送一段数据,服务器通过 recv() 函数接收客户发送的数据,并处理该请求,之后通过 send() 函数给客户发回一个响应。
  • 这个过程一直持续下去,直到客户关闭 Socket 连接,从而给服务器发送一个 EOF(文件结束)通知。服务器收到后接着也关闭与之相应的 Socket,然后结束运行或者等待新的客户连接。

5.3 相关API

服务器端

  1. 连接协议
   int socket(int domain ,int type ,int protocol);

参数说明:

domain :指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)

AF_INET IPV4因特网域
AF_INET IPV6因特网域
AF_UNIXUnix域
AF_ROUTE路由器套接字
AF_KEY密钥套接字
AF_UNSPEC未指定

type:指定socket的类型

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

protocol :通常赋值“0 ”
0 选择 type 类型对应的默认协议

IPPROTO_TCPTCP传输协议
IPPROTO_UDPUDP传输协议
IPPROTO_SCTPSCTP传输协议
  1. IP地址和端口号API

地址准备好

bind()函数:IP号端口号相应描述字赋值函数

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

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

功能: 用于绑定IP地址和端口号到socketfd

参数说明:

sockfd : 是一个socket描述符
addr :是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同

实际中常用scockaddr_in结构体把sockaddr替换掉

struct   scockaddr_in {
       sa_family_t     sin_family;   //协议族
       in_port_t         sin_port;       //端口号
       struct    in_addr   sin_addr;    //IP地址结构体
       unisgned   char   sin_zero[8]; 
      //填充,没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换
};
//ipv4 对应的是:
struct  sckaddr{
        unisgned short     as_family;   //协议族
        char                        sa_data[14];  //IP+端口
}
  1. 地址转换API
int inet_aton(const char* straddr , straddr ,struct in_addr *adddrp);

把字符串形式的“192.168.1.123”转为网络能识别的格式

char* inet_ntoa(struct in_addr inaddr);

把网络格式的IP地址转化为字符串形式

  1. 监听API

listen()函数 : 监听设置函数

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

int listen(int sockfd, int backlog);

功能:

  • 设置能处理的最大连接数,listen()并未开始接受连线,只是设置sockect的listen模式,listen函数只用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后**响应该连接请求,**并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。
  • 主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
  • 内核为任何一个给定监听套接字维护两个队列:
    • 未完成连接队列,每个这样的SYN报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于 SYN_REVD状态
    • 已完成连接队列,每个已完成 TCP三次握手过程的客户端对应其中一项。这些套接字处于ESTABLISHED状态;

参数说明:

sockfd: sockfd是socket系传调用返回的服务器端socket描述符

backlog: backlog指定在请求队列中允许的最大请求数

  1. 连接API

accept()函数

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

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

功能:accept 函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠

参数说明:

sockfd: sockfd是socket系统调用返回的服务器端socket 描述符,是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在

addr: 用来返回已连接的对端(客户端)的协议地址

addrled: 客户端地址长度

返回值

**该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,**accept函数接受一个客户端请求后会返回一个新的SOCKFD值,当有不同的客户端同时有不同请求时,会返回不同的SOCKFD的值。这个不同的值和建立SOCKET 时生成的SOCKFD还是不同的。服务器与客户端之间的通信就是在这些不同的SOCKFD上进行的。

而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。

  1. 数据收发API

套接字通信中进行字节读取函数: read() , write()。与 I/O中的读取函数略有区别,因为它们输入或输出的字节数比可能比请求的少

ssize_t write(int fd, const void*buf,sizet nbytes);
ssize_t read(int fd,void *buf,size_t nbyte)
  1. 数据收发第二套API

在TCP套接字上发送数据函数:有连接

ssize_t send(int s,const void *msg,size_t len,int flags);

//包含3要素:套接字s,待发数据msg,数据长度len
//函数只能对处于连接状态的套接字使用,参数s为已建立好连接的套接字描述
//符,即accept函数的返回值
//参数msg指向存放待发送数据的缓冲区
//参数len为待发送数据的长度,参数flags为控制选项,一般设置为0

在TCP套接字上接收数据函数:有连接

ssize_t recv(int s,void *buf,size_t len,int flags);

//包含3要素:套接字s,接收缓冲区buf,长度len
//函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收
//数据并保存到参数buf所指定的缓冲区
//参数len则为缓冲区长度,参数flags为控制选项,一般设置为0

  1. 客户端的connect函数

connect()函数:客户机连接主机

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

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

功能:该函数用于绑定之后的client 端(客户端),与服务器建立连接参数

参数说明:

sockfd:是目的服务器的 sockect 描述符

addr :是服务器端的IP 地址和端口号的地址结构指针

addrlen:地址长度常被设置为sizeof(struct sockaddr)

返回值:

成功返回0,遇到错误时返回-1,并且errno 中包含相应的错误码

5.4 socket服务端代码实现

服务器建立——可连接代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
//#include<linux/in.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    int s_fd;
    
    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;
    
    memset(&s_addr,0,sizeof(struct sockaddr_in));//清空数据 
    memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket

    s_fd = socket(AF_INET ,SOCK_STREAM,0);
    if(s_fd == -1){
           perror("socket");
           exit(-1);
     }

    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(8989);
    inet_aton("127.0.0.1", &s_addr.sin_addr);  //inet_aton 把字符串形式的“127.0.0.1”转为网络能识别的格式的API

//2.bind

    bind(s_fd,(struct sockaddr *)&s_addr ,sizeof(struct sockaddr_in));

//3.listen

    listen(s_fd,10);   //不断监听外面是否有数据  ,listen配置完后,马上会结束
    int clen = sizeof(struct sockaddr_in);

//4.accept

    int c_fd = accept(s_fd , (struct sockaddr *)&c_addr , &clen);  
//通过套接字连上客户端,后续对连接选项都是用返回值 c_fd  来操作,s_fd 可能还需要接收其他人连接。
     if(c_fd == -1){
               perror("accept");
     }
     printf("get connect%s\n",inet_ntoa(c_addr.sin_addr));
//5.read
//6.write
//7.close

printf("connect\n");
while(1);

return 0;
}

注意1:

在 cd /usr/include/ 下收索结构体 struct sockaddr_in grep “struct

sockaddr_in {” *-nir
*-nir表示在当前目录下递归的 找,r是递归 n是显示行号,i是不区分大小写

在这里插入图片描述

经过查找,在头文件加上#include<linux/in.h>

注意2:

s_addr.sin_port =(8888); 端口号用户要在5000以上 ,端口号要传到网络上去,所以我们要调用函数来变成网络字节序。

所以我们要知道字节序转换API

#include <netinet/in.h>
uint16_t  htons(uint16_t  host16bitvalue);   //返回网络字节序的值 
uint32_t  htonl(uint32_t   host32bitvalue);   //返回网络字节序的值
uint16_t  ntohs(uint16_t   net16bitvalue);    //返回主机字节序的值
uint32_t  ntohl( uint32_t   net32bitvalue);    //返回主机字节序的值

h代表hostn代表nets代表short(两个字节), l代表long(4个字节)
通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY ,INADDR_ANY 指定地址让操作系统自己获取

listen(s_fd,10); //不断监听外面是否有数据 ,listen配置完后,马上会结束
int c_fd = accept(s_fd,NULL,NULL); //判断是否有已经三次握手的,如果有,就继续往下,跟客户端进行连接,把客户端信息返回到c_fd;

注意3:

inet_aton(“127.0.0.1”, &s_addr.sin_addr);
//把字符串形式的“127.0.0.1”转为网络能识别的格式
把本机地址转变为网络可识别的地址,需要调用函数来转换,
int inet_aton(const char* straddr , straddr ,struct in_addr *adddrp);

结果显示:

虚拟机的服务端正在等待连接。

在这里插入图片描述

在windows下用cmd 命令来连接服务端

在这里插入图片描述

连接成功!

在这里插入图片描述

我们调用的 socket(AF_INET ,SOCK_STREAM,0);
AF_INET 是TCP协议,我们在windows端cmd下telnet也是TCP协议,所以可以连接上。

printf(“get connect%s\n”,inet_ntoa(c_addr.sin_addr));
char* inet_ntoa(struct in_addr inaddr);
把网络格式的IP地址转化为字符串形式

int c_fd = accept(s_fd , (struct sockaddr *)&c_addr , &clen); 返回客户端信息,打印客户端IP地址

在这里插入图片描述

服务器建立——可连接 —— 交互 ——代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    int s_fd;
    int n_read;
    char readBuf[128];
    char *msg ="Refuel.CONG";

    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;

    memset(&s_addr,0,sizeof(struct sockaddr_in));//清空数据 
    memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket

    s_fd = socket(AF_INET ,SOCK_STREAM,0);
    if(s_fd == -1){
           perror("socket");
           exit(-1);
     }

    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(7777);
    inet_aton("127.0.0.1", &s_addr.sin_addr);  //inet_aton 把字符串形式的“127.0.0.1”转为网络能识别的格式的API
    //2.bind

    bind(s_fd,(struct sockaddr *)&s_addr ,sizeof(struct sockaddr_in));

//3.listen
    listen(s_fd,10);
    int clen = sizeof(struct sockaddr_in);
//4.accept

    int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
    if(c_fd == -1){
           perror("accept");
    }
    printf("get connet%s\n",inet_ntoa(c_addr.sin_addr));


//5.read
    n_read = read(c_fd , readBuf ,128);
    if(n_read == -1){
          perror("read");
    }
    else{
          printf("get message:%d,%s\n",n_read,readBuf);
    }
//6.write
    write(c_fd,msg,strlen(msg));
//7.close
return 0;
}

在这里插入图片描述

5.5 socket客户端代码实现

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    int k_fd;
    int n_read;
    char readBuf[128];
    char *msg ="message from client \n";

    struct sockaddr_in k_addr;

    memset(&k_addr,0,sizeof(struct sockaddr_in));//清空数据 
    
//1.socket

    k_fd = socket(AF_INET ,SOCK_STREAM,0);
    if(k_fd == -1){
           perror("socket");
           exit(-1);
     }


    k_addr.sin_family = AF_INET;
    k_addr.sin_port = htons(7777);
    inet_aton("127.0.0.1", &k_addr.sin_addr);  //inet_aton 把字符串形式的“127.0.0.1”转为网络能识别的格式的API
   
//3.connet
   if( connect(k_fd,(struct sockaddr *)&k_addr ,sizeof(struct sockaddr)) == -1)
   {
      perror("connect");
      exit(-1);
   }
//4. send 
 
    write(k_fd,msg,strlen(msg));
//5. read
     
    n_read = read(k_fd , readBuf ,128);
    if(n_read == -1){
          perror("read");
    }
    else{
          printf("get message from sever:%d,%s\n",n_read,readBuf);
    }
//7.close
return 0;
}

打开服务端 ,服务端等待连接。。。打开客户端,连接成功,同时双端进行交互。

注意——连接的时候IP和端口号要一致:

    //服务端
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(7777);
    inet_aton("127.0.0.1", &s_addr.sin_addr); 
    //客户端
    k_addr.sin_family = AF_INET;
    k_addr.sin_port = htons(7777);
    inet_aton("127.0.0.1", &k_addr.sin_addr);

在这里插入图片描述

5.6 实现双方聊天

一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种情求达到时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

服务端代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>

int main(int argc , char **argv)
{
    int s_fd;
    int c_fd;
    int n_read;
    char readBuf[128];
    char msg[] ={0};

    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;

    memset(&s_addr,0,sizeof(struct sockaddr_in));//清空数据 
    memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket

    s_fd = socket(AF_INET ,SOCK_STREAM,0);
    if(s_fd == -1){
           perror("socket");
           exit(-1);
     }

    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1], &s_addr.sin_addr);  //inet_aton 把字符串形式的“127.0.0.1”转为网络能识别的格式的API
   
//2.bind

    bind(s_fd,(struct sockaddr *)&s_addr ,sizeof(struct sockaddr_in));

//3.listen
    listen(s_fd,10);
    int clen = sizeof(struct sockaddr_in);
//4.accept
    while(1)   //一直接收连接请求
    {
           c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
           if(c_fd == -1){
                   perror("accept");
            }
           printf("get connet :%s\n",inet_ntoa(c_addr.sin_addr));

      if(fork() == 0){ // 创建子进程  用来读取客户端
          if(fork() == 0){ // 在建子进程,用来与客户端发送
            while(1){
            
             memset(msg,0,sizeof(msg));
             printf("input(servel)  :");
             gets(msg);
             write(c_fd,msg ,strlen(msg));
           }   
       }   
        while(1){
              memset(readBuf,0,sizeof(readBuf));            
              n_read = read(c_fd , readBuf ,128);
              if(n_read == -1){
                    perror("read");
              }
               else{
                        printf("get message:  %d,%s\n",n_read,readBuf);
                   }
             }
             break;
      }
   }    
return 0;
}

客户端代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>

int main(int argc ,char **argv)
{
    int k_fd;
    int n_read;
    char readBuf[128];
    char msg[] ={0};
 
    struct sockaddr_in k_addr;

    memset(&k_addr,0,sizeof(struct sockaddr_in));//清空数据 
    
//1.socket

    k_fd = socket(AF_INET ,SOCK_STREAM,0);
    if(k_fd == -1){
           perror("socket");
           exit(-1);
     }

    k_addr.sin_family = AF_INET;
    k_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1], &k_addr.sin_addr);  //inet_aton 把字符串形式的“127.0.0.1”转为网络能识别的格式的API
   
//3.connet
   if( connect(k_fd,(struct sockaddr *)&k_addr ,sizeof(struct sockaddr)) == -1)
   {
      perror("connect");
      exit(-1);
   }
//4. send 
 while(1){
   if(fork() == 0){
      while(1){   
            memset(msg,0,sizeof(msg));
            printf("input(kehu)  :");
            gets(msg);
            write(k_fd,msg,strlen(msg));
      }
   }
//5. read
    
   while(1){
       memset(readBuf,0,sizeof(readBuf));      
       n_read = read(k_fd , readBuf ,128);
       if(n_read == -1){
            perror("read");
       }
       else{
           printf("get message from sever:%d,%s\n",n_read,readBuf);
       }
   }    
  }
//7.close
return 0;
}

运行结果:

服务端

在这里插入图片描述

客户端

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值