第十六章(一) 套接字初识

本文详细介绍了套接字编程的基础知识,包括地址格式、转换函数、查询函数、套接字的绑定、连接、监听和信息获取等核心概念及常用API。通过示例展示了如何使用getaddrinfo函数获取地址信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



地址格式
 一个地址标识一个特定通信域的套接字端点,地址格式与这个特定的通信域相关。为使不同的格式地址能够传入到套接字函数,地址会被强制转化成一个通用的地址结构:

struct sockaddr{
 sa_family_t  sa_family;   //address family
 char   sa_data[];   //variable-length address
 .
 .
 .
};


 在IPv4因特网域(AF_INET)中, 套接字结构地址用以下结构表示:

struct in_addr{
 in_addr_t  s_addr;  //IPv4 address
};

struct sockaddr_in{
 sa_family_t sin_family; //address family
 in_port_t sin_port; //port number
 struct in_addr_t sin_addr; //IPv4 address
};


 最终将转化为结构体 sockaddr 输入到套接字例程中。


有两个函数用于将地址格式进行转换

char *inet_ntop(int domain, void *addr, char *str, socklen_t size)
  //用于将网络字节序的二进制地址转化成文本字符串格式
char *inet_pton(int domain, char *str, void *addr)
  //用于将文本字符串格式转换成网络字节序的二进制地址。



地址查询

<span style="color:#000000;">struct hostent *gethostent(void);
  //可以找到给定计算机系统的主机信息
struct netent *getnetent(void);
  //获取网络编号和网络名字</span>



 我们可以用以下函数在协议名字和协议编号之间进行映射

struct protoent *getprotobyname(char * name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);


 各服务和端口号之间的关系

struct servent *getservbyname(char *name, char *proto)
   //将一个服务名映射到一个端口号 
struct servent *getserbyport(int pert, char *name)
   //将一个端口号映射到一个服务名
struct servent *getservent(void);
   //顺序扫描服务数据库


int getaddrinfo(char *host, char *service, struct addrinfo *hint, struct addrinfo **resg)
  //用于将一个主机名和一个服务名映射到一个地址
  //可以提供一个可选的hint来选择符合特定条件的地址

  如果getaddrinfo调用失败,不能使用perror或strerror来生成错误信息,而是调用
   函数 char *gai_strerror(int error)    
  如果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串联起来的addrinfo结构链表。可以导致返回多个addrinfo结构的情形有以下2个:
   1. 如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
   2. 如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。


 函数 getnameinfo将一个地址转换成一个主机名和一个服务名

 以下两个程序说明 getaddrinfo 的使用方法:

                       注:总是显示bind错误,尚未搜出个明白来。

例一:

#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage: %s hostname\n",
argv[1]);
exit(1);   
}

struct addrinfo *answer, hint, *curr;
char ipstr[16];   
bzero(&hint, sizeof(hint));
hint.ai_family = AF_INET;
hint.ai_socktype = SOCK_STREAM;

int ret = getaddrinfo(argv[1], NULL, &hint, &answer);
if (ret != 0) {
fprintf(stderr,"getaddrinfo: &s\n",
gai_strerror(ret));
exit(1);
}

for (curr = answer; curr != NULL; curr = curr->ai_next) {
inet_ntop(AF_INET,
&(((struct sockaddr_in *)(curr->ai_addr))->sin_addr),
ipstr, 16);
printf("%s\n", ipstr);
}
freeaddrinfo(answer);
exit(0);
}

例二:

void print_family(struct addrinfo *aip);  
void print_type(struct addrinfo *aip);  
void print_flags(struct addrinfo *aip);  
void print_protocol(struct addrinfo *aip);  

int main(int ac, char *av[])
{
	struct addrinfo *ailist, *aip;
	struct addrinfo hint;

	if(ac != 3)
		fprintf(stderr,"Usage : %s nodename service", av[0]);

	hint.ai_family = 0;    				//初始化hint结构体用于过滤
	hint.ai_socktype = 0;    
	hint.ai_protocol = 0;    
	hint.ai_addrlen = 0;    
	hint.ai_flags = AI_CANONNAME;			//需要一个规范名
	hint.ai_addr = NULL;    
	hint.ai_next = NULL;				
	hint.ai_canonname = NULL;

	if(getaddrinfo(av[1].av[2],&hint,&ailist) != 0)
		printf("%s \n",gai_strerror(error));

	for(aip=ailist; aip ; aip=aip->ai_next)				//打印得到的结果
	{
		print_family(aip);  
		print_type(aip);  
		print_protocol(aip);  
		print_flags(aip);  
  
		printf("\n\thost %s", aip->ai_canonname ?aip->ai_canonname:"-");  
		if(aip->ai_family == AF_INET)  
		{  
		/* 获取IP地址,并把网络字节序的二进制地址转换为文本字符串地址 */  
			sinp = (struct sockaddr_in *)aip->ai_addr;  
			addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);  
			printf(" address %s", addr ? addr:"unknown");  
			printf(" port %d", ntohs(sinp->sin_port));  
		}  
		printf("\n");  
	}
	return 0;
}

void print_family(struct addrinfo *aip)  
{  
    printf(" family-- ");  
    switch(aip->ai_family)  
    {  
        case AF_INET://IPv4  
            printf("inet");  
            break;  
        case AF_INET6://IPv6  
            printf("inet6");  
            break;  
        case AF_UNIX://UNIX域  
            printf("Unix");  
            break;  
        case AF_UNSPEC://未指定  
            printf("unspecified");  
            break;  
        default:  
            printf("unknown");  
    }  
}  
void print_type(struct addrinfo *aip)  
{  
    printf(" type.. ");  
    switch(aip->ai_socktype)  
    {  
        case SOCK_STREAM:  
            printf("stream");  
            break;  
        case SOCK_DGRAM:  
            printf("datagram");  
            break;  
        case SOCK_RAW:  
            printf("raw");  
            break;  
        case SOCK_SEQPACKET:  
            printf("seqpacket");  
            break;  
        default:  
            printf("unknown (%d)", aip->ai_socktype);  
    }  
}  
  
void print_protocol(struct addrinfo *aip)  
{  
    printf(" protocol++ ");  
    switch(aip->ai_protocol)  
    {  
        case IPPROTO_TCP:  
            printf("TCP");  
            break;  
        case IPPROTO_UDP:  
            printf("UDP");  
            break;  
        case IPPROTO_SCTP:  
            printf("SCTP");  
            break;  
        case 0:  
            printf("default");  
            break;  
        default:  
            printf("unknown (%d)", aip->ai_protocol);  
    }  
}  
void print_flags(struct addrinfo *aip)  
 {  
     printf(" flags ");  
     if(aip->ai_flags == 0)  
         printf("0");  
     else  
     {  
         if(aip->ai_flags & AI_PASSIVE)  
             printf(" passive ");  
         if(aip->ai_flags & AI_CANONNAME)  
             printf(" canon ");  
         if(aip->ai_flags & AI_NUMERICHOST)  
             printf(" numhost ");  
     }  
 }  


将套接字与地址关联

int bind(int sockfd, struct sockaddr *addr, socklen_t len)
  //用来关联地址和套接字
  /*对于使用的地址有以下一些限制:
   1、在进程正在运行的计算机上,指定的地址必须有效; 不能指定一个其他机器的地址
   2、地址必须和创建套接字时的地址族所支持的格式相匹配
   3、地址中的端口号必须不小于1024,除非该进程具有相应的特权(root身份)
   4、一般只能将一个套接字端点绑定到一个给定地址上,尽管有些协议允许多重绑定
   */

getsockname(int sockfd, struct sockaddr *addr, socklen_t alenp)
  //用来发现绑定到套接字上的地址
  /*在调用 getsockname之前, 将 alenp 设置为一个指向整数的指针。该整数指定缓冲区 sockaddr 的长度。 返回时, 该整数会被设置返回地址的大小。但是,如果地址和提供的缓冲区长度不匹配,地址会被自动截断而不报错。如果当前没有地址绑定到该套接字,结果是未定的。
   */

getpeername(int sockfd, struct sockaddr *addr, socklen_t alenp)
  //如果套接字已经和对等方连接, 可以用来找到对方的地址。

 


建立连接

int connect(int sockfd, struct sockaddr *addr, socklen_t len)
  //在请求服务的进程套接字(客户端)和提供服务的进程套接字之间建立一个连接。
  //所填入的地址是我们想要与之通信的服务器地址。


监听套接字

int listen(int sockfd, int backlog)
  //用于宣告服务器愿意接受连接请求
  //一旦系统满,就会拒绝多余的连接请求,所以backlog的值应该基于服务器期望负载和处理量来选择,其中处理量是指接受连接请求与启动服务的数量。


获取套接字信息

int accept(int sockfd, struct sockaddr *addr, socklen_t len)
 /* 一旦服务器调用了listen, 所用的套接字就接收连接请求。使用 accept 函数获得连接请求并建立连接
  返回的文件描述符是连接到调用connect的客户端的套接字描述符
  如果服务器调用accept,并且当前没有连接请求,服务器会阻塞知道一个链接请求到来。
  另外,服务器可以调用 poll 或 select 来等待一个请求的到来*/




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值