网络编程套接字socket

文章目录

一、socket编程接口

1.socket函数

2.connect函数

3.bind函数

4.listen函数

5.accept函数

6.sockaddr结构

二、封装tcp socket

三、TCP通用服务器 tcpserver.hpp



套接字接口是一组函数,他们和Unix IO结合起来,用以创建网络应用。从linux内核来看,一个套接字就是通信的一个断点。从linux应用程序看,套接字就是一个有相应描述符的打开文件。

因特网的套接字地址存放在sockaddr_in的16字节结构中,对于因特网,sin_family是AF_INET,sin_port成员是一个16位的端口号,sin_addr就是一个32位的ip地址,ip和port都是以网络字节顺序(大端)存储的。

connect,bind,accept函数要求一个指向与协议相关的套接字地址结构的指针。

一、socket编程接口

1.socket函数

客户端和服务器使用socket来创建一个套接字描述符

//创建socket 文件描述符
#include<sys/socket.h>
#include<sys/types.h>
int socket(int domain,int type,int protocol);


//使用示例
clientfd = socket(AF_INET,SOCK_STREAM,0);

2.connect函数

客户端通过connect函数来建立和服务器的连接

//建立连接
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);

connect函数试图与套接字地址为addr的服务器建立一个因特网连接,其中addrlen是sizeof(sockaddr_in)。connect函数会阻塞,一直到连接成功建立,或者发生错误。如果成功连接,clientfd描述符现在就准备好可以读写了,并且得到的连接是对(x:y,addr.sin_addr:addr.sin_port)刻画的,其中x表示客户端的ip地址,y表示临时端口,它唯一地确定了客户端主机上的客户端进程。

3.bind函数

服务器使用bind/listen/accept来和客户端建立连接

//绑定端口号
int bind(int socket,const struct sockaddr* addr, socklen_t address_len);

bind函数告诉内核将addr中的服务器套接字地址和套接字描述符fd联系起来,len是sizeof(addr)

4.listen函数

客户端是发起连接请求的主动实体,服务器是等待来自客户端的连接请求的被动实体。默认情况下,内核会认为socket函数创建的描述符对于主动套接字(active-socket),它存在于一个连接的客户端。服务器调用listen告诉内核,描述符是被服务器使用的而不是客户端使用的。


//开始监听socket
int listen(int socket,int backlog);

listen函数将sockfd从一个主动套接字转换成一个监听套接字,该套接字可以接受来自客户端的连接请求。backlog参数暗示了内核在开始拒绝连接请求之前,队列中要排队的未完成的连接请求的数量。backlog一般通常设置为一个较大的数值,如1024

5.accept函数

//接收请求
int accept(int listenfd,struct sockaddr* addr,socklen_t address_len);

accept函数等待来自客户端的连接请求到达侦听描述符listenfd,然后在addr中填写客户端的套接字地址,并且返回一个socket fd,这个返回值用于与客户端通信。

listenfd是作为客户端请求的一个端点,通常被创建一次,存在于服务器的整个生命周期。socketfd是客户端和服务器之间已经建立起来了连接的一个断点,服务器每次接收连接请求时都会被创建一次,只存在于服务器为一个客户端的服务过程中。

客户端实现socket套接字创建,connect和服务器端连接。服务器端完成socket,bind,listen,accept与客户端完成通信。listenfd和socketfd区别开,它可以使得我们建立并发服务器,能够处理多个客户端连接。例如每次客户端发一个connect到listenfd,我们可以fork一个新的进程,通过connectfd与客户端进行通信。

6.sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议ipv4,ipv6等,然而,各种网络协议的地址格式并不相同。

ipv4/ipv6的地址格式定义在netinet/in.h中,ipv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位ip地址ipv4/ipv6地址类型分别定义位常数AF_INET 、AF_INET_6,只要取得某种sockaddr的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容

sockaddr api都可以用struct sockaddr*表示,在使用的时候强制转换为sockaddr_in,这样的好处是程序的通用性,可以接收ipv4,ipv6以及unix domain socket各种类型的sockaddr结构体指针作为参数

虽然socket api中使用的接口是sockaddr,但是真正基于ipv4编程的时候,使用的数据结构是sockaddr_in,这个结构体主要有三部分信息:地址类型,端口号,ip地址

其中in_addr用来表示一个ipv4的ip地址,其实就是一个32位的整数

二、封装tcp socket

class TcpSocket 
{
  public:
     TcpSocket() : fd_(-1) { }
     TcpSocket(int fd) : fd_(fd) { }
     bool Socket() 
    {
         fd_ = socket(AF_INET, SOCK_STREAM, 0);
         if (fd_ < 0) 
        {
            perror("socket");
            return false;
        }
     printf("open fd = %d\n", fd_);
     return true;
    }


 bool Close() const 
{
     close(fd_);
     printf("close fd = %d\n", fd_);
     return true;
}


 bool Bind(const std::string& ip, uint16_t port) const 
{
 sockaddr_in addr;
 addr.sin_family = AF_INET;
 addr.sin_addr.s_addr = inet_addr(ip.c_str());
 addr.sin_port = htons(port);
 int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
 if (ret < 0) {   perror("bind");  return false;}
 return true;
}


 bool Listen(int num) const 
{
    int ret = listen(fd_, num);
    if (ret < 0) { perror("listen"); return false; }
    return true;
}


bool Accept(TcpSocket* peer, std::string* ip = NULL, uint16_t* port = NULL) const 
{
 sockaddr_in peer_addr;
 socklen_t len = sizeof(peer_addr);
 int new_sock = accept(fd_, (sockaddr*)&peer_addr, &len);
 if (new_sock < 0) {perror("accept");return false;}
 printf("accept fd = %d\n", new_sock);
 peer->fd_ = new_sock;
 if (ip != NULL) { *ip = inet_ntoa(peer_addr.sin_addr);}
 if (port != NULL) {*port = ntohs(peer_addr.sin_port);}
 return true;
}

 bool Recv(std::string* buf) const 
{
  buf->clear();
  char tmp[1024 * 10] = {0};
  ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
  if (read_size < 0) {perror("recv");return false;}
  if (read_size == 0) {return false;}
  buf->assign(tmp, read_size);
  return true;
}


 bool Send(const std::string& buf) const 
{
 ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
 if (write_size < 0) {perror("send"); return false;}
 return true;
}

 bool Connect(const std::string& ip, uint16_t port) const 
{
 sockaddr_in addr;
 addr.sin_family = AF_INET;
 addr.sin_addr.s_addr = inet_addr(ip.c_str());
 addr.sin_port = htons(port);
 int ret = connect(fd_, (sockaddr*)&addr, sizeof(addr));
 if (ret < 0) {perror("connect");return false;}
 return true;
}
 int GetFd() const {return fd_;}

private:
     int fd_;
};

三、TCP通用服务器 tcpserver.hpp

#pragma once

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "err.hpp"

namespace ns_server
{
    static const uint16_t defaultport = 8081;
    static const int backlog = 32; //? TODO

    using func_t = std::function<std::string(const std::string &)>;

    class TcpServer;
    class ThreadData
    {
    public:
        ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *ts)
        : sock(fd), clientip(ip), clientport(port), current(ts)
        {}
    public:
        int sock;
        std::string clientip;
        uint16_t clientport;
        TcpServer *current;
};

    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port = defaultport) : func_(func), port_(port), quit_(true)
        {
        }
        void initServer()
        {
            // 1. 创建socket, 文件
            listensock_ = socket(AF_INET, SOCK_STREAM, 0);
            if (listensock_ < 0)
            {
                std::cerr << "create socket error" << std::endl;
                exit(SOCKET_ERR);
            }
            // 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 = htonl(INADDR_ANY);
            if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind socket error" << std::endl;
                exit(BIND_ERR);
            }
            // 3. 监听
            if (listen(listensock_, backlog) < 0)
            {
                std::cerr << "listen socket error" << std::endl;
                exit(LISTEN_ERR);
            }
        }
        void start()
        {
            // signal(SIGCHLD, SIG_IGN); //ok, 我最后推荐的!
            // signal(SIGCHLD, handler); // 挥手,不太推荐

            quit_ = false;
            while (!quit_)
            {
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                // 4. 获取连接,accept
                int sock = accept(listensock_, (struct sockaddr *)&client, &len);
                if (sock < 0)
                {
                    std::cerr << "accept error" << std::endl;
                    continue;
                }
                // 提取client信息 -- debug
                std::string clientip = inet_ntoa(client.sin_addr);
                uint16_t clientport = ntohs(client.sin_port);

                // 5. 获取新连接成功, 开始进行业务处理
                std::cout << "获取新连接成功: " << sock << " from " << listensock_ << ", "
                          << clientip << "-" << clientport << std::endl;
                // v1
                // service(sock, clientip, clientport);

                // v2: 多进程版本
                // pid_t id = fork();
                // if (id < 0)
                // {
                //     close(sock);
                //     continue;
                // }
                // else if (id == 0) // child, 父进程的fd,会被child继承吗?会。 父子会用同一张文件描述符表吗?不会,子进程拷贝继承父进程的fd table;
                // {
                //     // 建议关闭掉不需要的fd
                //     close(listensock_);
                //     if(fork() > 0) exit(0); // 就这一行代码
                //     // child已经退了,孙子进程在运行
                //     service(sock, clientip, clientport);
                //     exit(0);
                // }

                // // 父, 一定关闭掉不需要的fd, 不关闭,会导致fd泄漏
                // close(sock);
                // pid_t ret = waitpid(id, nullptr, 0); //阻塞的! waitpid(id, nullptr, WNOHANG);//不推荐
                // if(ret == id) std::cout << "wait child " << id << " success" << std::endl; 

                // v3: 多线程 -- 原生多线程
                // 1. 要不要关闭不要的socket??不能
                // 2. 要不要回收线程?如何回收?会不会阻塞??
                // pthread_t tid;
                // ThreadData *td = new ThreadData(sock, clientip, clientport, this);
                // pthread_create(&tid, nullptr, threadRoutine, td);

                // v4: 一旦用户来了,你才创建线程, 线程池吗??
            }
        }
        static void *threadRoutine(void *args)
        {
            pthread_detach(pthread_self());

            ThreadData *td = static_cast<ThreadData *>(args);
            td->current->service(td->sock, td->clientip, td->clientport);
            delete td;
            return nullptr;
        }
        void service(int sock, const std::string &clientip, const uint16_t &clientport)
        {
            std::string who = clientip + "-" + std::to_string(clientport);
            char buffer[1024];
            while (true)
            {
                ssize_t s = read(sock, buffer, sizeof(buffer) - 1); // recv TODO
                if (s > 0)
                {
                    buffer[s] = 0;
                    std::string res = func_(buffer); // 进行回调
                    std::cout << who << ">>> " << res << std::endl;

                    write(sock, res.c_str(), res.size());
                }
                else if (s == 0)
                {
                    // 对方将连接关闭了
                    close(sock);
                    std::cout << who << " quit, me too" << std::endl;
                    break;
                }
                else
                {
                    close(sock);
                    std::cerr << "read error: " << strerror(errno) << std::endl;
                    break;
                }
            }
        }
        ~TcpServer()
        {
        }

    private:
        uint16_t port_;
        int listensock_; // TODO
        bool quit_;
        func_t func_;
    };
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值