TCP/IP基本实现

  • 应用层:应用程序通过这一层访问网络,常见 FTP、HTTP、DNS 和 TELNET 协议;
  • 传输层:TCP 协议和 UDP 协议;
  • 网络层:IP 协议,ARP、RARP 协议,ICMP 协议等;
  • 网络接口层:是 TCP/IP 协议的基层,负责数据帧的发送和接收。
ifconfig -a

MAC(Media Access Control)地址,或称为物理地址、硬件地址,用来定义互联网中设备的位置。

在 TCP/IP 层次模型中,网络层管理 IP 地址,链路层则负责 MAC 地址。因此每个网络位置会有一个专属于它的 IP 地址,而每个主机会有一个专属于它 MAC 地址。

img

  1. 查看自己电脑的 IP 地址,并截图。
  2. ping github.com,查看 IP 地址,并截图

了解了上面的理论知识过后,我们可以使用 tcpdump 这个抓包工具来实际看一下。

sudo tcpdump -ntx -c 1
  • -n :显示 IP 地址而非域名地址
  • -t :不显示时间戳
  • -x :以十六进制显示包内内容
  • -c :tcpdump 将在接受到几个数据包后退出

由此可见,流量控制实际上是对发送方数据流量的控制,使其发送速率不超过接收方的速率。所以需要一些规则使得发送方知道在什么情况下可以接着发送下一帧,而在什么情况下必须暂停发送,以等待收到某种反馈信息后再继续发送。这就是流量控制。

为了提供足够快的响应时间,以太网和 IEEE802.3 对数据帧长度都有限制,其最大值分别为 1500 字节和 1492 字节,链路层的这个特性称作 MTU,即最大传输单元

用命令 netstat -in 可以查看网络接口的 MTU:

基于TCP协议的客户端和服务端实现

服务端口代码

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>
#include <sys/socket.h>
#include "base.h"

//出错调用函数
static void error_handle(std::string opt, std::string message)
{
    
    
    //根据errno值获取失败原因并打印到终端
    perror(opt.c_str());
    std::cout << message << std::endl;
    exit(1);
}

int main(int argc, char *argv[])
{
    
    
    int serv_sock, client_sock;
    char message[BUFFSIZE];
    int str_len, ret;

    struct sockaddr_in serv_adr, client_adr;
    socklen_t client_adr_size;

    //判断命令行参数合法性
    if(argc < 2)
    {
    
    
        std::cout << "Usage : " << argv[0] << " <port>" << std::endl;
        exit(1);
    }

    //创建socket套接字
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock < 0)
    {
    
    
        error_handle("socket", "socket() error.");
    }

    //初始化服务器套接字参数,设置网卡IP 和端口号
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    //绑定端口
    ret = bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    if(ret < 0)
    {
    
    
        error_handle("bind", "bind() error.");
    }

    //监听TCP端口号
    ret = listen(serv_sock, 5);
    if(ret < 0)
    {
    
    
        error_handle("listen", "listen() error.");
    }

    client_adr_size = sizeof(client_adr);

    while(1)
    {
    
    
        //使用accept接收客户端连接请求
        client_sock = accept(serv_sock, (struct sockaddr*)&client_adr, &client_adr_size);
        if(client_sock < 0)
        {
    
    
            //接收客户端连接请求失败,
            continue;
        }
        //接收新的客户端请求,进行客户端数据处理
        std::cout << "Accept New Client : " << inet_ntoa(client_adr.sin_addr) << " , port : " << ntohs(client_adr.sin_port) << std::endl;
        std::cout << "Start Recv Client Data..." << std::endl;

        //清空缓存区
        memset((void *)&message, 0, BUFFSIZE);
        while((str_len = read(client_sock, message, BUFFSIZE)) != 0)
        {
    
    
            //成功读取客户端发送来的数据消息
            //打印输出
            std::cout << "Recv Message : " << message << std::endl;
            //将消息回传给客户端,作为回声服务器,类似 echo 命令
            write(client_sock, message, str_len);
            //清空缓存区,等待再次读取数据
            memset(message, 0, BUFFSIZE);
        }

        //客户端断开连接,关闭套接字
        close(client_sock);
    }

    //服务器关闭,关闭服务器套接字
    close(serv_sock);
    return 0;
}

客户端

//
// Created by wpf on 2022/8/7.
//

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>
#include <sys/socket.h>
#include "base.h"

//出错调用函数
static void error_handle(std::string opt, std::string message)
{
    
    
    //根据errno值获取失败原因并打印到终端
    perror(opt.c_str());
    std::cout << message << std::endl;
    exit(1);
}

int main(int argc, char *argv[])
{
    
    
    int sock, ret;
    char message[BUFFSIZE];
    int str_len;
    struct sockaddr_in serv_adr;

    if(argc < 3)
    {
    
    
        std::cout << "Usage : " << argv[0] << " <IP> <port>" << std::endl;
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
    
    
        error_handle("socket", "socket() error.");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    ret = connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    if(ret < 0)
    {
    
    
        error_handle("connect", "connect() error.");
    }

    std::cout << "Connect Success..." << std::endl;

    //进入数据处理
    while(1)
    {
    
    
        std::cout << "Please Input Message(Q to quit) : " << std::endl;
        std::cin >> message;
        if((!strcmp(message, "q")) || (!strcmp(message, "Q")))
        {
    
    
            //退出客户端
            break;
        }

        //将数据发送给服务端
        write(sock, message, strlen(message));
        //读取服务端回传的数据
        str_len = read(sock, message, BUFFSIZE-1);
        message[str_len] = 0;
        //打印输出
        std::cout << "Echo Message : " << message << std::endl;
    }

    //关闭套接字
    close(sock);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Android_WPF/article/details/126214650