计算机网络(tcp_socket )(一)

计算机网络(tcp_socket )(一)

  • [1 有关tcp socket API](#1 有关tcp socket API)
    • [1.1 创建套接字](#1.1 创建套接字)
    • [1.2 bind](#1.2 bind)
    • [1.3 建立连接](#1.3 建立连接)
    • [1.4 获取新的连接(accept)](#1.4 获取新的连接(accept))
    • [1.5 读、写信息(read,write)](#1.5 读、写信息(read,write))
    • [1.6 客户端发消息](#1.6 客户端发消息)
  • [2 代码一(单进程客户端通信)](#2 代码一(单进程客户端通信))
  • [3 代码一(多进程客户端通信)](#3 代码一(多进程客户端通信))

1 有关tcp socket API

1.1 创建套接字

(1)TCP通信和UDP通信一样我们首先需要创建套接字(socket)

参数一:网络通信(AF_INET)

参数二:套接字类型(SOCK_STREAM)流式套接

参数三:0(默认就为TCP通信)

1.2 bind

这里跟UDP是一样的

总的来说,初始化服务器端,基本与UDP一致,仅有流式和数据报式的差别

1.3 建立连接

(1)将socket设置为监听状态

参数一:文件描述符(socket)

参数二:后序介绍(这里该参数不能为0,最好设置为16/32)

cpp 复制代码
        // 3 TCP是面向连接的,所以TCP随时随地等待被连接
        n = ::listen(_listensockfd, BACKLOG); // 需要将socket设置为监听状态
        if(n<0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            Die(LISTEN_ERR);
        }

1.4 获取新的连接(accept)

(1)accept:从指定文件描述符(只负责接收新的连接)中获取新的连接 / 客户端

参数一:监听文件描述符(这里的fd只负责接收新的连接)

参数二:相当于UDP中recvfrom的倒数第二个参数

参数三:相当于UDP中recvfrom的最后一个参数

返回值:

成功:非0(文件描述符,真正提供服务的),失败:-1,没有客户端连接默认阻塞

1.5 读、写信息(read,write)

(1)TCP是通过文件描述符来实现功能的,所以TCP比UDP更像文件,且接口也是read

(2)写文件也是直接通过文件描述符来写

1.6 客户端发消息

(1)我们之前说过客户端是不需要显示的进行bind的,因为端口号是OS自动帮我们分配的,之前UDP发消息有sendto(参数里包含IP和PORT),但是TCP用的是write和read,明显用这个接口IP和PORT传入不了

(2)connect

参数一、文件描述符

参数二、ip地址和端口号

参数三、sockaddr的长度

2 代码一(单进程客户端通信)

(1)makefile

(2)TcpServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <memory>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"

using namespace LogModule;
const static uint16_t gport = 8080;
#define BACKLOG 8

class TcpServer
{
public:
    TcpServer(int port = gport) : _port(port), _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1 创建socket
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            Die(SOCK_ERR);
        }
        LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;

        // 2 bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));

        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY; // 云服务器建议绑任意IP地址

        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success";

        // 3 TCP是面向连接的,所以TCP随时随地等待被连接
        n = ::listen(_listensockfd, BACKLOG); // 需要将socket设置为监听状态
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success";
    }
    void HandlerRequest(int sockfd)
    {
        char inbuff[4096];
        while (1)
        {
            ssize_t n = ::read(sockfd, inbuff, sizeof(inbuff) - 1);
            LOG(LogLevel::INFO) << inbuff;
            if (n > 0)
            {
                inbuff[n] = 0;
                std::string echo_str = "server echo# ";
                echo_str += inbuff;

                ::write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0)
                std::cout << "client quite" << std::endl;
            else
                break;
        }
        ::close(sockfd); // 文件描述符是有限的,如果不关会出现fd泄漏
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1、不断获取新的连接
            struct sockaddr_in peer;
            socklen_t peerlen=sizeof(peer);
            // 我们可以通过peer来获取客户端的ip和端口号

            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }

            // 获取连接成功
            LOG(LogLevel::INFO) << "accept success, sockfd is: " << sockfd;

            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "client info: " << addr.Addr();

            HandlerRequest(sockfd); // 处理请求
        }
    }
    void Stop()
    {
        _isrunning = false;
    }
    ~TcpServer() {}

private:
    int _listensockfd;
    uint16_t _port;
    bool _isrunning;
};

(3)TcpServerMain.cc

(4)TcpClientMain.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// ./client server_ip server_port
int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        std::cout << "Usage error : ./client server_ip server_port" << std::endl;
        return 1;
    }

    std::string server_ip = argv[1];
    int server_port = std::stoi(argv[2]);

    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socket create error" << std::endl;
        return 2;
    }

    // client 客户端不需要显示的bind
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // connect 底层会自动进行bind
    int n = ::connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (n < 0)
    {
        std::cout << "connect error" << std::endl;
        return 3;
    }

    // echo client
    std::string message;
    while (1)
    {
        char inbuff[1024];
        std::cout << "Please Enter $ ";
        std::getline(std::cin, message);

        n = ::write(sockfd, message.c_str(), message.size());
        if (n > 0)
        {
            int m = ::read(sockfd, inbuff, sizeof(inbuff));
            if (m > 0)
            {
                inbuff[m] = 0;
                std::cout << inbuff << std::endl;
            }
            else
                break;
        }
        else
            break;
    }

    ::close(sockfd);
    return 0;
}

(5)需要的文件目录

3 代码一(多进程客户端通信)

只需要更改

cpp 复制代码
#pragma once
#include <iostream>
#include <memory>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include<signal.h>

#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"

using namespace LogModule;
const static uint16_t gport = 8080;
#define BACKLOG 8

class TcpServer
{
public:
    TcpServer(int port = gport) : _port(port), _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1 创建socket
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            Die(SOCK_ERR);
        }
        LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;

        // 2 bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));

        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY; // 云服务器建议绑任意IP地址

        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success";

        // 3 TCP是面向连接的,所以TCP随时随地等待被连接
        n = ::listen(_listensockfd, BACKLOG); // 需要将socket设置为监听状态
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success";
    }
    void HandlerRequest(int sockfd)
    {
        char inbuff[4096];
        while (1)
        {
            ssize_t n = ::read(sockfd, inbuff, sizeof(inbuff) - 1);
            LOG(LogLevel::INFO) << inbuff;
            if (n > 0)
            {
                inbuff[n] = 0;
                std::string echo_str = "server echo# ";
                echo_str += inbuff;

                ::write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0)
            {
                std::cout << "client quite" << std::endl;
                break;
            }
            else
                break;
        }
        ::close(sockfd); // 文件描述符是有限的,如果不关会出现fd泄漏

        ::signal(SIGCHLD,SIG_IGN);//忽略子进程的退出信号
        //OS会自动回收资源,不用waitpid了
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 1、不断获取新的连接
            struct sockaddr_in peer;
            socklen_t peerlen=sizeof(peer);
            // 我们可以通过peer来获取客户端的ip和端口号

            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }

            // 获取连接成功
            LOG(LogLevel::INFO) << "accept success, sockfd is: " << sockfd;

            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "client info: " << addr.Addr();

            // HandlerRequest(sockfd); // 处理请求

            pid_t id = fork();
            if (id == 0)
            {
                // child
                ::close(_listensockfd); // 关闭子进程多余的文件描述符
                HandlerRequest(sockfd);
                exit(0);
            }
            ::close(sockfd); // 关闭父进程多余的文件描述符
        }
    }
    void Stop()
    {
        _isrunning = false;
    }
    ~TcpServer() {}

private:
    int _listensockfd;
    uint16_t _port;
    bool _isrunning;
};
相关推荐
IT帮13 小时前
2020年08月份04741计算机网络原理真题及答案
计算机网络
无小道21 小时前
网络层次划分-网络层
网络·计算机网络·智能路由器
六点半8881 天前
【计算机网络】初识HTTP(超文本传输协议)
网络协议·计算机网络·http
初听于你2 天前
深入了解—揭秘计算机底层奥秘
windows·tcp/ip·计算机网络·面试·架构·电脑·vim
胖好白2 天前
【计算机网络笔记】计算机网络学习笔记1
计算机网络
武文斌772 天前
复习总结最终版:计算机网络
linux·开发语言·学习·计算机网络
沧澜sincerely3 天前
计算机网络数据链路层
计算机网络·数据链路层
ArabySide3 天前
【计算机网络】HTTP协议核心知识梳理
网络协议·计算机网络·http
Wadli3 天前
小林coding|计算机网络
计算机网络