Linux高性能服务器编程——Linux网络基础API及应用
Linux网络编程基础API
详细介绍了socket地址意义极其API,在介绍数据读写API部分引入一个有关带外数据发送和接收的程序,最后还介绍了其他一些辅助API。
socket地址API
主机字节序和网络字节序
字节序分为大端字节序和小端字节序。小端字节序又被称为主机字节序,大端字节序被称为网络字节序。大端字节序是指一个整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。小端字节序则相反。
Linux提供如下四个函数完成主机字节序与网络字节序之间的转换:
#include<arpa/inet.h>
uint32_thtonl(uint32_t hostlong);
uint16_thtons(uint16_t hostshort);
uint32_tntohl(uint32_t netlong);
uint16_tntohs(uint16_t netshort);
它们的含义明确,比如htonl将长整型(32bit)的主机字节序转化为网络字节序。这四个函数中,长整型用来转化IP地址,短整型用来转换端口号。
通用socket地址
#include <bits/socket.h>
Struct sockaddr
{
Sa_family_tsa_family;
charsa_data[14];
};
sa_family是地址族类型(sa_family_t)的变量。地址族通常与协议族类型对应,常见的地址族和对应的协议族如下表所示:
协议族 |
地址族 |
描述 |
PF_UNIX |
AF_UNIX |
UNIX本地域协议族 |
PF_INET |
AF_INET |
TCP/IPv4协议族 |
PF_INET6 |
AF_INET6 |
TCP/IPv6协议族 |
专用socket地址
UNIX本地域协议族专用socket地址结构体:
#include <sys/un.h>
struct sockaddr_un
{
sa_family+tsin_family; //地址族:AF_UNIX
charsun_path[108] //文件路径名
};
TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,他们分别用于IPv4和IPv6:
struct sockaddr_in
{
sa_family_tsin_family; //地址族:AF_INET
u_int16_tsin_port; //端口号,要用网络字节序表示
structin_addr sin_addr; //IPv4地址结构体
};
Struct in_addr
{
u_int32_ts_addr; //IPv4地址,要用网络字节序表示
};
Struct sockaddr_in6
{
sa_family_tsin6_family; //地址族:AF_INET6
u_int16_tsin6_port; //端口号,要用网路字节序表示
u_int32_tsin6_flowinfo; //流信息,应设置为0
structin6_addr; //IPv6地址结构体
u_int32_tsin6_scope_id; //scope ID,尚处实验阶段
};
Struct in6_addr
{
Unsignedchar sa_addr[16]; //IPv6地址,要用网络字节序表示
};
所有socket地址类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数的类型都是sockarrd。
IP地址转换函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, structin_addr *inp); //将点分十进制转化为网络字节序
in_addr_t inet_addr(const char *cp); //同上,但将结果放入cp所指地址
char *inet_ntoa(struct in_addr in); //将网络字节序转化为点分十进制
上面三个函数用于点分十进制字符串表示的IPv4地址和用网络字节序整数(二进制数)表示的IPv4地址之间的转换。
其中inet_aton内部用一个静态变量存储转化结果,所以函数不可重入,示例
#include <stdio.h> #include <sys/un.h> #include <arpa/inet.h> int main() { struct in_addr in1, in2; in1.s_addr = inet_addr("1.2.3.4"); in2.s_addr = inet_addr("10.194.71.60"); char* szValue1 = inet_ntoa(in1); char* szValue2 = inet_ntoa(in2); printf("address1:%s\n",szValue1); printf("address2:%s\n",szValue2); return 0; }
执行结果:
address1:10.194.71.60
address2:10.194.71.60
下面这对函数也能完成上面3个函数的功能
#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 size);
socket相关函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, intprotocol); //创建socket,指定协议族和服务类型
int bind(int sockfd, const struct sockaddr*addr, socklen_t addrlen); //绑定地址
int listen(int sockfd, int backlog); //服务器创建监听队列以存放处理的客户连接
int accept(int sockfd, struct sockaddr*addr, socklen_t *addrlen);/从监听队列接收一个连接
int connect(int sockfd, const structsockaddr *addr, socklen_t addrlen);//客户与服务器建立连接
#include <unistd.h>
int close(int fd); //关闭连接
#include <sys/socket.h>
int shutdown(int sockfd, int how); //关闭连接
数据读写
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_tlen, int flags);
ssize_t recvfrom(int sockfd, void *buf,size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr*msg, int flags);
ssize_t send(int sockfd, const void *buf,size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf,size_t len, int flags,
const struct sockaddr*dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const structmsghdr *msg, int flags);
示例:
带外数据的发送和接收:关于带外数据见http://blog.csdn.net/walkerkalr/article/details/35258523
发送带外数据:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] ); struct sockaddr_in server_address; bzero( &server_address, sizeof( server_address ) ); server_address.sin_family = AF_INET; inet_pton( AF_INET, ip, &server_address.sin_addr ); server_address.sin_port = htons( port ); int sockfd = socket( PF_INET, SOCK_STREAM, 0 ); assert( sockfd >= 0 ); if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 ) { printf( "connection failed\n" ); } else { printf( "send oob data out\n" ); const char* oob_data = "abc"; const char* normal_data = "123"; send( sockfd, normal_data, strlen( normal_data ), 0 ); send( sockfd, oob_data, strlen( oob_data ), MSG_OOB ); send( sockfd, normal_data, strlen( normal_data ), 0 ); } close( sockfd );
接收带外数据
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #define BUF_SIZE 1024 int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] ); 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 sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 ); int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 ); ret = listen( sock, 5 ); assert( ret != -1 ); struct sockaddr_in client; socklen_t client_addrlength = sizeof( client ); int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength ); if ( connfd < 0 ) { printf( "errno is: %d\n", errno ); } else { char buffer[ BUF_SIZE ]; memset( buffer, '\0', BUF_SIZE ); ret = recv( connfd, buffer, BUF_SIZE-1, 0 ); printf( "got %d bytes of normal data '%s'\n", ret, buffer ); memset( buffer, '\0', BUF_SIZE ); ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB ); printf( "got %d bytes of oob data '%s'\n", ret, buffer ); memset( buffer, '\0', BUF_SIZE ); ret = recv( connfd, buffer, BUF_SIZE-1, 0 ); printf( "got %d bytes of normal data '%s'\n", ret, buffer ); close( connfd ); } close( sock ); return 0; }
同时在chen123上启动发送带外数据程序,在li123上启动接收带外数据程序,同时用tcpdump抓取这一过程中客户端和服务器交换的TCP报文。
具体操作如下:
li123@ubuntu:/$ sudo tcpdump -ntx -i eth0 port 54321
li123@ubuntu:/$./a.out 192.168.73.130 54321
chen123@ubuntu:/$ ./a.out 192.168.73.130 54321
服务器输出如下:
got 5 bytes of normal data ‘123ab‘
got 1 bytes of oob data ‘c‘
got 3 bytes of normal data ‘123‘
客户端发给服务器的3字节的带外数据”abc”中,仅有最后一个字符’c’被服务器当成真正的带外数据。并且,服务器对正常数据的接收将被带外数据截断,即前一部分正常数据”123ab”和后续正常数据”123”是不能被一个recv调用全部读出的。
Tcpdump的输出内容中,和带外数据相关的输出如下:
IP 192.168.73.129.40643 > 192.168.73.130.54321:Flags [P.U], seq 4:7, ack 1, win 229, urg 3, options [nop,nop,TS val 4574025ecr 4573780], length 3
可以看到tcpdump输出标志U,这表示TCP报文段的头部被设置了紧急标志。urg 3是紧急偏移,塔指针带外数据在字节流中的位置的下一字节位置是7(3+4,其中4是报文段的序号值相对于初试序号值得偏移)。
其他基础API
#include <sys/socket.h>
int sockatmark(int sockfd); //判断是否处于带外标记
int getsockname(int sockfd, struct sockaddr*addr, socklen_t *addrlen); //获取本端sockfd对应的本端socket地址
int getpeername(int sockfd, struct sockaddr*addr, socklen_t *addrlen); //获取sockfd对应的远端socket地址
下面两个函数用来读取和设置socket文件描述符属性的方法
int getsockopt(int sockfd, int level, intoptname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, intoptname, const void *optval, socklen_t optlen);
下面两个函数用来根据主机名称获取主机的完整信息和根据IP地址获取主机的完整信息
#include <netdb.h>
struct hostent *gethostbyname(const char*name);
#include <sys/socket.h> /* for AF_INET */
struct hostent *gethostbyaddr(const void*addr, socklen_t len, int type);
下面两个函数根据名称获取某个服务的完整信息和根据端口获取某个服务的完整信息
#include <netdb.h>
struct servent *getservbyname(const char*name, const char *proto);
struct servent *getservbyport(int port,const char *proto);
下面这个函数能通过主机名获得IP地址,也能通过服务器获得端口号。
#include <netdb.h>
int getaddrinfo(const char *node, constchar *service, const struct addrinfo *hints,
struct addrinfo **res);
下面这个函数能通过socket地址同时获得以字符串标书的主机名和服务名。
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa,socklen_t salen, char *host, size_t hostlen,
char *serv, size_tservlen, int flags);