• 套接字编程:TCP通信程序


    目录

    一、服务端编写流程

    二、客户端编写流程

    三、相关接口

    1. 创建套接字

    2. 为套接字绑定地址信息

    3. 客户端:向服务器发起连接请求

    4. 服务端:监听接口

    5. 服务端:获取新建连接

    6. 收发数据

    7. 关闭套接字

    四、代码实现

    1. 头文件-套接字接口封装

    2. 服务端实现

    2.1 多进程实现

    2.2 多线程实现

    3. 客户端实现


    一、服务端编写流程

    1. 创建套接字

    2. 为套接字绑定地址信息

    3. 开始监听

    将套接字状态置为LISTEN:

            1)告诉服务器,当前socket可以开始处理连接请求。

            2)若有客户端发送连接请求过来,服务器会为客户端创建一个新的socket,这个socket负责专门与该客户端进行通信。

    4. 获取新建连接

            从已完成连接队列中取出一个新建套接字的描述符。这个描述符对应了指定的socket与指定客户端进行通信。

    5. 收发数据

    6. 关闭套接字

    二、客户端编写流程

    1. 创建套接字

    2. 为套接字绑定地址信息(不推荐)

    3. 向服务器发起连接请求

            客户端的tcp套接字中也会保存完整的五元组

    4. 收发数据

    5. 关闭套接字

    三、相关接口

    1. 创建套接字

    int socket(int domain, int type, int protocol);        

            domain:地址域类型:       

                    ipv4: AF_INET;

                    ipv6:AF_INET6;

            type:套接字类型:

                    SOCK_STREAM:提供字节流传输服务;

            protocol:协议类型:

                    TCP:IPPROTO_TCP;

    返回值:

            成功,返回一个套接字描述符;失败,返回-1。

    2. 为套接字绑定地址信息

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

            sockfd:创建套接字返回的描述符;

            addr:要绑定的地址信息结构:

                    ipv4:struct sockaddr_in;

                    ipv6:struct sockaddr_in6;

            addrlen:地址信息长度;

    返回值:

            成功,返回0;失败,返回-1。

    3. 客户端:向服务器发起连接请求

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

            sockfd:套接字描述符;

            *addr:服务器地址信息;

            addrlen:地址信息长度。

    返回值:

            成功,返回0;失败,返回-1。

    4. 服务端:监听接口

    int listen(int sockfd, int backlog);

            sockfd:监听套接字描述符;

            backlog:服务器端同一时间最大并发连接数。

    注意:第二个参数限制的是同一时间所能处理的最大连接请求数量,而不是服务器所能建立的总连接数量。

    在内核中,存在一个socket连接队列,其最大容量就是backlog+1。如果连接队列已满,则新到的连接请求会被丢弃。

    5. 服务端:获取新建连接

    int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen);

            listen_sockfd:监听套接字,决定获取的是哪个监听套接字的新建连接;

            addr:地址结构的空间首地址,用于接收新连接的客户端地址信息;

            *addrlen:用于指定想要获取的地址长度,以及返回实际的长度。

    返回值:

            成功,返回新建连接的描述符;失败,返回-1。

    6. 收发数据

            因为tcp通信的套接字中保存了完整的五元组,所以收发数据时,不需要再指定和获取对端的地址信息。

    接收数据:

    ssize_t recv(int sockfd, char *buf, int len, int flag);

            sockfd:新建的套接字的描述符;

            buf:用于存放接收的数据的空间首地址;

            len:想要获取的数据长度;

            flag:0-阻塞接收;

    返回值:

            成功,返回实际获取到的数据长度;出错,返回-1;连接断开,返回0。

    发送数据:

    ssize_t send(int sockfd, char *data, int len, int flag);

            sockfd:新建的套接字的描述符;

            data:要发送的数据的空间首地址;

            len:要发送的数据长度;

            flag:0-默认阻塞发送;

    返回值:

            成功,返回实际发送成功的数据长度;出错,返回-1。

    7. 关闭套接字

    int close(fd);

            fd:套接字描述符。

    注:关闭套接字时,监听套接字一般不关闭,不需要与哪个客户端通信,则关闭那个对应的通信套接字即可。

    四、代码实现

    1. 头文件-套接字接口封装

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. //封装TCPsocket类
    9. #define MAX_LISTEN 5
    10. #define CHECK_RETURN(X) if((X) == false) {return -1;}
    11. class TCPsocket {
    12. private:
    13. int _sockfd;
    14. public:
    15. TCPsocket () : _sockfd(-1) {}
    16. //1.创建套接字
    17. bool Socket() {
    18. _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    19. if (_sockfd < 0) {
    20. perror("create socket error!");
    21. return false;
    22. }
    23. return true;
    24. }
    25. //2.为套接字绑定地址信息
    26. bool Bind(const std::string &ip, uint16_t port) {
    27. struct sockaddr_in addr;
    28. addr.sin_family = AF_INET;
    29. addr.sin_port = htons(port);
    30. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    31. socklen_t len = sizeof(struct sockaddr_in);
    32. int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
    33. if (ret < 0) {
    34. perror("bind error!");
    35. return false;
    36. }
    37. return true;
    38. }
    39. //客户端:3.向服务器发起连接请求
    40. bool Connect(const std::string &ip, uint16_t port) {
    41. struct sockaddr_in addr;
    42. addr.sin_family = AF_INET;
    43. addr.sin_port = htons(port);
    44. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    45. socklen_t len = sizeof(struct sockaddr_in);
    46. int ret = connect(_sockfd, (struct sockaddr*)&addr, len);
    47. if (ret < 0) {
    48. perror("connect error!");
    49. return false;
    50. }
    51. return true;
    52. }
    53. //服务端:3.开始监听
    54. bool Listen(int backlog = MAX_LISTEN) {
    55. int ret = listen(_sockfd, backlog);
    56. if (ret < 0) {
    57. perror("connect error!");
    58. return false;
    59. }
    60. return true;
    61. }
    62. //服务端:4. 获取新建连接
    63. bool Accept(TCPsocket *sock, std::string *ip = NULL, uint16_t *port = NULL) {
    64. struct sockaddr_in addr;
    65. socklen_t len = sizeof(struct sockaddr_in);
    66. int newfd = accept(_sockfd, (struct sockaddr*)&addr, &len);
    67. if (newfd < 0) {
    68. perror("accept error!");
    69. return false;
    70. }
    71. sock -> _sockfd = newfd;
    72. if (ip != NULL) *ip = inet_ntoa(addr.sin_addr);
    73. if (port != NULL) *port = ntohs(addr.sin_port);
    74. return true;
    75. }
    76. //4. 接收数据
    77. bool Recve(std::string *body) {
    78. char temp[4096] = {0};
    79. int ret = recv(_sockfd, temp, 4095, 0);
    80. if (ret < 0) {
    81. perror("recve error!");
    82. return false;
    83. }
    84. else if (ret == 0) {
    85. std::cout<<"peer shutdown!"<< std::endl;
    86. return false;
    87. }
    88. body -> assign(temp, ret);
    89. return true;
    90. }
    91. //5.发送数据
    92. bool Send(const std::string &body) {
    93. int ret = send(_sockfd, body.c_str(), body.size(), 0);
    94. if (ret < 0) {
    95. perror("send error!");
    96. return false;
    97. }
    98. return true;
    99. }
    100. //6.关闭套接字
    101. bool Close() {
    102. if (_sockfd != -1) close(_sockfd);
    103. return true;
    104. }
    105. };

    2. 服务端实现

    2.1 多进程实现

    1. #include "socket_tcp.hpp"
    2. #include
    3. int new_worker(TCPsocket& conn_sock) {
    4. pid_t pid = fork();
    5. if (pid < 0) {
    6. perror("fork error!");
    7. return -1;
    8. }
    9. else if(pid == 0){
    10. while (1) {
    11. std::string buf;
    12. bool ret = conn_sock.Recve(&buf);
    13. if (ret == false) {
    14. conn_sock.Close();
    15. break;
    16. }
    17. std::cout<<"clinet send: "<
    18. std::cout<<"server reply: ";
    19. fflush(stdout);
    20. std::cin>>buf;
    21. ret = conn_sock.Send(buf);
    22. if (ret == false) {
    23. conn_sock.Close();
    24. break;
    25. }
    26. }
    27. //出错,则关闭套接字,直接退出当前进程
    28. conn_sock.Close();
    29. exit(-1);
    30. }
    31. return 0;
    32. }
    33. int main() {
    34. signal(SIGCHLD, SIG_IGN);//或略进行退出信息;子进程退出则会直接释放资源
    35. TCPsocket sock;
    36. //1.创建套接字
    37. CHECK_RETURN(sock.Socket());
    38. //2。绑定地址信息
    39. CHECK_RETURN(sock.Bind("192.168.247.128", 8888));
    40. //3.开始监听
    41. CHECK_RETURN(sock.Listen());
    42. while (1) {
    43. //4.获取新建连接
    44. TCPsocket conn_sock;
    45. std::string client_ip;
    46. uint16_t client_port;
    47. bool ret = sock.Accept(&conn_sock, &client_ip, &client_port);
    48. if (ret == false) continue;
    49. std::cout<<"new connect:"<" "<
    50. //5.使用新建连接与客户端通信
    51. //创建子进程负责通信
    52. new_worker(conn_sock);
    53. //父进程本质并不与客户端进行通信,所以父进程需要关闭该套接字,防止资源耗尽
    54. //因为父子进程数据独有,所以父进程的关闭不会影响子进程进行通信
    55. conn_sock.Close();
    56. }
    57. //6.关闭套接字
    58. sock.Close();
    59. return 0;
    60. }

    2.2 多线程实现

    1. #include "socket_tcp.hpp"
    2. #include
    3. void *entry(void *arg) {
    4. TCPsocket *conn_sock = (TCPsocket*)arg;
    5. while (1) {
    6. std::string buf;
    7. bool ret = conn_sock->Recve(&buf);
    8. if (ret == false) {
    9. conn_sock->Close();
    10. break;
    11. }
    12. std::cout<<"clinet send: "<
    13. std::cout<<"server reply: ";
    14. fflush(stdout);
    15. std::cin>>buf;
    16. ret = conn_sock->Send(buf);
    17. if (ret == false) {
    18. conn_sock->Close();
    19. break;
    20. }
    21. }
    22. conn_sock->Close();
    23. delete conn_sock;
    24. }
    25. bool new_worker(TCPsocket*conn_sock) {
    26. pthread_t tid;
    27. int ret = pthread_create(&tid, NULL, entry, (void*)conn_sock);
    28. if (ret != 0) {
    29. std::cout<<"create thread error!"<
    30. return false;
    31. }
    32. pthread_detach(tid);//设置分离属性,不关心返回值,退出直接释放资源
    33. return true;
    34. }
    35. int main() {
    36. TCPsocket sock;
    37. //1.创建套接字
    38. CHECK_RETURN(sock.Socket());
    39. //2。绑定地址信息
    40. CHECK_RETURN(sock.Bind("192.168.247.128", 8888));
    41. //3.开始监听
    42. CHECK_RETURN(sock.Listen());
    43. while (1) {
    44. //4.获取新建连接
    45. TCPsocket *conn_sock = new TCPsocket();
    46. std::string client_ip;
    47. uint16_t client_port;
    48. bool ret = sock.Accept(conn_sock, &client_ip, &client_port);
    49. if (ret == false) continue;
    50. std::cout<<"new connect:"<" "<
    51. //5.使用新建连接与客户端通信
    52. new_worker(conn_sock);
    53. //注意:线程间共享文件描述符表,所以这里不能关闭套接字
    54. }
    55. //6.关闭套接字
    56. sock.Close();
    57. return 0;
    58. }

    3. 客户端实现

    1. #include "socket_tcp.hpp"
    2. int main(int argc, char *argv[]) {
    3. if (argc != 3) {
    4. std::cout<<"please add server address!"<
    5. std::cout<<"usage: ./clinet_tcp ip port"<
    6. return -1;
    7. }
    8. std::string server_ip = argv[1];
    9. uint16_t server_port = std::stoi(argv[2]);
    10. TCPsocket sock;
    11. //1.创建套接字
    12. CHECK_RETURN(sock.Socket());
    13. //2.绑定地址信息(客户端不推荐)
    14. //3.向服务器发起连接请求
    15. CHECK_RETURN(sock.Connect(server_ip, server_port));
    16. //4.与服务器通信
    17. while (1) {
    18. std::string buf;
    19. std::cout<<"client send:";
    20. fflush(stdout);
    21. std::cin>>buf;
    22. //发送数据
    23. bool ret = sock.Send(buf);
    24. if (ret == false) {
    25. perror("send error!");
    26. sock.Close();
    27. return -1;
    28. }
    29. //接收数据
    30. buf.clear();
    31. ret = sock.Recve(&buf);
    32. if (ret == false) {
    33. perror("recve error!");
    34. sock.Close();
    35. return -1;
    36. }
    37. std::cout<<"server reply:"<< buf << std::endl;
    38. }
    39. //5.关闭套接字
    40. sock.Close();
    41. return 0;
    42. }

  • 相关阅读:
    Go 语言变量
    <C++>【入门篇】
    Wordpress - Xydown独立下载页面插件
    嵌入式Linux裸机开发(三)SDK移植及BSP管理
    DJYOS开源往事三:DJYOS源码发布网络实证
    Linux 中的 chage 命令及示例
    (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
    深度解析:为何在 SwiftUI 视图的 init 初始化器里无法更改 @State 的值?
    Java 并发编程解析 | 如何正确理解Java领域中的并发锁,我们应该具体掌握到什么程度?
    支持向量机(SVM)----sklearn库的应用
  • 原文地址:https://blog.csdn.net/m0_63020222/article/details/127574155