• 网络编程套接字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来创建一个套接字描述符

    1. //创建socket 文件描述符
    2. #include
    3. #include
    4. int socket(int domain,int type,int protocol);
    5. //使用示例
    6. clientfd = socket(AF_INET,SOCK_STREAM,0);

    2.connect函数

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

    1. //建立连接
    2. 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来和客户端建立连接

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

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

    4.listen函数

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

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

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

    5.accept函数

    1. //接收请求
    2. 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

    1. class TcpSocket
    2. {
    3. public:
    4. TcpSocket() : fd_(-1) { }
    5. TcpSocket(int fd) : fd_(fd) { }
    6. bool Socket()
    7. {
    8. fd_ = socket(AF_INET, SOCK_STREAM, 0);
    9. if (fd_ < 0)
    10. {
    11. perror("socket");
    12. return false;
    13. }
    14. printf("open fd = %d\n", fd_);
    15. return true;
    16. }
    17. bool Close() const
    18. {
    19. close(fd_);
    20. printf("close fd = %d\n", fd_);
    21. return true;
    22. }
    23. bool Bind(const std::string& ip, uint16_t port) const
    24. {
    25. sockaddr_in addr;
    26. addr.sin_family = AF_INET;
    27. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    28. addr.sin_port = htons(port);
    29. int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
    30. if (ret < 0) { perror("bind"); return false;}
    31. return true;
    32. }
    33. bool Listen(int num) const
    34. {
    35. int ret = listen(fd_, num);
    36. if (ret < 0) { perror("listen"); return false; }
    37. return true;
    38. }
    39. bool Accept(TcpSocket* peer, std::string* ip = NULL, uint16_t* port = NULL) const
    40. {
    41. sockaddr_in peer_addr;
    42. socklen_t len = sizeof(peer_addr);
    43. int new_sock = accept(fd_, (sockaddr*)&peer_addr, &len);
    44. if (new_sock < 0) {perror("accept");return false;}
    45. printf("accept fd = %d\n", new_sock);
    46. peer->fd_ = new_sock;
    47. if (ip != NULL) { *ip = inet_ntoa(peer_addr.sin_addr);}
    48. if (port != NULL) {*port = ntohs(peer_addr.sin_port);}
    49. return true;
    50. }
    51. bool Recv(std::string* buf) const
    52. {
    53. buf->clear();
    54. char tmp[1024 * 10] = {0};
    55. ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
    56. if (read_size < 0) {perror("recv");return false;}
    57. if (read_size == 0) {return false;}
    58. buf->assign(tmp, read_size);
    59. return true;
    60. }
    61. bool Send(const std::string& buf) const
    62. {
    63. ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
    64. if (write_size < 0) {perror("send"); return false;}
    65. return true;
    66. }
    67. bool Connect(const std::string& ip, uint16_t port) const
    68. {
    69. sockaddr_in addr;
    70. addr.sin_family = AF_INET;
    71. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    72. addr.sin_port = htons(port);
    73. int ret = connect(fd_, (sockaddr*)&addr, sizeof(addr));
    74. if (ret < 0) {perror("connect");return false;}
    75. return true;
    76. }
    77. int GetFd() const {return fd_;}
    78. private:
    79. int fd_;
    80. };

    三、TCP通用服务器 tcpserver.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include /* See NOTES */
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include "err.hpp"
    16. namespace ns_server
    17. {
    18. static const uint16_t defaultport = 8081;
    19. static const int backlog = 32; //? TODO
    20. using func_t = std::functionstring(const std::string &)>;
    21. class TcpServer;
    22. class ThreadData
    23. {
    24. public:
    25. ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *ts)
    26. : sock(fd), clientip(ip), clientport(port), current(ts)
    27. {}
    28. public:
    29. int sock;
    30. std::string clientip;
    31. uint16_t clientport;
    32. TcpServer *current;
    33. };
    34. class TcpServer
    35. {
    36. public:
    37. TcpServer(func_t func, uint16_t port = defaultport) : func_(func), port_(port), quit_(true)
    38. {
    39. }
    40. void initServer()
    41. {
    42. // 1. 创建socket, 文件
    43. listensock_ = socket(AF_INET, SOCK_STREAM, 0);
    44. if (listensock_ < 0)
    45. {
    46. std::cerr << "create socket error" << std::endl;
    47. exit(SOCKET_ERR);
    48. }
    49. // 2. bind
    50. struct sockaddr_in local;
    51. memset(&local, 0, sizeof(local));
    52. local.sin_family = AF_INET;
    53. local.sin_port = htons(port_);
    54. local.sin_addr.s_addr = htonl(INADDR_ANY);
    55. if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
    56. {
    57. std::cerr << "bind socket error" << std::endl;
    58. exit(BIND_ERR);
    59. }
    60. // 3. 监听
    61. if (listen(listensock_, backlog) < 0)
    62. {
    63. std::cerr << "listen socket error" << std::endl;
    64. exit(LISTEN_ERR);
    65. }
    66. }
    67. void start()
    68. {
    69. // signal(SIGCHLD, SIG_IGN); //ok, 我最后推荐的!
    70. // signal(SIGCHLD, handler); // 挥手,不太推荐
    71. quit_ = false;
    72. while (!quit_)
    73. {
    74. struct sockaddr_in client;
    75. socklen_t len = sizeof(client);
    76. // 4. 获取连接,accept
    77. int sock = accept(listensock_, (struct sockaddr *)&client, &len);
    78. if (sock < 0)
    79. {
    80. std::cerr << "accept error" << std::endl;
    81. continue;
    82. }
    83. // 提取client信息 -- debug
    84. std::string clientip = inet_ntoa(client.sin_addr);
    85. uint16_t clientport = ntohs(client.sin_port);
    86. // 5. 获取新连接成功, 开始进行业务处理
    87. std::cout << "获取新连接成功: " << sock << " from " << listensock_ << ", "
    88. << clientip << "-" << clientport << std::endl;
    89. // v1
    90. // service(sock, clientip, clientport);
    91. // v2: 多进程版本
    92. // pid_t id = fork();
    93. // if (id < 0)
    94. // {
    95. // close(sock);
    96. // continue;
    97. // }
    98. // else if (id == 0) // child, 父进程的fd,会被child继承吗?会。 父子会用同一张文件描述符表吗?不会,子进程拷贝继承父进程的fd table;
    99. // {
    100. // // 建议关闭掉不需要的fd
    101. // close(listensock_);
    102. // if(fork() > 0) exit(0); // 就这一行代码
    103. // // child已经退了,孙子进程在运行
    104. // service(sock, clientip, clientport);
    105. // exit(0);
    106. // }
    107. // // 父, 一定关闭掉不需要的fd, 不关闭,会导致fd泄漏
    108. // close(sock);
    109. // pid_t ret = waitpid(id, nullptr, 0); //阻塞的! waitpid(id, nullptr, WNOHANG);//不推荐
    110. // if(ret == id) std::cout << "wait child " << id << " success" << std::endl;
    111. // v3: 多线程 -- 原生多线程
    112. // 1. 要不要关闭不要的socket??不能
    113. // 2. 要不要回收线程?如何回收?会不会阻塞??
    114. // pthread_t tid;
    115. // ThreadData *td = new ThreadData(sock, clientip, clientport, this);
    116. // pthread_create(&tid, nullptr, threadRoutine, td);
    117. // v4: 一旦用户来了,你才创建线程, 线程池吗??
    118. }
    119. }
    120. static void *threadRoutine(void *args)
    121. {
    122. pthread_detach(pthread_self());
    123. ThreadData *td = static_cast(args);
    124. td->current->service(td->sock, td->clientip, td->clientport);
    125. delete td;
    126. return nullptr;
    127. }
    128. void service(int sock, const std::string &clientip, const uint16_t &clientport)
    129. {
    130. std::string who = clientip + "-" + std::to_string(clientport);
    131. char buffer[1024];
    132. while (true)
    133. {
    134. ssize_t s = read(sock, buffer, sizeof(buffer) - 1); // recv TODO
    135. if (s > 0)
    136. {
    137. buffer[s] = 0;
    138. std::string res = func_(buffer); // 进行回调
    139. std::cout << who << ">>> " << res << std::endl;
    140. write(sock, res.c_str(), res.size());
    141. }
    142. else if (s == 0)
    143. {
    144. // 对方将连接关闭了
    145. close(sock);
    146. std::cout << who << " quit, me too" << std::endl;
    147. break;
    148. }
    149. else
    150. {
    151. close(sock);
    152. std::cerr << "read error: " << strerror(errno) << std::endl;
    153. break;
    154. }
    155. }
    156. }
    157. ~TcpServer()
    158. {
    159. }
    160. private:
    161. uint16_t port_;
    162. int listensock_; // TODO
    163. bool quit_;
    164. func_t func_;
    165. };
    166. }

  • 相关阅读:
    TL,你是如何管理项目风险的?
    微信小程序名称、简称设置规范
    php 进程通信系列 (四)共享内存
    音视频开发—FFmpeg 从MP4文件中抽取视频H264数据
    刷题日记【第八篇】-笔试必刷题【查找输入整数二进制中1的个数+手套+完全数计算+扑克牌大小】
    HTML5七夕情人节表白网页制作【幻化3D相册】HTML+CSS+JavaScript
    2023跨境秋季大促备战攻略
    分析系统变慢或卡死
    网络面试-0x03http有哪些常见的请求头以及作用
    三万字盘点Spring/Boot的那些常用扩展点
  • 原文地址:https://blog.csdn.net/jolly0514/article/details/133250911