SOCKET编程(3):相关结构体与函数

相关结构体与函数

sockaddr、sockaddr_in结构体

sockaddr和sockaddr_in详解

struct sockaddr共16字节,协议族(family)占2字节,IP地址和端口号在sa_data字符数组中

/* Structure describing a generic socket address.  */
struct sockaddr
{
  __SOCKADDR_COMMON(sa_); /* Common data: address family and length.  */
  char sa_data[14];       /* Address data.  */
};

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

struct sockaddr_in更细致地划分了协议族、端口号和IP地址,其中IP地址定义了新的结构体struct in_addr该结构体中宏定义了uint32_t类型的变量,sin_zero字符数组存在的意义是为了使struct sockaddr_instruct sockaddr 大小相等,便于进行强制类型转换(与bind()等函数的参数有关)

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
  __SOCKADDR_COMMON(sin_);
  in_port_t sin_port;      /* Port number.  */
  struct in_addr sin_addr; /* Internet address.  */

  /* Pad to size of `struct sockaddr'.  */
  unsigned char sin_zero[sizeof(struct sockaddr) -
                         __SOCKADDR_COMMON_SIZE -
                         sizeof(in_port_t) -
                         sizeof(struct in_addr)];
};

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
  in_addr_t s_addr;
};

总结

  1. 二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr
  2. sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址
  3. sockaddr_in 是internet环境下套接字的地址形式
  4. 在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化
  5. 一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数

socket()函数

socket函数用于创建一个新的socket,也就是向系统申清一个socket资源

socket函数用户客户端和服务端

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

domain:协议域,又称协议族(family)。常用的协议族有AF INET、AF INET6、AF LOCAL(或称AF UNIX,Unix域Socket)、AF ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址

type:指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式socket(SOCK_STREAM)是一种面向连接的socket,针对于面向连接的TCP服务应用。数据报式socket(SOCK_DGRAM)是一种无连接的socket,对应于无连接的UDP服务应用

protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议

💡 第一个参数只能填AF INET,第二个参数只能填SOCK STREAM,第三个参数只填0

除非系统资料耗尽,socket函数一般不会返回失败

返回值:成功则返回一个socket,失败返回-1,错误原因存于errno中

💡 “资源耗尽”即Linux对打开文件数的限制,见2022-06-10笔记

// 第1步:创建服务端的socket
int listenfd;
for (int i = 0; i < 2000; ++i)
{
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        return -1;
    }
    cout << "sock id:" << listenfd << endl;
}
sock id:1012
sock id:1013
sock id:1014
sock id:1015
sock id:1016
sock id:1017
sock id:1018
sock id:1019
sock id:1020
sock id:1021
sock id:1022
sock id:1023
socket: Too many open files

inet_addr() 和inet_ntoa()函数

使用socket进行通信的时候,我们需要指定三个元素:通信域(地址族)、IP地址、端口号,这三个元素由SOCKADDR_IN结构体定义

为了简化编程一般将IP地址设置为INADDR_ANY,如果需要使用特定的IP地址则需要使用inet_addr()** 和**inet_ntoa()函数

inet_addr()inet_ntoa()完成字符串和in_addr结构体的互换

inet_addr()函数参数cp代表点分十进制的IP地址,如1.2.3.4,返回值为in_addr_t 类型

/* Convert Internet host address from numbers-and-dots notation in CP
   into binary data in network byte order.  */
extern in_addr_t inet_addr (const char *__cp) __THROW;

inet_ntoa() 函数输入为in_addr结构体而输出为字符串

/* Convert Internet number in IN to ASCII representation.  The return value
   is a pointer to an internal array containing the string.  */
extern char *inet_ntoa (struct in_addr __in) __THROW;

hostent结构体

hostent实例详解

/* Description of data base entry for a single host.  */
struct hostent
{
  char *h_name;			/* Official name of host.  */
  char **h_aliases;		/* Alias list.  */
  int h_addrtype;		/* Host address type.  */
  int h_length;			/* Length of address.  */
  char **h_addr_list;		/* List of addresses from name server.  */
#ifdef __USE_MISC
# define	h_addr	h_addr_list[0] /* Address, for backward compatibility.*/
#endif
};
struct hostent
	{
		char *h_name;         //正式主机名
		char **h_aliases;     //主机别名
		int h_addrtype;       //主机IP地址类型:IPV4-AF_INET
		int h_length;		      //主机IP地址字节长度,对于IPv4是四字节,即32位
		char **h_addr_list;	  //主机的IP地址列表
	};
	
	#define h_addr h_addr_list[0]   //保存的是IP地址
  • h_name:官方域名(Official domain name)。官方域名代表某一主页,但实际上一些著名公司的域名并未用官方域名注册
  • h_aliases:别名,可以通过多个域名访问同一主机。同一 IP 地址可以绑定多个域名,因此除了当前域名还可以指定其他域名
  • h_addrtype:gethostbyname() 不仅支持 IPv4,还支持 IPv6,可以通过此成员获取IP地址的地址族(地址类型)信息,IPv4 对应 AF_INET,IPv6 对应 AF_INET6
  • h_length:保存IP地址长度。IPv4 的长度为 4 个字节,IPv6 的长度为 16 个字节
  • h_addr_list:这是最重要的成员。通过该成员以整数形式保存域名对应的 IP 地址。对于用户较多的服务器,可能会分配多个 IP 地址给同一域名,利用多个服务器进行均衡负载

在这里插入图片描述

在实际的应用中,一台服务器往往有好几个IP地址,而域名只有一个,这样设计的好处是,可以使系统分布设计,提升服务器的稳定性和抗灾难能力

一般对服务器的访问,则是先经过DNS(Domain Name System)服务器,DNS通过均衡设计,返回合适的IP与客户端进行交互,避免客户端只连接一个IP,导致网络拥堵

gethostbyname()函数

gethostbyname()函数:通过域名获取IP地址

gethostbyname()函数详解

客户端中直接使用IP地址会有很大的弊端,一旦IP地址变化(IP地址会经常变动),客户端软件就会出现错误
而使用域名会方便很多,注册后的域名只要每年续费就永远属于自己的,更换IP地址时修改域名解析即可,不会影响软件的正常使用

域名仅仅是 IP 地址的一个助记符,目的是方便记忆,通过域名并不能找到目标计算机,通信之前必须要将域名转换成 IP 地址

gethostbyname() 函数可以完成这种转换

/* Return entry from host data base for host with NAME.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern struct hostent *gethostbyname (const char *__name);

bind()函数

服务端用于将把用于通信的地址和端口绑定到socket上

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

sockfd:需要绑定的socket

addr:存放了服务端用于通信的地址和端口

addrlen:表示addr结构体的大小

返回值:成功则返回0,失败返回-1,错误原因存于errno中

如果绑定的地址错误,或端口已被占用,bind函数一定会报错,否则一般不会返回错误

💡 注意第二个参数为sockaddr结构体指针

在这里插入图片描述

listen()函数

listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程

在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接

/* Prepare to accept connections on socket FD.
   N connection requests will be queued before further requests are refused.
   Returns 0 on success, -1 for errors.  */
extern int listen (int __fd, int __n) __THROW;

_fd:服务端的socket,标识绑定的,未连接的套接字的描述符

-n:挂起的连接队列的最大长度

  • 比如有100个用户链接请求,但是系统一次只能处理20个,那么剩下的80个不能不理人家,所以系统就创建个队列记录这些暂时不能处理,一会儿处理的连接请求,依先后顺序处理,那这个队列到底多大?就是这个参数设置,比如2,那么就允许两个新链接排队。这个不能无限大,那内存就不够了
  • 可以手动设置这个参数,但是别太大
  • 我们一般填写这个参数为SOMAXCONN ,让系统自动选择最合适的个数

connect()函数

#include <sys/types.h> 					
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
第一个参数:int sockdf:
		    socket文件描述符
第二个参数: const struct sockaddr *addr:
			传入参数,指定服务器端地址信息,含IP地址和端口号
第三个参数:socklen_t addrlen:
			传入参数,传入sizeof(addr)大小
返回值:
	成功: 0
	失败:-1,设置errno

当客户端调用 connect()函数之后,发生一下情况之一才会返回(完成函数调用)

  • 服务器端接收连接请求
  • 发生断网的异常情况而终端连接请求

需要注意的是,所谓的“接收连接”并不意味着服务器调用 accept()函数,其实是服务器端把连接请求信息记录到等待队列,因此 connect()函数返回后并不进行数据交换,而是要等服务器端 accept 之后才能进行数据交换(read、write)

客户端端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址

accept()函数

socket的accept函数解析

socket中accept()函数的理解

/* Await a connection on socket FD.
   When a connection arrives, open a new socket to communicate with it,
   set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
   peer and *ADDR_LEN to the address's actual length, and return the
   new socket's descriptor, or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int accept (int __fd, __SOCKADDR_ARG __addr,
		   socklen_t *__restrict __addr_len);

在这里插入图片描述

send()函数

send函数用于把数据通过socket发送给对端

不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd为已建立好连接的socket
  • buf为需要发送的数据的内存地址,可以是C语言基本数据类型变量的地址,也可以数组、结构体、字符串,内存中有什么就发送什么
  • len需要发送的数据的长度,为buf中有效数据的长度
  • flags填0,其他数值意义不大
  • 函数返回已发送的字符数,出错时返回-1,错误信息errno被标记
  • 注意,就算是网络断开,或socket已被对端关闭,send函数不会立即报错,要过几秒才会报错
  • 如果send函数返回的错误(<=0),表示通信链路已不可用

recv()函数

recv函数用于接收对端通过socket发送过来的数据

不论是客户端还是服务端,应用程序都用recv函数接收来自TCP连接的另一端发送过来数据

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数信息同send()函数

函数返回已接收的字符数,出错时返回-1,失败时不会设置errno的值

  • 如果socket的对端没有发送数据,recv函数就会等待
  • 如果对端发送了数据,函数返回接收到的字符数
  • 出错时返回-1,如果socket被对端关闭,返回值为0
  • 如果recv函数返回的错误(<=0),表示通信通道已不可用

💡 数据收发时的数据量会受到发送缓冲区和接收缓冲区大小的限制
数据收发时要注意字节序的问题(不同主机字节序的问题)

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prejudices

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值