Linux网络编程初步学习1

前言

1. 基础知识

协议就是我们在网络传输中的一组规则

网络有模型

OSI 7层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层

TCP/IP 4层模型:应用层(应用层,表示层,会话层),传输层,网络层,网络接口层(数据链路层,物理层)

TCP/IP模型就是我们经常使用的

应用层:http等,ftp,nfs,ssh,telnet

传输层:TCP等 UDP

网络层:IP等 ICMP,IGMP

链路层:以太网帧协议,ARP

数据在传输的过程中,先包装应用层,然后是传输层,网络层,链路层,才能传输在网络中,到达目的地,在打开这些包装

其中应用层是需要我们来干的,其他的都是系统干的

没有分装就不能在网络中传递

ARP数据格式

通过找到了目的IP地址,就可以找到以太网目的地址了

先出去找(请求)

找到了目的IP再返回以太网目的地址(应答)

意思就是ARP协议就是根据IP地址获取mac地址

以太网帧协议就是根据mac地址,完成数据的传递

每个网络设备都有一个mac地址

IP协议

首先讲一下TTL,为防止数据在网络中一直传递,因为有些时候可能设备之间的链接断了,这样就会一直传递

所以设置一个TTL,就是数据在网络中跳转的次数限制

IP版本:IPv4,IPv6

TTL:每经过一个路由节点,-1,当为0的时候,这个数据就会被抛弃

源IP:32位-----4字节 192.168.1.108 这个是十进制的IP地址,是字符串类型的 但是在网络中的IP是二进制的

目的IP:32位-----4字节

TCP就是和端口相关的

IP地址:在网络环境中,唯一标识一台主机

端口号:在网络上的一台主机上唯一标识一个进程

IP地址和端口号:可以在网络环境中唯一标识一个进程

UDP协议:

16位:源端口

16位:目的端口

TCP协议:

16位:源端口

16位:目的端口

32序号

32确认序号

6个标志位

16窗口大小

下面讲一下,cs模型和bs模型的区别

cs就是我们平时下的应用,比如哔哩哔哩

bs是浏览器访问的,比如浏览器上的哔哩哔哩

cs:优点:缓存大量数据(更好看,更漂亮,比如王者荣耀),协议选择灵活(可以不遵守一些协议,自己制定协议),速度快

缺点:不安全(下载万一有病毒那些呢),跨平台差,有些手机就不能下某些应用,开发工作量大,比如王者很大

bs:优点:安全,跨平台,有网址,不管什么浏览器都可以访问,开发工作量小

缺点:不能缓存大量数据,严格遵守http

2. 套接字

套接字就相当于插板和插座的关系

所以一定是成对出现的

一个源套接字(用户端),一个目的套接字(服务端)

都有发送端,接收端

网络字节序

在我们的计算机中,数据的存储是小端存储的,但是网络上是大端存储的,所以网络上的数据和我们计算机中的不一样,所以要转换一下

小端:高位存高地址,低位存低地址

htonl

‌htonl函数的主要功能是将‌主机字节顺序(小端)转换为‌网络字节顺序(大端)htonl函数常用于处理IP地址的字节顺序转换

我们知道的IP形式是192.168.1.110就是公网IP,这个是字符串形式的十进制,但是参数是整型,怎么办呢

我们可以用atoi,把字符串转换为int

192.168.1.110->string->atoi->int->htonl->网络字节序

其他

htons:本地->网络(port端口)

ntons:网络->本地(IP)

ntohs:网络->本地(port)

这些都是以前使用的函数,下面介绍一些新的

IP地址转换函数

inet_pton

专门给IP的,port就不用了

cpp 复制代码
int inet_pton(int af, const char *src, void *dst);

af:指的是IP版本,AF_INET(我们常用),AF_INET6

src:IP地址(点分十进制,就是192.168.1.110这个形式)

dst:传出,转化后的网络字节序的IP地址,放在dst地址所指向的内容,应该是一个整型

返回值:成功:1

异常:0,说明双人床、指向的不是一个有效的IP地址

失败:-1

inet_ntop

cpp 复制代码
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

网络字节序-》本地字节序

af:AF_INET,AF_INET6

src:网络字节序IP地址

dst:本地字节序(string IP)

size:dst的大小

返回值:成功:返回dst

失败:NULL

sockaddr地址结构

下面来讲一下sochet创建流程

这些全部都是函数

比如这个函数

有一个参数类型是sockaddr

值得注意的就是sockaddr我们已经不用了,我们现在用的都是sockaddr_in

这个就是它的地址结构

所以我们在使用bind的时候,先要创建sockaddr_in,在强制类型转换区传参

cpp 复制代码
		struct sochaddr_in addr;
		bind(fd,(Struct sockaddr*)&addr,size); 

除了bind是这样的,还有connect等

cpp 复制代码
		struct  sockaddr_in addr;
		//初始化addr,依次填充成员变量就可以了
		addr.sin_family=AF_INET/AF_INET6;
		addr.sin_port=htons(9527);//端口号//本地端口9527转换为网络上的端口
		int dst;		addr.sin_addr.s_addr=inet_pton(AF_INET,"192.157.22.45",(void*)&dst);//要的就是网络上的IP,所以这样干
		//结构体套结构体也一样的初始化
		bind(fd,(struct sockaddr*)&addr,size);

或者这样干

cpp 复制代码
		struct  sockaddr_in addr;
		//初始化addr,依次填充成员变量就可以了
		addr.sin_family=AF_INET/AF_INET6;
		addr.sin_port=htons(9527);//端口号//本地端口9527转换为网络上的端口
		int dst;		
		inet_pton(AF_INET,"192.157.22.45",(void*)&dst);
		addr.sin_addr.s_addr=dest;//因为网络上的IP就是整型,32位的
		bind(fd,(struct sockaddr*)&addr,size);

再或者这样

cpp 复制代码
		struct  sockaddr_in addr;
		addr.sin_family=AF_INET/AF_INET6;
		addr.sin_port=htons(9527);
		addr.sin_addr.s_addr=htonl(INADDR_ANY);//这个就是在系统中取出任意一个IP地址,,因为我们一般只有一个IP,所以没有什么区别其实
		bind(fd,(struct sockaddr*)&addr,size);

socket模型创建流程

首先在服务器端,socket会创建一个socket

bind会绑定客户端的IP和port,

listen会设置监听上限,什么意思呢,这个意思就是同时能和几个客户网络联系,

accept会阻塞监听用户端连接,什么意思呢,意思就是,服务端要等,等客户端connect的时候,然后建立连接,并创建一个一模一样的socket,去连接客户端,自己这个socket就担当监听的功能,所以网络通信的时候,一般有三个socket

socket

cpp 复制代码
		#include<sys/socket.h>
		int socket(int domain, int type, int protocol);

domain:AF_INET,AF_INET6,AF_UNIX

type:指定socket的类型,有三种

‌SOCK_STREAM‌:这是TCP中以流的方式传输类型,用于可靠的、面向连接的通信。一般用这个

‌SOCK_DGRAM‌:这是UDP的数据包传输类型,用于不可靠的、无连接的通信。

‌SOCK_RAW‌:这种类型的socket用于接收原始的数据包,允许直接访问底层网络协议的数据。

protocol:选定协议类型,为0,就是选的默认的

返回值:成功:新套接字的所对应的文件描述符,也就是这个socket的句柄,就是这个socket,可以控制这个socket

失败:-1 errno

cpp 复制代码
		fd=socket(AF_INET,SOCK_STREAM,0);

bind

cpp 复制代码
       int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

给这个socket绑定对应的客户端的IP和port

sockfd:sock函数返回值

cpp 复制代码
		struct sockaddr_in addr;
		addr.sin_family=AF_INET;
		addr.sin_port=htons(8888);
		addr.sin_addr.s_addr=htonl(INADDR_ANY);

addr:(struct sockaddr*)&addr

addrlen:sizeof(addr):地址结构的大小

返回值:成功:0

失败:-1 errno

listen

cpp 复制代码
int listen(int sockfd, int backlog);

sockfd:sock函数返回值

backlog:与服务器建立连接的上限数,但是就算你设置了次数,系统还是默认的最大128

返回值:成功---0

失败:-1 errno

accept

cpp 复制代码
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

阻塞等待与客户端建立连接,成功的话,返回一个与客户端建立连接成功的socket文件描述符,这个socket文件描述符是自己服务端的新的

sockfd:sock函数返回值

addr:传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+port)

addrlen:入:addr的大小,出:客户端addr的实际大小

cpp 复制代码
		socklen_t clit_addr_len=sizeof(addr);

addrlen:&clit_addr_len

返回值:能与服务器进行数据通信的socket对应的文件描述

失败:-1,errno

connect

cpp 复制代码
       int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

这个的作用就是与服务端建立连接

sockfd:客户端socket函数返回值

addr:传入型参数,意思是addr对应的要初始化好,而且这个是服务器的addr,服务器的地址结构

addrlen:服务器地址结构的大小

返回值,成功 0

失败-1 errno(意思是有错误码)

客户端不用bind,因为它是隐式绑定的,自己隐式有一个端口,绑定自己机器的IP

read

cpp 复制代码
       #include <unistd.h>
       ssize_t read(int fd, void *buf, size_t count);

fd就是socket的返回值,谁来读,就传谁的fd

write

cpp 复制代码
       #include <unistd.h>
       ssize_t write(int fd, const void *buf, size_t count);

流程

server:服务端

  1. socket() 创建服务端的socket
  2. bind 绑定服务器的地址结构
  3. listen 设置监听上限
  4. accept 阻塞监听客户端连接
  5. read 读取获取客户端数据
  6. 小写转大写
  7. write
  8. close
    client:客户端
  9. socket()
  10. connect 与服务器建立连接
  11. write
  12. read
  13. 显示
  14. close

3. socket模型创建

如何求我们计算机的IP呢,

cpp 复制代码
ip addr show

就可以了

其中那个127.0.0.1就是我们的IP


sz指令可以把我们linux中的文件放在windows中

然后直接拖动windows中的文件,就可以把windows中的文件放在linux中了

cpp 复制代码
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<ctype.h>

#define PORT 9527//给我们的服务端设置一个端口

void sys_err(const char*str)
{
    perror(str);
    exit(1);
}

int main()
{

    int sfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
    //bind给套接字绑定端口和IP
    //       int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
   //首先得创建一个addr,这个里面存的就是服务端的端口和IP,因为const是传入型的,你要初始化好,addrlen就是addr的大小了
    struct sockaddr_in saddr;
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(PORT);//因为传的是网络版的
    //saddr.sin_addr.s_addr=传入的是网路版的IP
    saddr.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY是默认的我们这个计算机的IP
    bind(sfd,(struct sockaddr*)&saddr,sizeof(saddr));//第二个参数强转一下就可以了
    listen(sfd,128);//设置最大访问数
    //       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    //阻塞等待与客户端的连接
    //addr是输出型参数,意思就是我们传进去的地址,指向内容不用初始化,返回的就是客户端的socket的地址结构,返回的是
    //我们在服务端新创建的一个socket的句柄
    //第三个参数的意思就是我们传进去时,是我们服务端的地址结构的大小,传出来的时候就是客户端socket地址结构的大小了
    struct sockaddr_in laddr;
    socklen_t addrlen=sizeof(saddr);
    int sfd2=accept(sfd,(struct sockaddr*)&laddr,&addrlen);
    //ssize_t read(int fd, void *buf, size_t count);
    //第一个参数就是说,我们要在哪里读,在服务端,就输入服务端的fd,就是读到buf里面,count是buf的大小
    char buf[BUFSIZ]={0};//这个是系统自带的#define的,是个很大的数字,,左括号和d就可以查看
    while(1)
    {
    read(sfd2,buf,BUFSIZ);
    //这下就读到buf中了
    //接下来把buf中的所有字符都转成大写的
    for(int i=0;i<BUFSIZ;i++)
    {
        buf[i]=toupper(buf[i]);//这个就是把小写转大写的函数
    }
    write(STDOUT_FILENO,buf,BUFSIZ);//把buf返回给屏幕,就是打印的意思
   //ssize_t read(int fd, void *buf, size_t count);
   //fd就是我们现在服务端的socket
   write(sfd2,buf,BUFSIZ);//返回给客户端
    }
    close(sfd);//关闭的是socket
    close(sfd2);
    return 0;
}
cpp 复制代码
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<ctype.h>

#define SERVE_PORT 9527//给我们的服务端设置一个端口

void sys_err(const char*str)
{
    perror(str);
    exit(1);
}

int main()
{

    int cfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
    //客户端的socket不用bind,意思就是不用完成端口,和IP的输入到地址结构中,因为
    // 客户端会默认bind的,默认给自己这个进程设置一个端口,IP就是对应机器的IP了
    //int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
    //与服务器端绑定
    //addr是服务器的地址结构,addrlen就是其对应的大小
    struct sockaddr_in saddr;
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(SERVE_PORT);
    //saddr.sin_addr.s_addr=htonl(INADDR_ANY);//因为我们用的一个机器,所以可以这样,但是我们知道服务器的IP了,和端口了,不应该这样
    //写,万一不在一台机器上测试呢
    //       int inet_pton(int af, const char *src, void *dst);///这个是把我们的string转换成网络字节序
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);
    connect(cfd, (struct sockaddr*)&saddr, sizeof(saddr));
    //ssize_t read(int fd, void *buf, size_t count);
    // ssize_t write(int fd, const void *buf, size_t count);
    char arr[BUFSIZ] = { 0 };
    int count = 5;
    while (--count)
    {
        write(cfd, "hahaha", 6);//向服务器端写入,写入的个数为6
        read(cfd, arr, BUFSIZ);//从服务器端读取
        write(STDOUT_FILENO, arr, BUFSIZ);//向屏幕写入
    }
    close(cfd);
    return 0;
}

这个就是客户端的逻辑

接下来我们在服务端试一下打印客户端的端口和IP地址

相关推荐
Komorebi.py34 分钟前
【Linux】-学习笔记05
linux·笔记·学习
Mr_Xuhhh39 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
速盾cdn4 小时前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin5211234 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin4 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
PersistJiao5 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
黑客Ash8 小时前
【D01】网络安全概论
网络·安全·web安全·php
->yjy8 小时前
计算机网络(第一章)
网络·计算机网络·php
朝九晚五ฺ8 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream8 小时前
Linux的桌面
linux