网络编程——socket定义和地址格式

网络编程——socket定义和地址格式


目录

  1. socket 是什么?
  2. 套接字地址格式

1. socket 是什么?

在这里插入图片描述

  1. 网络编程中, socket 翻译为套接字或套接口,指可以通过插口接入的方式,快速完成网络连接和数据收发。
  2. 上图表示网络编程中,客户端和服务器工作的核心逻辑。
  3. 服务器端,在客户端发起连接请求之前,服务器端必须初始化好。
    1. 初始化 socket。
    2. 执行 bind 函数,将服务能力绑定在一个可知的地址和端口上。
    3. 然后执行 listen 操作,将原先的 socket 转化为服务端的 socket。
    4. 服务端最后阻塞在 accept 上等待客户端请求。
  4. 当服务器端已经准备就绪,客户端需要先初始化 socket,再执行 connect 向服务器端的地址和端口发起连接请求,这里的地址和端口必须是客户端预先知道的。connect请求就是TCP 三次握手(Three-way Handshake)。
  5. 三次握手完成后,客户端和服务器端建立连接,就进入了数据传输过程。
    1. 客户端进程向操作系统内核发起 write 字节流写操作,内核协议栈将字节流通过网络设备传输到服务器端,服务器端从内核得到信息,将字节流从内核读入到进程中,并开始业务逻辑的处理,完成之后,服务器端再将得到的结果以同样的方式写给客户端。
    2. 所以一旦连接建立,数据的传输就不再是单向的,而是双向的
  6. 当客户端完成和服务器端的交互后,比如执行一次 Telnet 操作,或者一次 HTTP 请求,需要和服务器端断开连接时,就会执行 close 函数。
    1. 操作系统内核此时会通过原先的连接链路向服务器端发送一个 FIN 包,服务器收到之后执行被动关闭,这时候整个链路处于半关闭状态。
    2. 此后,服务器端也会执行 close 函数,整个链路才会真正关闭。
    3. 半关闭的状态下,发起 close 请求的一方在没有收到对方 FIN 包之前都认为连接是正常的。
    4. 而在全关闭的状态下,双方都感知连接已经关闭。
  7. 以上所有的操作,都是通过 socket 来完成的。无论是客户端的 connect,还是服务端的 accept,或者 read/write 操作等,socket 是用来建立连接,传输数据的唯一途径

2. 套接字地址格式

  1. 在使用套接字时,首先要解决通信双方寻址的问题,需要套接字的地址建立连接,套接字的地址格式如下。

1. 通用套接字地址格式

  1. 套接字的通用地址结构:
/* POSIX.1g 规范规定了地址族为 2 字节的值.  */
typedef unsigned short int sa_family_t;
/* 描述通用套接字地址  */
struct sockaddr{
    sa_family_t sa_family;  /* 地址族.  16-bit*/
    char sa_data[14];   /* 具体的地址值 112-bit */
}; 
  1. 第一个字段是地址族,表示使用什么样的方式对地址进行解释和保存。地址族在 glibc 里的定义非常多,常用的有以下几种:
    • AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,一般用于本地 socket 通信,也可以写成 AF_UNIX、AF_FILE。
    • AF_INET:因特网使用的 IPv4 地址。
    • AF_INET6:因特网使用的 IPv6 地址。
  2. AF_ 表示的含义是 Address Family, PF_表示Protocol Family协议族,比如 PF_INET、PF_INET6 等。用 AF_xxx 的值来初始化 socket 地址,用 PF_xxx 的值来初始化 socket。在 <sys/socket.h> 头文件中可以看到,这两个值是一一对应的。
/* 各种地址族的宏定义  */
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL  PF_LOCAL
#define AF_UNIX   PF_UNIX
#define AF_FILE   PF_FILE
#define AF_INET   PF_INET
#define AF_AX25   PF_AX25
#define AF_IPX    PF_IPX
#define AF_APPLETALK  PF_APPLETALK
#define AF_NETROM PF_NETROM
#define AF_BRIDGE PF_BRIDGE
#define AF_ATMPVC PF_ATMPVC
#define AF_X25    PF_X25
#define AF_INET6  PF_INET6
  1. sockaddr 是一个通用的地址结构,适用于多种地址族。

2. IPv4 套接字格式地址

  1. 常用的 IPv4 地址族的结构:
//IPV4 套接字地址,32bit 值.
typedef uint32_t in_addr_t; 
struct in_addr
  {
    in_addr_t s_addr;
  };
  
struct sockaddr_in //描述 IPV4 的套接字地址格式
  {
    sa_family_t sin_family; 	//16-bit */
    in_port_t sin_port;     	//端口口  16-bit
    struct in_addr sin_addr;    //Internet address. 32-bit
 
    unsigned char sin_zero[8]; 	//这里仅仅用作占位符,不做实际用处
  };
  1. 和 sockaddr 一样,都有一个 16-bit 的 sin_family 字段,对于 IPv4 来说值为 AF_INET。
  2. 端口号,端口号最多是 16-bit,即最大支持 2 的 16 次方(65535)。
    1. 保留端口是约定俗成的,已经被对应服务使用的端口,比如 ftp 的 21 端口,ssh 的 22 端口,http 的 80 端口等。
    2. 一般大于 5000 的端口可以作为自己应用程序的端口使用。
  3. 下面是 glibc 定义的保留端口。
/* Standard well-known ports.  */
enum
  {
    IPPORT_ECHO = 7,    /* Echo service.  */
    IPPORT_DISCARD = 9,   /* Discard transmissions service.  */
    IPPORT_SYSTAT = 11,   /* System status service.  */
    IPPORT_DAYTIME = 13,  /* Time of day service.  */
    IPPORT_NETSTAT = 15,  /* Network status service.  */
    IPPORT_FTP = 21,    /* File Transfer Protocol.  */
    IPPORT_TELNET = 23,   /* Telnet protocol.  */
    IPPORT_SMTP = 25,   /* Simple Mail Transfer Protocol.  */
    IPPORT_TIMESERVER = 37, /* Timeserver service.  */
    IPPORT_NAMESERVER = 42, /* Domain Name Service.  */
    IPPORT_WHOIS = 43,    /* Internet Whois service.  */
    IPPORT_MTP = 57,
 
 
 
 
    IPPORT_TFTP = 69,   /* Trivial File Transfer Protocol.  */
    IPPORT_RJE = 77,
    IPPORT_FINGER = 79,   /* Finger service.  */
    IPPORT_TTYLINK = 87,
    IPPORT_SUPDUP = 95,   /* SUPDUP protocol.  */
 
 
    IPPORT_EXECSERVER = 512,  /* execd service.  */
    IPPORT_LOGINSERVER = 513, /* rlogind service.  */
    IPPORT_CMDSERVER = 514,
    IPPORT_EFSSERVER = 520,
 
 
    /* UDP ports.  */
    IPPORT_BIFFUDP = 512,
    IPPORT_WHOSERVER = 513,
    IPPORT_ROUTESERVER = 520,
 
 
    /* Ports less than this value are reserved for privileged processes.  */
    IPPORT_RESERVED = 1024,
 
 
    /* Ports greater this value are reserved for (non-privileged) servers.  */
    IPPORT_USERRESERVED = 5000
  1. IPv4 地址是一个 32-bit 的字段,最多支持的地址数是 2 的 32 次方,大约是 42 亿。

3. IPv6 套接字地址格式

  1. IPv6 的地址结构:
struct sockaddr_in6
  {
    sa_family_t sin6_family; 	//16-bit
    in_port_t sin6_port;  		//传输端口号 # 16-bit 
    uint32_t sin6_flowinfo; 	//IPv6 流控信息 32-bit
    struct in6_addr sin6_addr;  //IPv6 地址 128-bit
    uint32_t sin6_scope_id; 	//IPv6 域 ID 32-bit
  };
  1. 整个结构体长度是 28 个字节,其中流控信息和域 IP 一个在 glibc 的官网上没出现,另一个是当前未使用的字段。地址族是 AF_INET6,端口同 IPv4 地址一样,关键的地址从 32 位升级到 128 位,完全解决了寻址数字不够的问题。
  2. IPv4 和 IPv6 的地址格式都是因特网套接字的格式,还有一种本地套接字格式,用来作为本地进程间的通信, 即 AF_LOCAL。
struct sockaddr_un {
    unsigned short sun_family; 	//固定为 AF_LOCAL
    char sun_path[108];   		//路径名
};

4. 几种套接字地址格式比较

  1. 几种地址的比较见下图,IPv4 和 IPv6 套接字地址结构的长度是固定的,而本地地址结构的长度是可变的。

image.png

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很高兴能够回答你的问题。以下是一个简单的即时通信系统的C++网络编程示例,基于Linux平台使用socket库进行开发。 客户端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接服务器 int connect_result = connect(client_socket, (sockaddr *) &server_address, sizeof(server_address)); if (connect_result == -1) { cout << "连接服务器失败!" << endl; return -1; } // 发送消息 char message[1024] = {0}; while (true) { cin.getline(message, sizeof(message)); if (strlen(message) == 0) { continue; } send(client_socket, message, strlen(message), 0); memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); return 0; } ``` 服务器端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定socket地址 bind(server_socket, (sockaddr *) &server_address, sizeof(server_address)); // 监听客户端连接 listen(server_socket, 10); // 接受客户端连接 sockaddr_in client_address{}; socklen_t client_address_len = sizeof(client_address); int client_socket = accept(server_socket, (sockaddr *) &client_address, &client_address_len); // 接收消息 char message[1024] = {0}; while (true) { int recv_result = recv(client_socket, message, sizeof(message), 0); if (recv_result <= 0) { break; } cout << "收到消息:" << message << endl; memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); close(server_socket); return 0; } ``` 在上面的代码中,客户端和服务器端都使用了socket库中的函数来完成网络通信。客户端使用`socket`函数创建socket,使用`connect`函数连接服务器,并使用`send`函数发送消息。服务器端使用`socket`函数创建socket,使用`bind`函数将socket服务器地址绑定,使用`listen`函数监听客户端连接,并使用`accept`函数接受客户端连接,最后使用`recv`函数接收客户端发送的消息。 当然,这只是一个简单的示例。在实际开发中,你需要考虑更多的细节,例如如何处理多个客户端连接、如何处理异常情况等等。 希望这个示例能够帮助你了解Linux下C++ socket网络编程的基本流程和方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值