
套接字接口是一组函数,他们和Unix IO结合起来,用以创建网络应用。从linux内核来看,一个套接字就是通信的一个断点。从linux应用程序看,套接字就是一个有相应描述符的打开文件。
因特网的套接字地址存放在sockaddr_in的16字节结构中,对于因特网,sin_family是AF_INET,sin_port成员是一个16位的端口号,sin_addr就是一个32位的ip地址,ip和port都是以网络字节顺序(大端)存储的。
connect,bind,accept函数要求一个指向与协议相关的套接字地址结构的指针。
客户端和服务器使用socket来创建一个套接字描述符
- //创建socket 文件描述符
- #include
- #include
- int socket(int domain,int type,int protocol);
-
-
- //使用示例
- clientfd = socket(AF_INET,SOCK_STREAM,0);
客户端通过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表示临时端口,它唯一地确定了客户端主机上的客户端进程。
服务器使用bind/listen/accept来和客户端建立连接
- //绑定端口号
- int bind(int socket,const struct sockaddr* addr, socklen_t address_len);
-
bind函数告诉内核将addr中的服务器套接字地址和套接字描述符fd联系起来,len是sizeof(addr)
客户端是发起连接请求的主动实体,服务器是等待来自客户端的连接请求的被动实体。默认情况下,内核会认为socket函数创建的描述符对于主动套接字(active-socket),它存在于一个连接的客户端。服务器调用listen告诉内核,描述符是被服务器使用的而不是客户端使用的。
-
- //开始监听socket
- int listen(int socket,int backlog);
listen函数将sockfd从一个主动套接字转换成一个监听套接字,该套接字可以接受来自客户端的连接请求。backlog参数暗示了内核在开始拒绝连接请求之前,队列中要排队的未完成的连接请求的数量。backlog一般通常设置为一个较大的数值,如1024
- //接收请求
- 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与客户端进行通信。
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位的整数
- 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_;
- };
- #pragma once
-
- #include
- #include
- #include
- #include
- #include
/* See NOTES */ - #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "err.hpp"
-
- namespace ns_server
- {
- static const uint16_t defaultport = 8081;
- static const int backlog = 32; //? TODO
-
- using func_t = std::function
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
(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_;
- };
- }