一、服务与地址的关系
- 一个端口代表一个服务(服务进程),因此可以使用服务名来代替端口的使用
- 像主机一样,服务也通常靠名字来认知
- 如果我们在程序代码中通过其名字而不是其端口号来指代一个服务,而且从名字到端口号的映射关系保存在一个文件中(通常是/etc/services),那么即使端口号发生变动,我们需修改的仅仅是/etc/services文件中的某 一行,而不必重新编译应用程序。getservbyname函数用于根据给定名字查找相应服务
二、struct servent结构体
struct servent {
char *s_name; /* official service name */
char **s_aliases; /* alias list */
int s_port; /* port number */
char *s_proto; /* protocol to use */
}
参数
- s_name:服务的官方名称
- s_aliases:服务别名列表
- s_port:服务对应的端口号
- s_proto:与此服务一起使用的协议的名称
三、getservbyname
#include <netdb.h>
struct servent *getservbyname(const char *servname, const char *protoname);
- 功能:通过服务名与服务的协议类型返回一个struct servent结构体信息
参数
返回值
- 成功:返回struct servent结构体指针
- 失败:返回NULL
参数使用的注意事项
- 参数1必须指定,参数2可以选择忽略或指定
- 如果protoname指定:如果同时指定了协议(即protoname参数为非空指针),那么指定服务必须有匹配的协议。有些因特网服务既用TCP也用UDP提供(例如DNS),其他因特网服务则仅仅支持单个协议(例如FTP要求使用TCP,而不能使用UDP)
- 如果protoname未指定:如果protoname未指定而servname指定服务支持多个协议,那么返回哪个端口号取决于实现。通常情况下这种选择无关紧要,因为支持多个协议的服务往往使用相同的TCP端口号和UDP端口号,不过这点 并没有保证
- 例如:既然FTP仅仅支持TCP,第二个调用和第三个调用等效,第四个调用则会失败
struct servent *sptr;
sptr = getservbyname("domain", "udp"); /* DNS using UDP */
sptr = getservbyname("ftp", "tcp"); /* FTP using TCP */
sptr = getservbyname("ftp", NULL); /* FTP using TCP */
sptr = getservbyname("ftp", "udp"); /* this call will fail */
演示案例
//程序传入主机名和服务名,先根据主机名得到IP
//然后根据服务名获得端口。然后创建一个套接字连接这个IP和端口对应的服务
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<string.h>
#include<netdb.h>
#define MAXLINE 1024
int Socket(int domain,int type,int protocal);
int main(int argc, char **argv)
{
int sockfd, n;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
struct in_addr **pptr;
struct in_addr *inetaddrp[2];
struct in_addr inetaddr;
struct hostent *hp; //保存主机信息
struct servent *sp; //保存服务信息
if (argc != 3){
perror("usage: daytimetcpcli1 <hostname> <service>");
exit(1);
}
//通过域名解析获得主机的信息
if ( (hp = gethostbyname(argv[1])) == NULL)//如果没有解析到
{
if (inet_aton(argv[1], &inetaddr) == 0) {//将字符串IP转为网络IP
printf("hostname error for %s: %s\n", argv[1], hstrerror(h_errno));
exit(2);
} else {//转换的网络IP存放在inetaddrp[0]中
inetaddrp[0] = &inetaddr;
inetaddrp[1] = NULL;
pptr = inetaddrp;
}
}
//将解析到的IP地址列表给pptr
else {
pptr = (struct in_addr **) hp->h_addr_list;
}
//解析服务信息
if ((sp = getservbyname(argv[2], "tcp")) == NULL){
printf("getservbyname error for %s\n", argv[2]);
exit(3);
}
//尝试连接这个服务
for ( ; *pptr != NULL; pptr++)
{
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = sp->s_port;
memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
printf("trying %s\n",inet_ntoa((struct in_addr)servaddr.sin_addr));
//连接服务
if (connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) == 0)
break; //连接成功就break
perror("connect error");
close(sockfd);
}
if (*pptr == NULL){
perror("unable to connect");
exit(5);
}
while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0;
fputs(recvline, stdout);
}
exit(0);
}
int Socket(int domain,int type,int protocal)
{
int sockFd=socket(domain,type,protocal);
if(sockFd<0){
perror("socket");
exit(4);
}
return sockFd;
}
四、getservbyport
#include <netdb.h>
struct servent *getservbyport(int port, const char *proto);
//返回值:成功返回struct servent结构体指针;失败返回NULL
- 功能:通过端口号与端口对应的服务协议类型返回一个struct servent结构体信息
注意
struct servent *sptr;
sptr = getservbyport(htons(53),"udp"); /* DNS using UDP */
sptr = getservbyport(htons(21), "tcp"); /* FTP using TCP */
sptr = getservbyport(htons(21), NULL); /* FTP using TCP */
sptr = getservbyport(htons(21), "udp"); /* this call will fail */
- 注意事项二:一个端口可能有多种协议类型,因此参数2对应于不同的协议类型,返回的信息也不同
- 必须清楚的是,有些端口号在TCP上用于一种服务,在UDP上却用于完全不同的另一种服 务。例如:表明端口514在TCP上由rsh命令使用,在UDP上却由syslog守护进程使用。512~514范围内的端口都有这个特性