• 网络套接字编程(二)


    socket常见API

    创建套接字:(TCP/UDP,客户端+服务器)

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

    绑定端口号:(TCP/UDP,服务器

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

    监听套接字:(TCP,服务器)

    int listen(int sockfd, int backlog);

    接收请求:(TCP,服务器)

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

    建立连接:(TCP,客户端)

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

    基于TCP协议实现网络通信

    与学习UDP的接口相同,我们通过实现一个简单的TCP服务器与TCP客户端学习这些接口。首先我们编写服务器。

    TCP服务器

    我们同样使用一个类来实现服务器,TCP下的网络通信同样需要创建套接字,使用TCP创建套接字的与UDP下大致相同

    1. //UDP
    2. int socket_fd=socket(AF_INET,SOCK_DGRAM,0);
    3. //TCP
    4. int socket_fd=socker(AF_TNET,SOCK_STREAM,0);

    与UDP协议不同,TCP创建套接字的第二个参数应该选择 SOCK_STREAM。

    • SOCK_DGRAM:套接字提供的数据传输方式为数据报。
    • SOCK_STREAM:套接字提供的数据传输方式为字节流。
    • 连接性:SOCK_DGRAM无连接,SOCK_STREAM面向连接。
    • 数据完整性:SOCK_STREAM保证数据的顺序和完整性,而SOCK_DGRAM不保证。
    • 速度:SOCK_DGRAM可能更快,因为它减少了协议开销,但牺牲了可靠性。
    • 用途:SOCK_DGRAM适用于小数据量和可以容忍一定数据丢失的场景,SOCK_STREAM适用于需要可靠传输大量数据的场景。

    UDP是无连接的,TCP是有连接的。所以UDP采用的是数据报传输方式,TCP采用的是字节流传输方式。

    创建套接字

    1. class Server
    2. {
    3. public:
    4. Server(std::string ip,uint16_t port)
    5. :_sockfd(-1);
    6. :_ip(ip);
    7. :_port(port);
    8. {}
    9. bool init()
    10. {
    11. //创建套接字
    12. _sockfd = socket(AF_INET,SOCK_STREAM,0);
    13. if(_sockfd<0)
    14. {
    15. std::cerr << "socket flase" << std::endl;
    16. return false;
    17. }
    18. }
    19. ~Server()
    20. {
    21. if(_sockfd>=0)
    22. {
    23. close(_sockfd);
    24. }
    25. }
    26. private:
    27. int _sockfd;
    28. std::string _ip;
    29. uint16_t _port;
    30. };

    绑定ip与端口

    与我们编写的UDP服务端相同我们需要绑定IP与端口。流程与UDP协议相同。

    1. bool init()
    2. {
    3. //创建套接字
    4. _sockfd = socket(AF_INET,SOCK_STREAM,0);
    5. if(_sockfd<0)
    6. {
    7. std::cerr << "socket flase" << std::endl;
    8. return false;
    9. }
    10. //绑定端口
    11. struct sockaddr_in local;
    12. memset(&local,'\0',sizeof(local));
    13. //协议家族
    14. local.sin_family=AF_INET;
    15. //端口号
    16. local.sin_port=htons(_port);
    17. //ip
    18. local.sin_addr.s_addr = inet_addr(_ip.c_str());
    19. //绑定
    20. if(bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr_in)) < 0 )
    21. {
    22. //绑定失败
    23. std::cerr << "bind error" << std::endl;
    24. return false;
    25. }
    26. }

    设置为监听 

    TCP是面向连接的,客户端与服务端需要建立连接后才能通信。所以TCP服务器需要时刻注意有无客户端的连接请求。我们需要将套接字设置为监听状态。

    监听套接字:(TCP,服务器)

    int listen(int sockfd, int backlog);

    参数说明: 

    • sockfd 参数是要监听的套接字的文件描述符。
    • backlog 参数是系统应该允许的,处于未完成连接队列中的连接请求的最大数量 

    如果有多个客户端同时发来连接请求,此时未被服务器处理的连接就会放入连接队列,backlog代表的就是这个全连接队列的最大长度,一般不要设置太大,设置为5或10即可。 

     返回值:

    • 监听成功返回0,监听失败返回-1,同时错误码会被设置。

    在初始化中我们将套接字设置为监听套接字。

    1. bool init()
    2. {
    3. //创建套接字
    4. _sockfd = socket(AF_INET,SOCK_STREAM,0);
    5. if(_sockfd<0)
    6. {
    7. std::cerr << "socket flase" << std::endl;
    8. return false;
    9. }
    10. std::cerr << "socket succes" << std::endl;
    11. //绑定端口
    12. struct sockaddr_in local;
    13. memset(&local,'\0',sizeof(local));
    14. //协议家族
    15. local.sin_family=AF_INET;
    16. //端口号
    17. local.sin_port=htons(_port);
    18. //ip
    19. local.sin_addr.s_addr = inet_addr(_ip.c_str());
    20. //绑定
    21. if(bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr_in)) < 0 )
    22. {
    23. //绑定失败
    24. std::cerr << "bind error" << std::endl;
    25. return false;
    26. }
    27. std::cerr << "bind succes" << std::endl;
    28. if(listen(_sockfd,5) < 0 )
    29. {
    30. //监听失败
    31. std::cerr << "listen error" << std::endl;
    32. return false;
    33. }
    34. std::cerr << "listen succes" << std::endl;
    35. }

    获取连接

    在设置完监听状态后,我们需要接受客户端的连接请求。

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

    参数说明:

    • sockfd 参数是监听套接字的文件描述符。
    • addr 参数是一个指向 sockaddr 结构的指针,该结构用于接收连接客户端的地址信息。如果调用者不需要地址信息,可以将此参数设置为 NULL。
    • addrlen 参数是一个指向 socklen_t 类型的变量的指针,它在调用时指定 addr 缓冲区的长度,并在调用成功后更新为实际接收的地址结构长度。  

    返回值:

    • 成功时,函数返回一个新的套接字文件描述符,用于与客户端进行通信。这个新的套接字会继承监听套接字的一些属性,如类型和协议。
    • 出错时,函数返回 -1 并设置全局变量以指示错误类型。
    1. void start()
    2. {
    3. while(1)
    4. {
    5. struct sockaddr_in sendfrom;
    6. memset(&sendfrom,'\0',sizeof(sendfrom));
    7. int client;
    8. socklen_t len=sizeof(struct sockaddr_in);
    9. client=accept(_sockfd,(struct sockaddr*)&sendfrom,&len);
    10. if(client<0)
    11. {
    12. std::cerr << "accept error" << std::endl;
    13. continue;;
    14. }
    15. std::string client_ip = inet_ntoa(sendfrom.sin_addr);
    16. int client_port = ntohs(sendfrom.sin_port);
    17. std::cout<<"new link->"<<" ["<<client_ip<<"]:"<< client_port <<std::endl;
    18. server(client);
    19. }
    20. }

     服务端处理请求

    accept返回的是一个描述符套接字·,我们在上一节讲到套接字描述符实际上就是文件描述符,同时文件的读写方式是字节流,而TCP协议的传输也是字节流,所以我们可以使用文件的读写函数接受和发送信息。

    使用 read 函数从TCP套接字读取数据:

    ssize_t read(int fd, void *buf, size_t count);
    

    参数说明:

    • fd:特定的文件描述符,表示从该文件描述符中读取数据。
    • buf:数据的存储位置,表示将读取到的数据存储到该位置。
    • count:数据的个数,表示从该文件描述符中读取数据的字节数。

    返回值说明:

    • 如果返回值大于0,则表示本次实际读取到的字节个数。
    • 如果返回值等于0,则表示对端已经把连接关闭了。
    • 如果返回值小于0,则表示读取时遇到了错误。

    使用 write 函数向TCP套接字写入数据:

    ssize_t write(int fd, const void *buf, size_t count);
    

    参数说明:

    • fd:特定的文件描述符,表示将数据写入该文件描述符对应的套接字。
    • buf:需要写入的数据。
    • count:需要写入数据的字节个数。

    返回值说明:

    • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。
    1. bool server(int sock)
    2. {
    3. char buffer[1024];
    4. while (true)
    5. {
    6. ssize_t size = read(sock, buffer, sizeof(buffer)-1);
    7. if (size > 0)
    8. { //读取成功
    9. buffer[size] = '\0';
    10. std::cout << sock << " [client]:" << buffer << std::endl;
    11. write(sock, buffer, size);
    12. }
    13. else if (size == 0)
    14. { //对端关闭连接
    15. std::cout << " close!" << std::endl;
    16. break;
    17. }
    18. else
    19. { //读取失败
    20. std::cerr << sock << " read error!" << std::endl;
    21. break;
    22. }
    23. }
    24. close(sock); //归还文件描述符
    25. }

     我们在start中调用,在mian函数中实例化。

    1. #include"tcp_server.hpp"
    2. int main(int argc,char** argv)
    3. {
    4. if(argc<2)
    5. {
    6. std::cerr << "port" << std::endl;
    7. return -1;
    8. }
    9. //端口号转化
    10. uint16_t port=atoi(argv[1]);
    11. std::string ip="127.0.0.1";
    12. Server server(ip,port);
    13. server.init();
    14. server.start();
    15. return 0;
    16. }

    客户端 

    客户端也与UDP客户端相似,客户端不需要绑定与监听,首先我们创建套接字。

    创建套接字 

     

    1. class Client
    2. {
    3. public:
    4. Client(std::string server_ip , uint16_t server_port)
    5. :_sockfd(-1)
    6. ,_server_ip(server_ip)
    7. ,_server_port(server_port)
    8. {}
    9. bool init()
    10. {
    11. //创建套接字
    12. _sockfd=socket(AF_INET,SOCK_STREAM,0);
    13. if(_sockfd<0)
    14. {
    15. std::cerr << "socket error" <<std::endl;
    16. return false;
    17. }
    18. return true;
    19. }
    20. ~Client()
    21. {
    22. if(_sockfd>=0)
    23. {
    24. close(_sockfd);
    25. }
    26. }
    27. private:
    28. uint16_t _server_port;
    29. std::string _server_ip;
    30. int _sockfd;
    31. };

    连接服务器 

    发起连接请求的函数叫做connect,该函数的函数原型如下:

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

    参数说明:

    • sockfd:特定的套接字,表示通过该套接字发起连接请求。
    • addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
    • addrlen:传入的addr结构体的长度。

    返回值说明:

    • 连接或绑定成功返回0,连接失败返回-1,同时错误码会被设置。
    1. void start()
    2. {
    3. char buffer[1024];
    4. struct sockaddr_in server;
    5. memset(&server, '\0', sizeof(server));
    6. //协议家族
    7. server.sin_family = AF_INET;
    8. //端口号
    9. server.sin_port=htons(_server_port);
    10. //IP
    11. server.sin_addr.s_addr = inet_addr(_server_ip.c_str());
    12. socklen_t len=sizeof(server);
    13. //连接
    14. if(connect(_sockfd,(struct sockaddr*)&server,len) <0)
    15. {
    16. std::cout << "connect false" <<std::endl;
    17. return;
    18. }
    19. while (1)
    20. {
    21. std::cout << "Please enter# ";
    22. std::cin.getline(buffer, sizeof(buffer));
    23. std::string s=buffer;
    24. write(_sockfd,s.c_str(),s.size());
    25. }
    26. }

    我们在main中实例化并调用。

    1. #include"tcp_client.hpp"
    2. int main(int argc,char** argv)
    3. {
    4. if(argc < 3)
    5. {
    6. std::cout << "name ip port" << std::endl;
    7. return -1;
    8. }
    9. uint16_t port=atoi(argv[2]);
    10. std::string ip=argv[1];
    11. Client client(ip,port);
    12. client.init();
    13. client.start();
    14. return 0;
    15. }

    我们简单测试一下。 

    863ee401e5214f4e85c692f132ee017a.png

     可以看到他们成功完成了通讯。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    收发电子邮件
    第二篇------Virtual I/O Device (VIRTIO) Version 1.1
    钉钉h5微应用调试 整理
    [AR Foundation] AR Foundation学习之路(持续记录)
    MySQL表的增删改查--你都知道吗?
    linux系统ELK组件介绍
    从“白人饭”到美味佳肴,拓世AI为你打造独一无二的饮食计划
    Prometheus系列第十篇一核心之micrometer源码分析一micrometer-registry-prometheus核心实现
    计算机网络五层协议的体系结构
    图神经网络 | 混合神经网络模型GCTN地铁客流预测
  • 原文地址:https://blog.csdn.net/2301_80926085/article/details/140986191