当前位置:   article > 正文

Linux网络编程:套接字编程

Linux网络编程:套接字编程

1.Socket套接字编程

1.1.什么是socket套接字编程

Socket套接字编程 是一种基于网络层和传输层网络通信方式,它允许不同主机上的应用程序之间进行双向的数据通信。Socket是网络通信的基本构件,它提供了不同主机间的进程间通信端点的抽象。一个Socket就是一个通信端点,它提供了应用程序访问网络通信协议(如TCP/IP)的接口。并且Socket编程基于客户端(Client)和服务器端(Server)进行全双工通信!!!

  • 客户端:通常指的是发起连接请求的一方,它使用Socket API创建一个Socket对象,并指定要连接的服务器地址和端口号,然后向服务器发送连接请求。连接建立后,客户端就可以通过Socket发送和接收数据了。
  • 服务器端:则是监听来自客户端的连接请求的一方。服务器端也使用Socket API创建一个Socket对象,并绑定到一个指定的地址和端口号上,然后开始监听来自客户端的连接请求。当有客户端连接时,服务器端会接受这个连接,并创建一个新的Socket对象来与这个客户端进行通信。

简单来说,Socket编程就是使用Socket API(应用程序接口)来编写网络应用程序。这些网络应用程序可以是客户端,也可以是服务器端,它们通过Socket进行数据的发送和接收。 

1.2.如何进行socket套接字编程

首先socket套接字编程是基于TCP、IP四层网络协议栈实现的,而在传输层协议中UDP协议是无连接、面向数据报的,TCP协议是有链接、面向字节流的,因此系统维护了两套Socket套接字编程接口,给UDP场景和TCP场景使用!!!

1.2.1.UDP的套接字编程

  1. // UDP服务器
  2. {
  3. int port; // 服务器开放的端口号
  4. int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); // 网络通信,UDP套接字
  5. // 填充sockaddr结构体对象
  6. struct sockaddr_in local;
  7. bzero(&local, sizeof(local)); // 初始化结构体
  8. local.sin_family = AF_INET; // 绑定网络通信
  9. local.sin_port = htons(); // 绑定端口
  10. local.sin_addr.s_addr = INADDR_ANY; // 允许所有外来ip访问
  11. // 绑定指定网络信息和指定的文件系统
  12. int n = ::bind(sock_fd, (struct sockaddr *)&local, sizeof(local));
  13. // 获取客户端信息
  14. char buff_r[1024];
  15. sockaddr_in peer;
  16. socklen_t len = sizeof(peer);
  17. ssize_t n = recvfrom(sock_fd, buff_r, sizeof(buff_r) - 1, 0, (struct sockaddr *)&peer, &len);
  18. // 给客户端发送信息
  19. std::string buffer;
  20. ssize_t m = sendto(sock_fd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&peer, &len);
  21. }

服务器进行套接字编程流程:

  1. 通过socket函数获取套接字,其中SOCK_DGRAM对应UDP协议
  2. 构建一个sockaddr_in对象,并绑定端口和设置允许任意的IP地址访问服务器
  3. 接着通过bind函数显性绑定套接字和sockaddr_in对象
  4. 接下来就可以和客户端进行IO通信了!!! 
  1. / UDP客户端
  2. {
  3. int port_server; // 链接服务器的端口号
  4. int ip_server; // 链接服务器的端口号
  5. int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); // 网络通信,UDP套接字
  6. struct sockaddr_in server;
  7. bzero(&server, sizeof(server)); // 初始化结构体
  8. server.sin_family = AF_INET; // 设置为网络协议
  9. server.sin_port = htons(port_server); // 绑定服务器端口
  10. server.sin_addr.s_addr = inet_addr(ip_server.c_str()); // 实现ip的动态绑定
  11. // 客户端不用通过bind函数显性绑定套接字
  12. // 因为服务器先启动,已经获得了套接字,只要绑定服务器的ip和端口就能使用这个套接字
  13. // 向服务器发送信息
  14. std::string buffer;
  15. ssize_t n = sendto(sock_fd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&server, sizeof(server));
  16. // 从服务端获取信息
  17. char buff_r[1024];
  18. struct sockaddr_in client;
  19. socklen_t len = sizeof(client);
  20. ssize_t m = recvfrom(sock_fd, buff_r, sizeof(buff_r) - 1, 0, (struct sockaddr *)&client, &len);
  21. }

 客户端进行套接字编程的流程:

  1. 通过socket函数获取套接字,其中SOCK_DGRAM对应UDP协议
  2. 绑定服务器端口和服务器ip地址
  3. 直接进行和服务器的IO通信

服务端和客户端Socket编程的异同 

  • 相同的是:都需要调用socket函数来获取套接字,设置网络协议为AF_INET和SOCK_DGRAM,并且需要设置sockaddr_in结构体,初始化这个结构体的内置变量。均共用一套IO的接口sendto和recvfrom。
  • 不同的是:服务端的IP地址设置为INADDR_ANY,表示可以绑定多个IP地址,这也符合服务器需要和多台客户端进行IO的特性。另外服务端需要显性地绑定socket_fd(套接字文件描述符)和sockaddr_in(IPV4套接字结构)。而客户端需要绑定唯一一个服务器的IP,并且不需要显性的绑定socket_fd和sockaddr_in。

1.2.2.TCP套接字编程

TCP协议是面向连接的,所以与UDP套接字流程相比,除了绑定套接字,TCP需要在通信之前先建立连接,具体来说就是:服务器监听客户端发出链接请求请求,接着客户端发出connect请求,最终服务器接收请求,获取一个通信的套接字,最终完成链接的建立。接着再进行IO通信!!!

  1. // TCP服务器
  2. {
  3. // 创建监听套接字
  4. int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
  5. // 定义并配置本地
  6. struct sockaddr_in local;
  7. bzero(&local, sizeof(local));
  8. local.sin_family = AF_INET;
  9. local.sin_port = htons(_port);
  10. local.sin_addr.s_addr = INADDR_ANY;
  11. int n = ::bind(listen_sock, (struct sockaddr *)&local, sizeof(local));
  12. // TCP是面向连接的,需要监听client的链接
  13. int m = listen(listen_sock, 5); // 对listen这个套接字进行监听是否完成链接,设置全连接队列为5
  14. // 获取连接
  15. struct sockaddr_in peer;
  16. socklen_t len = sizeof(peer);
  17. // accept返回的新的套接字(通信套接字)
  18. int sock_fd = accept(_listen_sock, (struct sockaddr *)&peer, &len); // 用于数据通信,accept未接收会阻塞(未完成通信)!!
  19. // 通过readwrite函数进行IO通信
  20. std::string buffer;
  21. ssize_t m = write(sock_fd, buffer.c_str(), buffer.size());
  22. char buffer_read[1024];
  23. ssize_t n = read(sock_fd, buffer_read, sizeof(buffer_read));
  24. }

TCP服务端套接字编程的流程:

  1. 创建监听的套接字,然后设置协议为AF_INET和SOCK_STREAM(TCP专用)
  2. 定义并配置套接字结构体,最后进行监听套接字和网络套接字的结构体绑定
  3. 进行监听(在此期间等待客户端的connect请求)
  4. 设置网络套接字来接收客户端的信息,并通过accept函数获取到新的通信套接字
  5. 进行通信IO
  1. // TCP客户端
  2. {
  3. // 创建套接字
  4. int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  5. // 绑定服务器
  6. struct sockaddr_in server;
  7. server.sin_family = AF_INET;
  8. server.sin_port = htons(server_port);
  9. inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);
  10. // TCP链接服务器
  11. int n = connect(sock_fd, (struct sockaddr *)&server, sizeof(server));
  12. // 通过readwrite函数进行IO通信
  13. std::string buffer;
  14. ssize_t m = write(sock_fd, buffer.c_str(), buffer.size());
  15. char buffer_read[1024];
  16. ssize_t n = read(sock_fd, buffer_read, sizeof(buffer_read));
  17. }

TCP客户端套接字编程流程:

  1. 创建套接字,并将网络套接字结构体绑定到服务器
  2. 调用connect发起链接服务器的请求(此时处于服务端监听状态)
  3. 完成链接,进行IO通信

 到了这里,我们已经知道如何用代码来构建UDP、TCP通信最基本的架构了,而TCP是面向连接的,这也体现在listen、connect、accept这三个函数中(跟三次握手紧密相关,但不等同)。

 如图为TCP中服务端和客户端建立通信的过程。

2.理解Socket套接字编程结构

我们在1.2中学习了如何搭建Socket套接字编程的结构,但是我们还不知道什么是Socket、什么是sockaddr_in和为什么要将sockaddr_in类型强转为(struct sockaddr*)等等,所以在有了对Socket编程的使用理解的基础上,我们来讲一下原理!!!

2.1.网络字节序

我们知道主机的地址排布,也就是字节序是可能存在不同的,大端机的字节序排列为高地址,小端机的字节序排列为低地址,那么这样就会导致在网络通信时,字节序读取不一致导致数据不一致问题。

例如在大端机中,32位整数,0x12345678,地址排布为:78563412,小端机则表示为:12345678

所以为了统一字节序的读取,在套接字编程中需要对网络字节序进行规定,以大端字节序为网络字节序标准,进行读取。我们在回到我们的代码中:

local.sin_port = htons(port);    // htons即为字节序转换函数 

而这些htons函数为系统提供的转换字节序的接口函数!

  1. #include <arpa/inet.h>//必须包含的头文件
  2. // 主机序列转网络序列
  3. uint32_t htonl(uint32_t hostlong);//将主机上unsigned int类型的数据转换成对应网络字节序
  4. uint16_t htons(uint16_t hostshort);//将主机上unsigned short类型的数据转换成对应网络字节序
  5. // 网络序列转主机序列
  6. uint32_t ntohl(uint32_t netlong);//将从网络中读取的unsigned int类型的数据转换成当前计算机字节序
  7. uint16_t ntohs(uint16_t netshort);//将从网络中读取的unsigned short类型的数据转换成当前计算机字节序

 所以当我们在网络中获取了一些字节序数据,我们需要对他进行大端字节序的转换成符合本机的字节序列。

这里即为将网络获取的数据字节序转化为本机的数据字节序,而htons即为将本机的字节序数据转化为网络字节序(大端) 

2.2.网络套接字结构体

我们之前在1.2.提及了一个新名称“网络套接字结构体”,而这个结构体用于标识网络通信的端点,包括IP地址、端口号和地址族等信息。

  1. // 通用网络套接字结构
  2. struct sockaddr
  3. {
  4. __SOCKADDR_COMMON(sa_); /* Common data: address family and length. */
  5. char sa_data[14]; /* Address data. */
  6. };
  7. // 网络套接字结构
  8. struct sockaddr_in
  9. {
  10. __SOCKADDR_COMMON(sin_);
  11. in_port_t sin_port; /* Port number. */
  12. struct in_addr sin_addr; /* Internet address. */
  13. /* Pad to size of `struct sockaddr'. */
  14. unsigned char sin_zero[sizeof(struct sockaddr) -
  15. __SOCKADDR_COMMON_SIZE -
  16. sizeof(in_port_t) -
  17. sizeof(struct in_addr)];
  18. };

如图:我们在网络进行IO通信时,就是传输这个通用结构体对象sockaddr,所以我们在1.2.中的代码中也经常看到类型转换为(struct sockaddr *)。而这里也可以看作是C语言实现的多态,其中sockaddr为基类、sockaddr_in和sockaddr_un为派生类。

3.文件+socket+系统+网络

66-2小时33分

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/793959
推荐阅读
相关标签
  

闽ICP备14008679号