Linux高性能服务器编程--- 网络函数

【摘自《linux高性能服务器编程》】

现代PC大多采用小端字节序,因此称为主机字节序大端字节序称为网络字节序。

Linux提供了如下4个函数来完成主机字节序和网络字节序之间的转换。

#include<netinet/in.h>

Unsigned long int htonl(unsigned long int hostlong);

Unsigned short int htons(unsigned short int hostshort);

Unsigned long int ntohl(unsigned long int netlong);

Unsigned short int ntohs(unsigned short int netshort);

Htonl表示“host to network long”,即将长整型(32bit)的主机字节序数据转化为网络字节序数据。这4个函数中,长整型函数通常用来转换IP地址,短整型函数用来转换端口号(当然不限于此。任何格式化的数据通过网络传输时,都应该使用这些函数来转换字节序)。

点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换:

#include<arpa/inet.h>

In_addr_t inet_addr(const char* strptr);

Int inet_aton(const char* cp, struct in_addr* inp);

Char* inet_ntoa(struct in_addr in);


Inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制表示的IPv4地址。但需要注意的是,该函数内部用一个静态变量存储转化结果,函数的返回该静态内存,因此

Inet_ntoa是不可重入的。

Char *szValue1 = inet_ntoa(“1.2.3.4”);

Char * szValue2 = inet_ntoa(“10.194.71.60”);

Printf(“address 1: %s\n”, szValue1);

Printf(“address 2: %s\n” szValue2);

运行结果为:address1: 10.194.71.60

            Address2:10.194.71.60

 

同时适用IPv4IPv6的转换函数:

#include<arpa/inet.h>

Int inet_pton(int af,const char *src,void* dst);

Const char*  inet_ntop(int af,const void* src,char*dst,socklen_t cnt);

Cnt指定目标存储单元的大小。适用以下两个宏:

#include<netinet/in.h>

#define INET_ADDRSTRLEN 16

#define INET6_ADDRSTRLEN 46

Inet_ntop成功时返回目标存储单元的地址,失败则返回NULL并设置errno.

 

创建socket

#include<sys/types.h>

#include<sys/socket.h>

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

Domain使用那个协议族,PF_INETPF_INET6

Type SOCK_STREAM SOCK_UGRAM

Protocol 前面已经确认了,一般默认为0,使用默认协议。

成功返回socket文件符

 

服务器程序需要命名socket,客户端使用自动分配的socket地址。

服务器端适用bind函数

#include<sys/types.h>

#include<sys/socket.h>

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

 

监听socket

#include<sys/socket.h>

Int listen(int socket,int backlog);

Backlog参数提示内核监听队列的最大长度。ESTABLISHEDSYS_RCVD

 

接受连接

#include<sys/types.h>

#include<sys/socket.h>

int accept(int socket,struct sockaddr *addr,socklen_t *addrlen);

accept只是从监听队列中取出连接,而不论连接处于何种状态(如上面的ESTABLISED

状态和CLOSE_WAIT状态),更不关心任何网络状况的变化。

 

发起连接

服务器通过Listen调用来被动接受连接,那么客户端需要通过主动与服务器建立连接:

#include<sys/types.h>

#include<sys/socket.h>

Int connect(int sockfd,const struct sockaddr * serv_addr,socklen_t addrlne);

sockfd参数由socket系统调用返回一个socketServ_addr参数是服务器监听的socket地址,addrlen参数则制定这个地址的长度。

 

关闭连接

#include<unistd.h>

int close(int fd);

fd参数是待关闭的socket。不过,close系统调用并非总是立即关闭一个连接,而是将fd引用计数减一。只有当fd的引用计数为0时,才真正关闭连接。多进程程序中,一次fork系统调用默认将使父进程中打开的socket的引用计数加1,因此必须在父进程和子进程中都对该socket执行close调用才能将连接关闭。

 

如果需要立即终止连接(而不是将socket的引用计数减1),使用shutdown系统调用(相对close来说,它是专门为网络编程设计的):

#include<sys/socket.h>

Int shutdown(int sockfd ,int howto);


sockfd参数是待关闭的socketHowto参数决定了shutdown的行为。

SHUT_RD:关闭读,舍弃缓冲区

SHUT_WR:关闭写,在真正关闭前将缓冲区的数据发送出去

SHUT_RDWR:同时关闭sockfd上的读和写

Close在关闭连接时只能将socket上的读和写同时关闭。

Shutdown成功时返回0

 

数据读写:

TCP数据读写

#include<sys/types.h>

#include<sys/socket.h>

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

sszie_t  send(int sockfd,const void *buf,  size_t len, int flags);

Flags: MSG_CONFIRM,  MSG_DONTROUTE,  MSG_DONTWAIT,  MSG_MORE,

     MSG_WAITALL,  MSG_PEEK,  MSG_OOB,  MSG_NOSIGNAL

其中MSG_OOB是带外紧急数据,只接受最后一个字节。如“abc”,只接收’c’

Flags参数只对sendrecv的当前调用有效,可以通过setsockopt系统调用永久性修改socket的某些属性。

 

UDP数据读写:

#include<sys/types.h>

#include<sys/socket.h>

ssize_t  recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr

              , socklen_t* addrlen);

ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* 

            Dest_addr, socklen_t addrlen);

Recvfrom/sendto系统调用也可以用于面向连接(STREAM)的 socket的数据读写,

只需要把最后两个参数都设置为NULL以忽略发送端/接收端的socket地址(因为我们已经和对方建立了连接,所以已经知道其socket地址了)。

 

通用数据读写函数:

#include<sys/socket.h>

Ssize_t  recvmsg(int sockfd,struct msghdr* msg, int flags);

Ssize_t sendmsg(int sockfd, structmsghdr* msg, int flags);

Struct msghdr

{

  Void* msg_name;  //socket地址

  Socklen_t msg_namelen;  //socket地址的长度

  Struct iovec* msg_iov;  //分散的内存块

  Int msg_iovlen;  //分散的内存块的数量

  Void* msg_control;  //指向辅助数据的起始位置。

  Socklen_t msg_controllen; //辅助数据的大小

  Int msg_flags;  //复制函数中的flags参数,并在调用过程中更新

};

Struct iovec

{

  Void* iov_base;

  Size_t iov_len;

};

 

带外标记:

#include<sys/socket.h>

Int sockatmark(int sockfd);

Sockatmark判断sockfd是否处于带外标记,即下一个被取到的数据是否是带外数据。

如果是,sockatmark返回1,此时我们就可以利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则sockatmark返回0

内核通知应用程序带外数据到达的两种常见方式是:I/O复用产生的异常事件和SIGURG信号。

 

地址信息函数:

通过socket获取socket地址

#include<sys/socket.h>

Int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len);

Int getpeername(int sockfd, struct sockaddr* address,socklen_t* address_len);

Getsockname获取本端的socket地址。

Getpeername获取sockfd对应的远端socket地址。

 

Socket选项:

#include<sys/socket.h>

int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len);

int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);

 

sockfd参数指定被操作的目标socketLevel参数指定要操作哪个协议的选项(即属性),

比如IPv4,IPv6,TCP等。option_name参数则指定选项的名字。

注意:对服务器而言,有部分socket只能由accept调用返回,而acceptlisten监听socket设置才有效。

监听socket选项特性:对监听socket设置这些socket选项,那么accept返回的连接socket将自动继承这些选项。这些socket选项包括:SO_DEBUGSO_DONTROUTESO_KEEPALIVESO_LINGERSO_OOBINLINESO_RCVBUFSO_RCVLOWAT

SO_SNDBUF,SO_SNDLOWATTCP_MAXSEGTCP_NODELAY。而对客户端而言,这些选项要在调用connect函数之前设置,因为connect调用成功返回之后,TCP三次握手已经完成。

 

SO_REUSEADDR选项:

TCP连接TIME_WAIT状态,并提到服务器程序可以通过设置socket选项SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接占用的socket地址。

重用本地地址

Int sock = socket(PF_INET, SOCK_STREAM,0);

Assert(sock >= 0);

In reuse = 1;

Setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

 

Struct sockaddr_in address;

Bzero(&address, sizeof(address));

Address.sin_family = AF_INET;

Inet_pton(AF_INET, ip ,&address.sin_addr);

Address.sin_port = htons(port);

Int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));

经过setsockopt的设置之后,即使sock处于TIME_WAIT状态,与之绑定的socket地址也可以立即被重用。此外我们也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收被关闭的socket,从而使得TCP连接根本就不进入TIME_WAIT状态,进而允许应用程序立即重用本地socket地址。

 

SO_RCVLOWATSO_SNDLOWAT

TCP接收缓冲区中可读数据的总数大于其低水位标记时,I/O复用系统调用将通知应用程序可以从对应的socket上来读取数据;当TCP发送缓冲区中的空闲空间大于其低水位标记时,I/O复用系统调用将通知应用程序可以往对应的socket上写入数据。默认情况下,该值都为1字节。

 

SO_LINGER

#include<sys/socket.h>

Struct linger

{

  Int l_onoff;//开启非0,关闭0

  Int l_linger;//滞留时间

};

L_onoff == 0//不起作用close用默认方法关闭socket

L_onoff != 0l_linger == 0;//close系统调用立即返回,TCP模块将丢弃被关闭的socket对应的TCP发送缓冲区中残留的数据,同时给对方发送一个复位报文段,这种情况服务器提供了异常终止一个连接的方法。

L_onoff != 0, l_linger > 0;一是被关闭的socket对应的TCP发送缓冲区中是否还有残留的数据;二是该socket是阻塞的,还是非阻塞的socketclose将等待一段长为l_linger的时间,直到TCP模块发送完所有的残留和数据。否则报错。

 

以下为网络信息API

Gethostbynamegethostbyaddr

Gethostbyname函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息。

#include<netdb.h>

Struct hostent * gethostbyname(const char* name);

Struct hostent* gethostbyaddr(const void* addr, size_t len, int type);

Name参数指定目标主机名,addr参数目标主机IP地址,len参数指定addr所指的IP地址长度,type类型,包括AF_INETAF_INET6(用于IPv6);

#include<netdb.h>

Struct hostent

{

   Char* h_name;

   Char** h_aliases;//主机别名列表

   Int h_addrtype;//地址类型

   Int h_length; //地址长度

   Char ** h_addr_list;//按网络字节序列出的主机IP地址列表

}

 

Getservbynamegetservbyport

Getservbyname函数根据名称获取某个服务的完整信息,getservbyport函数根据端口号获取某个服务的完整信息,实际上通过读取/etc/services来获取服务信息。

#include<netdb.h>

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

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

Proto参数指服务类型,tcp,udp,null获取所有类型的服务。

#include<netdb.h>

Struct servent

{

   Char* s_name;//服务名称

   Char** s_aliases;//服务的别名列表,可能多个

   Int s_port;//端口号

   Char* s_proto;//服务类型,通常是tcp或者udp

};

这四个函数都是不可重入的,线程不安全的。

 

Getaddrinfo

函数既能通过主机名获得IP地址

#include<netdb.h>

Int getaddrinfo(const char* hostname, const char* service,const struct 

Addrinfo* hints, struct addrinfo** result);

Hostname参数可以接收主机名,IP地址,service接收服务名或十进制字符串,hints参数是应用程序给getaddrinfo的一个提示,以对getaddrinfo的输出进行更精确的控制。Hints参数是可以被设置为NULL,表示允许getaddrinfo反馈任何可用的结果,result参数指向一个链表,存储getaddrinfo反馈的结果。

Struct addrinfo

{

   Int ai_flags;//见后文

   Int ai_family;//地址族

   Int ai_socktype;//服务类型,SOCK_STREAMSOCK_DGRAM

   Int ai_protocol;//见后文

   Socklen_t  ai_addrlen;//socket地址ai_addr的长度

   Char* ai_cannonname;//主机的别名

   Struct sockaddr* ai_addr;//指向socket地址

   Struct addrinfo* ai_next;//指向下一个sockinfo结构的对象

};

Ai_flags成员可以取表中的标志按位或。

AI_PASSIVEAI_NUMERICHOSTAI_CANONNAMEAI_NUMERICSERV

AI_V4MAPPEDAI_ALLAI_ADDRCONFIG

使用hinst时可以设置其ai_flags,ai_family,ai_socktypeai_protocal四个字段,其他设置为NULL

Struct addrinfo hinst;

Struct addrinfo* res;

Bzero(&hisnt,sizeof(hinst));

Hinst.ai_socktype = SOCK_STREAM;

Getaddrinfo(“ernest-laptop”,”daytime”,&hinst,&res);

Getaddrinfo将隐式分配堆内存,在调用结束 后使用

#include<netdb.h>

Void freeaddrinfo(struct addrinfo* res);来释放内存。


Getnameinfo函数通过socket地址同时获得以字符串表示的主机名和服务名。

#include<netdb.h>

Int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host,

Socklen_t hostlen, char* serv, socklen_t servlen, int flags);

Flags参数:

NI_NAMEREQDNI_DGRAMNI_NUMERICHOST,NI_NUMERICSERV,

NI_NOFQDN

 

Getaddrinfogetnameinfo函数调用返回的错误码

EAI_AGAINBAI_BADFLAGSEAI_FAILEAI_FAMILYEAI_MEMORY

EAI_NONAMEEAI_OVERFLOWEAI_SERVICEEAI_SOCKTYPEEAI_SYSTEM

Linuxstrerror函数能够将数值错误码errno转换成易读的字符串形式。

#include<netdb.h>

Const char*gai_strerror(int error);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值