当前位置: 首页 > article >正文

【Linux】网络编程2

1.TCP通信流程        

        TCP是一个面向连接的,安全的,流式传输协议,这个协议是一个传输层协议。

面向连接:是一个双向连接,通过三次握手完成,断开连接需要通过四次挥手完成。
安全:tcp通信过程中,会对发送的每一数据包都会进行校验, 如果发现数据丢失, 会自动重传
流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致

 

 TCP通信流程中服务器端要做的事情有:

  1. 创建一个用于监听的套接字。(监听:监听是否有客户端的连接。套接字:这个套接字其实就是一个文件描述符)
  2. 将这个监听文件描述符和本地的IP以及端口号绑定。(客户端连接服务器的时候使用的就是这个IP和端口)
  3. 设置监听,监听的文件描述符fd开始工作
  4. 阻塞等待,当有客户端发起连接时,解除阻塞,接收客户端的连接,会得到一个客户端通信的套接字(fd)
  5. 通信。(接收数据和发送数据)
  6. 通信结束,断开连接。

客户端要做的事情有:

  1. 创建一个用于通信的套接字(fd)
  2. 连接服务器,需要指定要连接的服务器的IP和端口。
  3. 连接成功了,客户端和服务器通信。
  4. 通信结束,断开连接。

2.套接字函数

        使用套接字通信函数需要包含头文件<arpa/inet.h>,包含了这个头文件<sys/socket.h>就不用在包含了。

创建socket套接字

// 创建一个套接字
int socket(int domain, int type, int protocol);

参数:
domain: 使用的地址族协议
        AF_INET: 使用IPv4格式的ip地址
        AF_INET6: 使用IPv6格式的ip地址
type:
        SOCK_STREAM: 使用流式的传输协议
        SOCK_DGRAM: 使用报式(报文)的传输协议

protocol: 一般写0即可, 使用默认的协议
        SOCK_STREAM: 流式传输默认使用的是tcp
        SOCK_DGRAM: 报式传输默认使用的udp
返回值:
        成功: 可用于套接字通信的文件描述符
        失败: -1

        函数的返回值是一个文件描述符,通过这个文件描述符可以操作内核中的某一块内存,网络通信是基于这个文件描述符来完成的。

绑定bind

// 将文件描述符和本地的IP与端口进行绑定   
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
        sockfd: 监听的文件描述符, 通过socket()调用得到的返回值
        addr: 传入参数, 要绑定的IP和端口信息需要初始化到这个结构体中,IP和端口要转换为网络字节序
        addrlen: 参数addr指向的内存大小, sizeof(struct sockaddr)
返回值:成功返回0,失败返回-1

监听listen

// 给监听的套接字设置监听
int listen(int sockfd, int backlog);

参数:
        sockfd: 文件描述符, 可以通过调用socket()得到,在监听之前必须要绑定 bind()
        backlog: 同时能处理的最大连接要求,最大值为128
返回值:函数调用成功返回0,调用失败返回 -1

接收连接请求accept

// 等待并接受客户端的连接请求, 建立新的连接, 会得到一个新的文件描述符(通信的)		
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:
        sockfd: 监听的文件描述符
        addr: 传出参数, 里边存储了建立连接的客户端的地址信息
        addrlen: 传入传出参数,用于存储addr指向的内存大小
返回值:函数调用成功,得到一个文件描述符, 用于和建立连接的这个客户端通信,调用失败返回 -1

        这个函数是一个阻塞函数,当没有新的客户端连接请求的时候,该函数阻塞;当检测到有新的客户端连接请求时,阻塞解除,新连接就建立了,得到的返回值也是一个文件描述符,基于这个文件描述符就可以和客户端通信了。

接收数据recv

// 接收数据
ssize_t read(int sockfd, void *buf, size_t size);
ssize_t recv(int sockfd, void *buf, size_t size, int flags);

参数:
        sockfd: 用于通信的文件描述符, accept() 函数的返回值
        buf: 指向一块有效内存, 用于存储接收是数据
        size: 参数buf指向的内存的容量
        flags: 特殊的属性, 一般不使用, 指定为 0
返回值:
        大于0:实际接收的字节数
        等于0:对方断开了连接
        -1:接收数据失败了

发送数据send

// 发送数据的函数
ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);

参数:
        fd: 通信的文件描述符, accept() 函数的返回值
        buf: 传入参数, 要发送的字符串
        len: 要发送的字符串的长度
        flags: 特殊的属性, 一般不使用, 指定为 0
返回值:
        大于0:实际发送的字节数,和参数len是相等的
        -1:发送数据失败了

连接请求connect

// 成功连接服务器之后, 客户端会自动随机绑定一个端口
// 服务器端调用accept()的函数, 第二个参数存储的就是客户端的IP和端口信息
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
        sockfd: 通信的文件描述符, 通过调用socket()函数就得到了
        addr: 存储了要连接的服务器端的地址信息: iP 和 端口,这个IP和端口也需要转换为大端然后再赋值
        addrlen: addr指针指向的内存的大小 sizeof(struct sockaddr)
返回值:连接成功返回0,连接失败返回-1

3.sockaddr 类型

        在bind函数中,有一个参数是一个结构体:struct sockaddr,它的里面存储了两部分数据,分别是16位的地址类型,也就是地址族协议,是IPV4还是IPV6,还有端口号+IP地址和剩余的空地址。

        它的定义是这样的:

// 在写数据的时候不好用
struct sockaddr {
	sa_family_t sa_family;       // 地址族协议, ipv4
	char        sa_data[14];     // 端口(2字节) + IP地址(4字节) + 填充(8字节)
}

typedef unsigned short  uint16_t;
typedef unsigned int    uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

struct in_addr
{
    in_addr_t s_addr;
};  

// sizeof(struct sockaddr) == sizeof(struct sockaddr_in)
struct sockaddr_in
{
    sa_family_t sin_family;		/* 地址族协议: AF_INET */
    in_port_t sin_port;         /* 端口, 2字节-> 大端  */
    struct in_addr sin_addr;    /* IP地址, 4字节 -> 大端  */
    /* 填充 8字节 */
    unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -
               sizeof (in_port_t) - sizeof (struct in_addr)];
};  

服务器的代码:server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 将socket()返回值和本地的IP端口绑定到一起
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(34567); // 大端端口
    // INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址
    // 这个宏可以代表任意一个IP地址
    // 这个宏一般用于本地的绑定操作
    inet_pton(AF_INET, "192.168.149.133", &addr.sin_addr.s_addr);
    // addr.sin_addr.s_addr = INADDR_ANY; // 这个宏的值为0 == 0.0.0.0
    //    inet_pton(AF_INET, "192.168.149.133", &addr.sin_addr.s_addr);
    int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret == -1)
    {
        perror("bind");
        exit(0);
    }

    // 3. 设置监听
    ret = listen(lfd, 128);
    if (ret == -1)
    {
        perror("listen");
        exit(0);
    }

    // 4. 阻塞等待并接受客户端连接
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &clilen);
    if (cfd == -1)
    {
        perror("accept");
        exit(0);
    }
    // 打印客户端的地址信息
    char ip[24] = {0};
    printf("客户端的IP地址: %s, 端口: %d\n",
           inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(cliaddr.sin_port));

    // 5. 和客户端通信
    while (1)
    {
        // 接收数据
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        int len = read(cfd, buf, sizeof(buf));
        if (len > 0)
        {
            printf("客户端say: %s\n", buf);
            // buf = {0};
            gets(buf);
            write(cfd, buf, len);
        }
        else if (len == 0)
        {
            printf("客户端断开了连接...\n");
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }

    close(cfd);
    close(lfd);

    return 0;
}

客户端的代码:client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    // 1. 创建通信的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(34567); // 大端端口
    inet_pton(AF_INET, "192.168.149.133", &addr.sin_addr.s_addr);

    int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret == -1)
    {
        perror("connect");
        exit(0);
    }

    // 3. 和服务器端通信
    int number = 0;
    while (1)
    {
        // 发送数据
        char buf[1024];
        gets(buf);
        // sprintf(buf, "你好, 服务器...%d\n", number++);
        write(fd, buf, strlen(buf) + 1);

        // 接收数据
        memset(buf, 0, sizeof(buf));
        int len = read(fd, buf, sizeof(buf));
        if (len > 0)
        {
            printf("服务器say: %s\n", buf);
        }
        else if (len == 0)
        {
            printf("服务器断开了连接...\n");
            break;
        }
        else
        {
            perror("read");
            break;
        }
        sleep(1); // 每隔1s发送一条数据
    }

    close(fd);

    return 0;
}


http://www.kler.cn/a/389745.html

相关文章:

  • 《MYSQL45讲》误删数据怎么办
  • JQuery封装的ajax
  • 事件循环 -- 资源总结(浏览器进程模型、事件循环机制、练习题)
  • 同三维T610UDP-4K60 4K60 DP或HDMI或手机信号采集卡
  • 操作系统离散存储练习题
  • Python标准库模块的使用:math、datetime
  • 架构师备考-概念背诵(系统架构)
  • LeetCode 热题100之 动态规划1
  • 如何在短时间内备考集成项目管理工程师
  • 一文学习Android中的Treeview
  • FreeRTOS 23:事件组EventGroup创建、删除、置位操作
  • 逆向攻防世界CTF系列27-200simple-check-100
  • 【HarmonyNext】显示提示文字的方法
  • 【大数据学习 | HBASE高级】storeFile文件的合并
  • 【智谱开放平台-注册/登录安全分析报告】
  • 数据中心类DataCenter(二)
  • 【Linux 31】网络层协议 - IP
  • 【嵌入式设备】蓝牙鼠标使用教程——遥控器编码值
  • Netty篇(WebSocket)
  • VSCode + linux 远程免密登录
  • WSL 2 中 FastReport 与 FastCube 的设置方法与优化策略
  • Android 单元测试环境配置问题 Execution failed for task ‘:mergeDebugJavaResource‘.
  • J2EE平台
  • CompletableFuture:supplyAsync与runAsync
  • 【Spring】Spring框架中有有哪些常见的设计模式
  • macOS 下的 ARM 裸机嵌入式开发入门- 第二部分:实现第一个裸机应用并且调试