网络套接字与寻址

1 套接字描述

套接字是通信端点的抽象,创建一个套接字使用如下函数:

#include <sys/socket.h>

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

返回值:若成功,返回套接字描述符;若出错,返回-1.

参数:

domain: 指定通信的特征,包括地址格式,以AF_开头的常数表示地址族(address family):



描述


AF_INET


IPv4因特网域


AF_INET6


IPv6因特网域


AF_UNIX


UNIX域


AF_UPSPEC


未指定(可以代表任何域)

type: 指定套接字的类型。如下为POSIX.1的套接字类型,也可以增加其他类型的支持:


类型


描述


SOCK_DGRAM


固定长度、无连接、不可靠


SOCK_RAM


IP协议的数据报接口


SOCK_SEQPACKET


固定长度、面向连接、有序、可靠


SOCK_STREAM


有序、可靠、双向、面向连接

protocol: 根据指定的domain和type所提供的默认协议,通常设为0即可。在AF_INET通信域中,SOCK_STREAM默认的协议为TCP;SOCK_DGRAM默认的协议为UDP.

使用无连接的数据报通信类似于邮寄信件,你不能保证传递的次序,信件可能会丢失,每封信都包含收信人地址;相反地,使用面向连接的通信协议类似于打电话,首先需要建立连接,连接建立好以后,彼此可以双向地通信,对话中不需要包含地址信息,连接本身指定了通话的源和目的,就像是端与端之间有一条虚拟链路一样。

SOCK_STREAM套接字提供了字节流服务,应用程序不能分辨出报文的界限;SOCK_SEQPACKET套接字提供了基于报文的服务,这意味着接受的数据量和发送的数据量完全一致;SOCK_RAW套接字提供了一个数据报接口,用于直接访问下面的网络层,使用该套接字时,必须有root用户权限,并且需要应用程序自己负责构造协议头。

套接字通信是双向的,可以使用shutdown函数来禁止一个套接字I/O:

#include <sys/socket.h>

int shutdown(int sockfd, int how);

返回值:若成功,返回0;若出错,返回-1.

参数:

how: SHUT_RD(关闭读),SHUT_WR(关闭写),SHUT_RDWR(关闭读写).

套接字本质上是一个文件描述符,如下为适用于文件描述符的函数在套接字中的表现行为:


文件描述符函数


使用套接字时的行为


close


释放套接字


dup / dup2


复制套接字


fchdir


失败,并且将errno设置为ENOTDIR


fchomod


未指定


fchown


由实现定义


fcntl


支持某些命令


fdatasync / fsync


由实现定义


fstat


支持一些stat结构成员,如何支持由实现定义


ftruncate


未指定


ioctl


依赖于底层设备驱动


lseek


由实现定义,失败时将errno设置为ESPIPE


mmap


未指定


poll


正常工作


pread / pwrite


失败时,将errno设置为ESPIPE


read / readv


与没有任何标志位的recv等价


select


正常工作


write / writev


与没有任何标志位的send等价

2 寻址

:: 不同的处理器架构有着不同的字节序,如果需要实现异构通信,必须统一字节序方式。网络协议指定了字节序(网络字节序),TCP/IP协议栈使用大端方式,对于使用TCP/IP的应用程序,有4个函数可以用来在处理器字节序和网络字节序之间进行转换:

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32);  // 32位主机-->32位网络

uint16_t htons(uint16_t hostint16);  // 16位主机-->16位网络

uint32_t ntohl(uint32_t netint32); // 32位网络-->32位主机

uint16_t ntohs(uint16_t netint16); // 16位网络-->16位主机

说明:

h: 主机字节序

n: 网络字节序

l: 4字节的长整型

s: 2字节的短整型

 1 #include <arpa/inet.h>
 2 #include <stdio.h>
 3
 4 int main(void)
 5 {
 6         unsigned int bytes = 0x12345678;
 7         const char* p = (const char*)&bytes;
 8         printf("%x_%x_%x_%x\n", p[0], p[1], p[2], p[3]);
 9
10         unsigned int netbytes = htonl(bytes);    /* 将主机字节序转换为网络字节序 */
11         p = (const char*)&netbytes;
12         printf("%x_%x_%x_%x\n", p[0], p[1], p[2], p[3]);
13
14         return 0;
15 }


由于地址格式与特定的通信域相关,因此,为了使得不同格式的地址都能够传入套接字函数,所有的地址必须先被强制转换为一个通用的地址结构sockaddr:

struct sockaddr

{

    unsigned char sa_len;    /* total length */

    sa_family_t sa_family;   /* address family */

    char sa_data[14];    /* variable-length address */

};

因特网地址定义在<netinet/in.h>中:

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  sin_addr;   /* IPv4 address */

};

数据类型in_port_t为uint16_t,in_addr_t为uint32_t,在<stdint.h>中定义了这些类型。

:: 如下函数可以在数值地址和文本格式字符串地址之间进行转换:

#include <arpa/inet.h>

/* 将数值地址转换为文本字符串格式 */

const char* inet_ntop(int domain,

           const void *restrict addr,

           char* restrict str, socklen_t size);

/* 将文本字符串格式转换为数值地址 */

int inet_pton(int domain,

       const char* restrict str,

       void* restrict addr);

参数:

domain: AF_INET或者AF_INET6.

size: 指定保存文本字符串缓冲区str的大小,该参数为INET_ADDRSTREAM或者INET6_ADDRSTREAM时,表明使用足够大的空间来存放该地址。

说明:

inet_pton的输出为网络字节序,inet_ntop的输入为网络字节序,要注意转换。

 1 #include <arpa/inet.h>
 2 #include <stdio.h>
 3
 4 int main(void)
 5 {
 6         char dotaddr[] = "192.168.8.128";
 7         struct in_addr ipaddr;
 8
 9         inet_pton(AF_INET, dotaddr, (void*)&ipaddr);
10
11         ipaddr.s_addr = ntohl(ipaddr.s_addr);    /* 将网络字节序转换为主机字节序 */
12         printf("addr = %x\n", ipaddr.s_addr);
13
14         ipaddr.s_addr = htonl(ipaddr.s_addr);    /* 将主机字节序转换为网络字节序 */
15         inet_ntop(AF_INET, (void*)&ipaddr, dotaddr, 16);
16         printf("addr = %s\n", dotaddr);
17
18         return 0;
19 }            


地址查询:

#include <netdb.h>

struct hostent* gethostent(void);      /* 返回下一个文件 */

void sethostent(int stayopen);       /* 打开文件 */

void endhostent(void);                   /* 关闭文件 */

:: gethostent返回一个指向hostent结构体的指针,hostent结构如下:

struct hostent

{

    char *h_name;                          /* 主机名 */

    char **h_aliases;                   /* 可选的别名列表 */

    int h_addrtype;                        /* 地址类型,一般为AF_INET */

    int h_length;                           /* 地址长度 */

    char **h_addr_list;                      /* 网络地址列表 */

 

    #define h_addr h_addr_list[0];  /* 第一个网络地址 */

};

 

说明:

之所以主机的地址是一个列表的形式,其原因是一个主机可能有多个网络接口。

返回的地址为网络字节序。

 1 #include <stdio.h>
 2 #include <sys/socket.h>
 3 #include <netdb.h>
 4 #include <netinet/in.h>
 5
 6 int main()
 7 {
 8         struct hostent* h;
 9         h = gethostbyname("benxintuzi");
10         printf("host: %s\n", h->h_name);
11         printf("addr: %s\n", inet_ntoa(*((struct in_addr*)h->h_addr)));
12
13         return 0;
14 }


:: 如下函数用来获得网络名字和网络编号:

#include <netdb.h>

struct netent* getnetbyaddr(uint32_t net, int type);

struct netent* getnetbyname(const char* name);

struct netent* getnetent(void);

void setnetent(int stayopen);

void endnetent(void);

netent结构体如下定义:

struct netent

{

    char* n_name;       /* network name */

    char** n_aliases;   /* alternate network name array pointer */

    int n_addrtype;     /* net address type */

    uint32_t n_net;     /* network number*/

};

 1 #include <stdio.h>
 2 #include <netdb.h>
 3
 4 void printnet(struct netent* net)
 5 {
 6         char** p = net->n_aliases;
 7         printf("net name: %s\n", net->n_name);
 8
 9         while(*p != NULL)
10         {
11                 printf("alias name: %s\n", *p);
12                 p++;
13         }
14
15         printf("address type: %d\n", net->n_addrtype);
16
17         printf("net number: %u\n", net->n_net);
18 }
19
20 int main()
21 {
22         struct netent* net = NULL;
23         setnetent(1);
24         while((net = getnetent()) != NULL)
25         {
26                 printnet(net);
27                 printf("\n");
28         }
29         endnetent();
30
31         return 0;
32 }


:: 如下函数用于操作协议名和协议编号:

#include <netdb.h>

struct protoent* getprotobyname(const char* name);

struct protoent* getprotobynumber(int proto);

struct protoent* getprotoent(void);

void setprotoent(int stayopen);

void endprotoent(void);

struct protoent

{

  char *p_name;                 /* Official protocol name.  */

  char **p_aliases;             /* Alias list.  */

  int p_proto;                  /* Protocol number.  */

};

 1 #include <netdb.h>
 2
 3 void printproto(struct protoent* proto)
 4 {
 5         char** p = proto->p_aliases;
 6         printf("proto name: %s\n", proto->p_name);
 7
 8         while(*p != NULL)
 9         {
10                 printf("alias name: %s\n", *p);
11                 p++;
12         }
13
14
15         printf("proto number: %d\n", proto->p_proto);
16 }
17
18 int main()
19 {
20         struct protoent* proto = NULL;
21         setprotoent(1);
22         while((proto = getprotoent()) != NULL)
23         {
24                 printproto(proto);
25                 printf("\n");
26         }
27         endprotoent();
28
29         return 0;
30 }


:: 如下函数用于操作服务于端口号:

#include <netdb.h>

struct servent* getservbyname(const char* name, const char* proto);

struct servent* getservbyport(int port, const char* proto);

struct servent* getservent(void);

void setservent(int stayopen);

void endservent(void);

struct servent

{

  char *s_name;                 /* Official service name.  */

  char **s_aliases;             /* Alias list.  */

  int s_port;                   /* Port number.  */

  char *s_proto;                /* Protocol to use.  */

};

 1 #include <stdio.h>
 2 #include <netdb.h>
 3
 4 void printservent(struct servent* serv)
 5 {
 6         char** p = serv->s_aliases;
 7         printf("servent name: %s\n", serv->s_name);
 8
 9         while(*p != NULL)
10         {
11                 printf("alias name: %s\n", *p);
12                 p++;
13         }
14
15         printf("port number: %d\n", serv->s_port);
16
17         printf("proto to use: %s\n", serv->s_proto);
18 }
19
20 int main()
21 {
22         struct servent* serv = NULL;
23         setservent(1);
24         while((serv = getservent()) != NULL)
25         {
26                 printservent(serv);
27                 printf("\n");
28         }
29         endservent();
30
31         return 0;
32 }


:: POSIX.1中定义的函数getaddrinfo用来代替过时的gethostbyname和gethostbyaddr

#include <sys/socket.h>

#include <netdb.h>

int getaddrinfo(const char* restrict host,

        const char* restrict service,

        const struct addrinfo* restrict hint,

        struct addrinfo** restrict res);

        void freeaddrinfo(sruct addrinfo* ai);

struct addrinfo

{

  int ai_flags;                 /* Input flags.  */

  int ai_family;                /* Protocol family for socket.  */

  int ai_socktype;              /* Socket type.  */

  int ai_protocol;              /* Protocol for socket.  */

  socklen_t ai_addrlen;         /* Length of socket address.  */

  struct sockaddr *ai_addr;     /* Socket address for socket.  */

  char *ai_canonname;           /* Canonical name for service location.  */

  struct addrinfo *ai_next;     /* Pointer to next in list.  */

};

 

getaddrinfo函数根据提供的主机名或服务名返回一个addrinfo链表;freeaddrinfo用来释放一个addrinfo结构体。如果getaddrinfo失败了,必须调用gai_strerror将返回的错误码转换成错误消息:

const char* gai_strerror(int error);

如果host非空,则指向一个长度为hostlen字节的缓冲区,用于存放返回的主机名;同样,如果service非空,则指向一个长度为servlen字节的缓冲区,用于返回的服务名。

具体的flags参数如下:


标志


描述


NI_DGRAM


服务基于数据报而非基于流


NI_NAMEREQD


如果找不到主机名,将其作为一个错误对待


NI_NOFQDN


对于本地主机,仅返回全限定域名的节点名部分


NI_NUMERICHOST


返回主机地址的数字形式,而非主机名


NI_NUMERICSCOPE


对于IPv6,返回数字形式


NI_NUMERICSERV


返回服务地址的数字形式(即端口号)

 1 #include <stdio.h>
 2 #include <arpa/inet.h>
 3 #include <netdb.h>
 4 #include <sys/socket.h>
 5 #include <netinet/in.h>
 6
 7 int main(void)
 8 {
 9         struct addrinfo*        ailist, *aip;
10         struct addrinfo         hint;
11         struct sockaddr_in*     sinp;
12         const char*             addr;
13         int                     err;
14         char                    abuf[INET_ADDRSTRLEN];
15
16         hint.ai_flags   = AI_CANONNAME;
17         hint.ai_family  = 0;
18         hint.ai_socktype        = 0;
19         hint.ai_protocol        = 0;
20         hint.ai_addrlen         = 0;
21         hint.ai_canonname       = NULL;
22         hint.ai_addr            = NULL;
23         hint.ai_next            = NULL;
24
25         if((err = getaddrinfo("benxintuzi", "nfs", &hint, &ailist)) != 0)
26                 printf("getaddrinfo error: %s", gai_strerror(err));
27         for(aip = ailist; aip != NULL; aip = aip->ai_next)
28         {
29                 printf("host: %s\n", aip->ai_canonname ? aip->ai_canonname : "-");
30                 if(aip->ai_family == AF_INET)
31                 {
32                         sinp = (struct sockaddr_in*)aip->ai_addr;
33                         addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);
34                         printf("address: %s\n", addr ? addr : "Unknown");
35
36                 }
37         printf("\n");
38         }
39
40         return 0;
41 }


:: getnameinfo将一个地址转换为一个主机名和一个服务名:

#include <sys/socket.h>

#include <netdb.h>

int getnameinfo(const struct sockaddr* addr,

        socklen_t alen,

        char* restrict host,

        socklen_t hostlen,

        char* restrict service,

        socklen_t servlen,

        int flags);

struct sockaddr

{

unsigned short sa_family;   /* address family, AF_xxx */

char sa_data[14];           /* 14 bytes of protocol address */

};

 

 套接字与地址的关联:

给某个服务器关联一个众所周知的地址,使得客户端可以访问该服务器,最简单的一个办法是服务器保留一个地址并且将其注册在/etc/services中,使用bind函数:

#include <sys/socket.h>

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

地址中的端口号一般不小于1024,并且一般只能将一个套接字端口绑定在一个给定的地址上。

使用getsockname找到绑定到套接字上的地址:

int getsockname(int sockfd,

        struct sockaddr* restrict addr,

        socklen_t* restrict alenp);

alenp指向缓冲区addr的长度。返回时,该整数会被设置成返回地址的大小,如果地址和提供的缓冲区长度不匹配,则地址会被自动截断而不报错。

如果套接字已经和连接上,则可以使用getpeername来找到对方的地址:

int getpeername(int sockfd,

        struct sockaddr* restrict addr,

        socklen_t* restrict alenp);

时间: 2024-08-06 10:25:55

网络套接字与寻址的相关文章

APUE中网络套接字一章——使用pthread改写远程时间服务器

最近在看<Unix环境高级编程>一书,我一直对网络编程有兴趣,所以就直接跳到了网络套接字这一章. 这一章中有一个示例程序:一个TCP客户端向服务器发送连接请求,服务器在接受请求后,调用uptime命令 并将结果返回给客户端,客户端再将其打印出来. 因为前面刚看过线程那一章,所以我想把服务器改造成多线程的,以便同时服务多个线程.但是却碰到 一个问题,调试了半天还是没有进展(linux下调试我真的不是很会),google了下也没找到答案.索性先po 上来,整理下思路,如果有园子里的朋友能够帮忙解答

什么是网络套接字(Socket)?

什么是网络套接字(Socket)?一时还真不好回答,而且网络上也有各种解释,莫衷一是.下文将以本人所查阅到的资料来说明一下什么是Socket. 1. Socket定义 Socket在维基百科的定义: A network socket is an endpoint of an inter-process communication across a computer network. Today, most communication between computers is based on t

进程间通信(10) - 网络套接字(socket)[2]

1.前言 本篇文章的所有例子,基于RHEL6.5平台(linux kernal: 2.6.32-431.el6.i686). 在前一篇文章中(点此链接),已经介绍了socket(),bind(),listen(),connect(),accept()这些函数. 至此,服务器与客户机已经建立好了连接.可以调用网络I/O进行读写操作了,即实现网络中不同进程之间的通信.网络I/O操作有下面的几组函数: · read() / write() · readv() / writev() · send() /

网络 套接字编程 TCP、UDP

网络是大端 发数据从低地址发, 先发的是 高位的数据. 收数据从高位收,先收到的数据存放到低地址. TCP 是 流式的 所用套接字也是流式的 文件描述符 socket 是 IP 加 端口号 用到的函数:         int socket(int domain, int type, int protocol);        int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);        #include <s

Linux - Socket网络套接字

OSI七层协议功能 物理层 面向物理传输媒体,屏蔽媒体的不同 主要定义物理设备标准,如网线的接口类型.光纤的接口类型.各种传输介质的传输速率等.它的主要作用是传输比特流(就是由1.0转化为电流强弱来进行传输,到达目的地后在转化为1.0,也就是我们常说的模数转换与数模转换).这一层的数据叫做比特. 链路层 面向一条链路,成帧和无差错传输 主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装.常把这一层的数据叫做帧.在这一层工作的设备是交换机,数据通过交换机来传输. 网络层 分配地址.

进程间通信(10) - 网络套接字(socket)

1.前言 本篇文章的所有例子,基于RHEL6.5平台(linux kernal: 2.6.32-431.el6.i686). 2.网络中进程间通信 本地的进程间通信(IPC)方式有很多种,总结起来,大概可以分为下面的这4类: a).消息传递.包括管道(点此链接),FIFO(点此链接),消息队列(点此链接)等. b).共享内存.包括匿名和具名的(点此链接). c).同步.包括互斥量,条件变量,读写锁,文件和记录锁,信号量等. d).远程过程调用.例如Sun RPC. 但是,这些通信方式,大部分都局

linux 网络套接字

在内核分析网络分组时,底层协议的数据将传输到跟高的层.而发送数据的时候顺序是相反的.每一层都是通过加(首部+净荷)传向跟底层,直至最终发送. 这些操作决定了网络的的性能. 就如下图所示 linux因此设计了一个结构体 如下代码 /** * struct sk_buff - socket buffer * @next: Next buffer in list * @prev: Previous buffer in list * @list: List we are on * @sk: Socket

Java学习笔记(3)----网络套接字服务器多线程版本

本例给出一个客户端程序和一个服务器程序.客户端向服务器发送数据.服务器接收数据,并用它来生成一个结果,然后将这个结果返回给客户端.客户端在控制台上显示结果.在本例中,客户端发送的数据是圆的半径,服务器生成的结果是圆的面积. 客户端通过输出流套接字的 DataOuputStream 发送半径,服务器通过输入流套接字的 DataInputStream 接收半径.服务器计算面积,然后,通过输出流套接字的 DataOutputStream 把它发送给客户端,客户端通过输入流套接字的 DataInputS

网络编程--Socket(套接字)

网络编程 网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯.网络编程中 有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后 如何可靠高效的进行数据传输.在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的 路由,由IP地址可以唯一地确定Internet上的一台主机.而TCP层则提供面向应用的可靠的 或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据 的. 目前较为流行的网络编程模型是客户机/服务器(C/S)结构