IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 一个TCP网络编程的简单实例 -> 正文阅读

[系统运维]一个TCP网络编程的简单实例

socket发送数据

操作系统提供了如下函数,供TCP的socket发送数据:

#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);

#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);

write函数把socket当作普通文件,往里面写数据。写操作有两种方式:

  1. 阻塞式write函数会把应用数据写到内核相应的缓冲区里,直到完成该操作才返回。有时write的返回值小于指定的要写入的字节数,这是因为内核缓冲区满了,需要等待内核把缓冲区的数据写到真实的硬件介质上,腾出空间,才能继续调用write
  2. 非阻塞式write函数先返回,内核后续再慢慢地把数据写到对应的位置。

默认是阻塞式写。
普通文件有文件系统的缓冲区,socket网络组件的缓冲区。真实的写操作由操作系统内核去执行。
在这里插入图片描述
write作用到普通文件,是把数据写到硬盘write作用到socket,是把数据从网卡发出去

两者有个明显的区别:

  1. 普通文件而言,当write函数返回时,应用进程可以认为数据已经写到了文件里,即文件收到了应用程序传给它的数据
  2. socket而言,当write函数返回时,应用进程并不能认为socket对端已经接收到了数据。因为数据通过网络发送到对端存在无法忽视的时延。

通过一个例子看下:

  1. 服务器进程。不停地从连接套接字接收数据,直到读到EOF
  2. 客户机进程。构造一大块数据, 通过write发送给服务器。

socket接收数据

// #include <sys/socket.h>
ssize_t recv(int socket, void *buffer, size_t length, int flags);
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len);
ssize_t recvmsg(int socket, struct msghdr *message, int flags);

// #include <unistd.h>
ssize_t read(int fildes, void *buf, size_t nbyte);

服务器进程

common.h

#ifndef COMMON_H
#define COMMON_H

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

inline const char *g_srvIp = "127.0.0.1";
inline uint16_t g_srvPort = 1234;
inline uint32_t g_backLog = 1024;
inline int RET_OK = 0;
inline int RET_ERR = -1;
inline const uint32_t MESSAGE_SIZE = 10240;
inline const uint32_t BUF_SIZE = 1024;

inline void PrintTime()
{
    time_t rawTime;
    time(&rawTime);
    fprintf(stdout, "%ld\n", rawTime);
}

#endif

server.c

#include "common.h"

uint32_t MakeListenSocket()
{
    int listenFd = socket(AF_INET, SOCK_STREAM, 0); // TCP
    if (listenFd == -1) {
        perror("socket");
        return RET_ERR;
    }

    struct sockaddr_in srvAddr;
    bzero(&srvAddr, sizeof(srvAddr));
    srvAddr.sin_family = AF_INET; // IPv4
    srvAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网口
    srvAddr.sin_port = htons(g_srvPort);

    if (bind(listenFd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) == -1) {
        perror("bind");
        return RET_ERR;
    }

    if (listen(listenFd, g_backLog) == -1) {
        perror("listen");
        return RET_ERR;
    }

    return listenFd;
}

int ReadN(int fd, char *buf, uint32_t len)
{
    int remain = len;
    while (remain)
    {
        int num = read(fd, buf, remain);
        if (num == -1) {
            perror("read");
            return -1;
        }

        if (num == 0) {
            break;
        }

        remain -= num;
        buf += num;
    }

    return len - remain;
}

void ReadData(int fd)
{
    char buf[BUF_SIZE];
    int time = 0;

    for (;;) {
        int n = ReadN(fd, buf, BUF_SIZE);
        if ((n == -1) || (n == 0)) {
            return;
        }

        PrintTime();
        fprintf(stdout, "%d bytes read, time = %d\n", n, time);
        ++time;
        sleep(1);
    }
}

int main()
{
    int listenFd = MakeListenSocket();
    if (listenFd == RET_ERR) {
        perror("MakeSocket");
        return RET_ERR;
    }

    PrintTime();
    fprintf(stdout, "server start ...\n");

    for (;;) {
        struct sockaddr_in cliAddr;
        socklen_t cliLen = 0;
        bzero(&cliAddr, sizeof(cliAddr));

        int connectFd = accept(listenFd, (struct sockaddr *)&cliAddr, &cliLen);
        if (connectFd == -1) {
            perror("accept");
            continue;
        }

        PrintTime();
        fprintf(stdout, "connection established ...\n");
        fprintf(stdout, "read start ...\n");
        ReadData(connectFd);
        PrintTime();
        fprintf(stdout, "read finish ...\n");
        close(connectFd);
    }

    return 0;
}

客户机进程

client.c

#include "common.h"

int MakeSocket()
{
    int socketFd = socket(AF_INET, SOCK_STREAM, 0);
    if (socketFd == -1) {
        perror("socket");
        return RET_ERR;
    }

    struct sockaddr_in srvAddr;
    bzero(&srvAddr, sizeof(srvAddr));
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_port = htons(g_srvPort);
    if (inet_pton(AF_INET, g_srvIp, &srvAddr.sin_addr.s_addr) != 1) {
        perror("inet_pton");
        return RET_ERR;
    }

    if (connect(socketFd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) == -1) {
        perror("connect");
        return RET_ERR;
    }

    return socketFd;
}

void SendData(int fd)
{
    char *query = (char *)malloc(MESSAGE_SIZE + 1);
    if (!query) {
        perror("malloc");
        return;
    }

    for (int i = 0; i < MESSAGE_SIZE; ++i) {
        query[i] = 'a';
    }
    query[MESSAGE_SIZE] = '\0';

    const char *data = query;
    uint32_t remain = strlen(data);
    while (remain) {
        int num = write(fd, data, remain);
        if (num < 1) {
            perror("write");
            free(query);
            query = NULL;
            return;
        }

        remain -= num;
        data += num;
    }

    free(query);
    query = NULL;
}

int main()
{
    int socketFd = MakeSocket();
    if (socketFd == RET_ERR) {
        perror("MakeSocket");
        return RET_ERR;
    }

    PrintTime();
    fprintf(stdout, "send start ...\n");
    SendData(socketFd);
    PrintTime();
    fprintf(stdout, "send finish ...\n");
    close(socketFd);

    return 0;
}

运行结果

使用g++ -std=c++17是因为程序中使用了inline变量
在这里插入图片描述
在这里插入图片描述
通过时间戳可以看到,在客户端的阻塞式写操作返回后,服务器才陆续收到客户端发来的所有数据

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-10-01 17:15:45  更:2021-10-01 17:17:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 17:42:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码