计算机网络自顶向下(3)---TCPsocket

发布于:2024-10-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

1.TCPsocket

        TCPsocket是指使用传输控制协议(TCP)的网络套接字。套接字是网络中两台计算机之间进行通信的端点。TCP是一种可靠的、面向连接的协议,提供了错误检测、流量控制和拥塞控制等功能。

        TCPsocket通常用于客户端-服务器通信,其中客户端程序通过TCP/IP网络连接到服务器程序。客户端和服务器可以通过TCP套接字发送和接收TCP数据包来交换数据。

2.Linux中的TCPsocket

        在Linux中,TCPsocket是一种在网络编程中使用的套接字,用于建立TCP连接并进行数据交换。

在Linux中,使用C语言编写网络程序通常涉及到以下的系统调用和函数来创建和使用TCPsocket:

  1. socket()函数:用于创建一个新的套接字,返回一个套接字描述符(文件描述符)。

  2. bind()函数:将一个本地地址绑定到套接字。

  3. listen()函数:将套接字设置为监听状态,接受客户端的连接请求。

  4. accept()函数:接受客户端的连接请求,并返回一个新的套接字描述符,用于与客户端进行通信。

  5. connect()函数:用于与远程服务器建立TCP连接。

  6. send()和recv()函数:用于发送和接收数据。

  7. close()函数:关闭套接字连接。

        通过这些函数,可以在Linux中创建TCPsocket并进行数据通信。在编程中,可以使用套接字描述符进行读取和写入操作,来发送和接收数据。

当使用TCPsocket进行网络编程时,以下是对这些函数接口的详细介绍:

        socket()函数:创建一个新的套接字,返回一个套接字描述符(file descriptor)。函数原型为:

int socket(int domain, int type, int protocol);
  • domain参数指定套接字的地址族(address family),如AF_INET表示IPv4地址族、AF_INET6表示IPv6地址族。
  • type参数指定套接字的类型,如SOCK_STREAM表示TCP套接字、SOCK_DGRAM表示UDP套接字。
  • protocol参数指定套接字使用的协议,通常选择0,由操作系统根据套接字类型自动选择合适的协议。

        bind()函数:将一个本地地址绑定到套接字。函数原型为:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd参数为套接字描述符。
  • addr参数为要绑定的本地地址,通常使用结构体sockaddr_in(IPv4)或sockaddr_in6(IPv6)来表示。
  • addrlen参数为addr结构体的大小。

        listen()函数:将套接字设置为监听状态,接受客户端的连接请求。函数原型为:

int listen(int sockfd, int backlog);
  • sockfd参数为套接字描述符。
  • backlog参数指定等待连接队列的最大长度。

        accept()函数:接受客户端的连接请求,并返回一个新的套接字描述符,用于与客户端进行通信。函数原型为:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd参数为监听套接字描述符。
  • addr参数为指向用于存储客户端地址的结构体指针。
  • addrlen参数为指向addr结构体大小的指针。

        connect()函数:用于与远程服务器建立TCP连接。函数原型为:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd参数为套接字描述符。
  • addr参数为远程服务器的地址。
  • addrlen参数为addr结构体的大小。

        send()和recv()函数:用于发送和接收数据。函数原型分别为:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd参数为套接字描述符。
  • buf参数为数据的缓冲区。
  • len参数为数据的长度。
  • flags参数为可选的标志,如0表示无特殊选项。

        close()函数:关闭套接字连接。函数原型为:

int close(int sockfd);
  • sockfd参数为套接字描述符。
#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "InetAddr.hpp"
#include "Log.hpp"
#include "ThreadPool.hpp"

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    USAGE_ERROR
};

const static int defaultsockfd = -1;
const static int gbacklog = 16;

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, InetAddr addr, TcpServer *s):sockfd(fd), clientaddr(addr), self(s)
    {}
public:
    int sockfd;
    InetAddr clientaddr;
    TcpServer *self;
};

using task_t = std::function<void()>;

class TcpServer
{
public:
    TcpServer(int port) : _port(port), _listensock(defaultsockfd), _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1. 创建流式套接字
        _listensock = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            LOG(FATAL, "socket error");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "socket create success, sockfd is : %d\n", _listensock);

        // 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;

        int n = ::bind(_listensock, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind success, sockfd is : %d\n", _listensock);

        // 3. tcp是面向连接的,所以通信之前,必须先建立连接。服务器是被连接的
        //    tcpserver 启动,未来首先要一直等待客户的连接到来
        n = ::listen(_listensock, gbacklog);
        if (n < 0)
        {
            LOG(FATAL, "listen error");
            exit(LISTEN_ERROR);
        }
        LOG(DEBUG, "listen success, sockfd is : %d\n", _listensock);
    }
    void Service(int sockfd, InetAddr client)
    {
        LOG(DEBUG, "get a new link, info %s:%d, fd : %d\n", client.Ip().c_str(), client.Port(), sockfd);

        std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "]# ";
        while (true)
        {
            char inbuffer[1024];
            ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << clientaddr << inbuffer << std::endl;

                std::string echo_string = "[server echo]# ";
                echo_string += inbuffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                // client 退出&&关闭连接了
                LOG(INFO, "%s quit\n", clientaddr.c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read error\n", clientaddr.c_str());
                break;
            }
	    sleep(5);
	    break;
        }
	std::cout << "server开始退出" << std::endl;
	shutdown(sockfd, SHUT_RD);
	std::cout << "shut _ rd " << std::endl;
	sleep(10);
	//shutdown(sockfd, SHUT_WR);
	//std::cout << "shut _ wr " << std::endl;
        //::close(sockfd); // 文件描述符泄漏
    }
    static void *HandlerSock(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->self->Service(td->sockfd, td->clientaddr);
        delete td;
        return nullptr;
    }
    void Loop()
    {
        _isrunning = true;
        // 4. 不能直接接受数据,先获取连接
        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = ::accept(_listensock, (struct sockaddr *)&peer, &len);
            if (sockfd < 0)
            {
                LOG(WARNING, "accept error\n");
                continue;
            }
            // Version 0 : 一次只能处理一个请求 --- 不可能
            // Service(sockfd, InetAddr(peer));

            // Version 1: 采用多进程
            // pid_t id = fork();
            // if (id == 0)
            // {
            //     // child : 关心sockfd, 不关心listensock
            //     ::close(_listensock); // 建议
            //     if(fork() > 0) exit(0); 
            //     Service(sockfd, InetAddr(peer)); //孙子进程 -- 孤儿进程 --- 系统领养
            //     exit(0);
            // }

            // // father: 关心listensock,不关心sockfd
            // ::close(sockfd);
            // waitpid(id, nullptr, 0);

            // version 2: 采用多线程
            pthread_t t;
            ThreadData *td = new ThreadData(sockfd, InetAddr(peer), this);
            pthread_create(&t, nullptr, HandlerSock, td); //将线程分离

            // vesion 3: 采用线程池
            // task_t t = std::bind(&TcpServer::Service, this, sockfd, InetAddr(peer));
            // ThreadPool<task_t>::GetInstance()->Enqueue(t);
        }
        _isrunning = false;
    }
    ~TcpServer()
    {
        if (_listensock > defaultsockfd)
            ::close(_listensock);
    }

private:
    uint16_t _port;
    int _listensock;
    bool _isrunning;
};
#include <iostream>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

// ./tcp_client serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

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

    // tcp client 要bind,不要显示的bind.
    struct sockaddr_in server;
    // 构建目标主机的socket信息
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        std::cerr << "connect error" << std::endl;
        exit(3);
    }

    while(true)
    {
        std::cout << "Please Enter# ";
        std::string outstring;
        std::getline(std::cin, outstring);

        ssize_t s = send(sockfd, outstring.c_str(), outstring.size(), 0); //write
        if(s > 0)
        {
            char inbuffer[1024];
            ssize_t m = recv(sockfd, inbuffer, sizeof(inbuffer)-1, 0);
            if(m > 0)
            {
                inbuffer[m] = 0;
                std::cout << inbuffer<< std::endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }
    //shutdown(sockfd, SHUT_WR);
    ::close(sockfd);
    return 0;
}