网络编程基础(2):Linux网络编程基础API 包含socket地址api,创建服务器&客户端必要的api ,数据读写api,socket设置,信息获取api等 注意:这一章节非常重要 ,涉及很多基础操作,要多看!!!
主要内容
socket地址相关的API。IP地址+端口号
socket基础API。主要包括:创建socket,命名socket,监听,接收连接,设置socket选项等
网络信息API。用于实现主机名和IP地址之间的转换,以及服务名称和端口号之间的转换。
socket地址API 字节序问题
大端字节序:一个整数的高位字节(23-31bit)存储在内存的低地址处,低位字节(0-7bit)存储在内存的高地址处。
小端字节序:整数的高位字节存储在内存高地址处,低位字节存储在内存低地址处
现代PC大多采用小端序,所以也称小端序为主机字节序。网络中默认大端序,所以也称大端序为网络字节序。
Linux提供了四个函数来完成主机序和网络序之间的转换。
1 2 3 4 5 6 7 8 #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 netlong);
通用socket地址
原始版
1 2 3 4 5 6 7 #include <bits/socket.h> struct sockaddr{ sa_family_t sa_family; char sa_data[14 ]; }
改良后通用socket地址
1 2 3 4 5 6 7 #include <bits/socket.h> struct sockaddr_storage{ sa_family_t sa_family; unsigned long int __ss_align; char __ss_padding[128 -sizeof (__ss_align)]; }
专用的socket地址
上面的是考虑通用性,但是在实际程序编写中,常常根据具体选择的协议族来选用相应的专用socket地址。
协议族&地址族的类型
1 2 3 4 5 协议族 地址族 描述 || || || PF_UNIX AF_UNIX UNIX本地域协议族 PF_INET AF_INET TCP/IPv4协议族 PF_INET6 AF_INET6 TCP/IPv6协议族
因为二者具有完全相同的值,所以PF_*与AF_*二者通常混用。
IP地址转换函数
通常人们喜欢用字符串形式下点分十进制来描述IP地址。但编程中需要先将它们转换成整数(二进制)才能使用。为此设计有如下的函数来解决相关问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #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 );
所谓不可重入性,是因为该函数指向一个静态变量,意味着后面的将覆盖前面的。
代码示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 gao@gao-VirtualBox:~/桌面/net_code$ cat in_ntoa.c int main (){ struct in_addr addr1; struct in_addr addr2; inet_aton("1.2.3.4" , &addr1); inet_aton("10.194.71.60" , &addr2); char* szValue1 = inet_ntoa(addr1); char* szValue2 = inet_ntoa(addr2); printf ("address1:%s\n" , szValue1); printf ("address2:%s\n" , szValue2); return 0; } gao@gao-VirtualBox:~/桌面/net_code$ gcc in_ntoa.c -o test gao@gao-VirtualBox:~/桌面/net_code$ ./test address1:10.194.71.60 address2:10.194.71.60
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 gao@gao-VirtualBox:~/桌面/net_code$ cat in_ntoa.c int main (){ struct in_addr addr1; struct in_addr addr2; inet_aton("1.2.3.4" , &addr1); char* szValue1 = inet_ntoa(addr1); printf ("address1:%s\n" , szValue1); inet_aton("10.194.71.60" , &addr2); char* szValue2 = inet_ntoa(addr2); printf ("address2:%s\n" , szValue2); return 0; } gao@gao-VirtualBox:~/桌面/net_code$ gcc in_ntoa.c -o test gao@gao-VirtualBox:~/桌面/net_code$ ./test address1:1.2.3.4 address2:10.194.71.60
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <netinet/in.h> #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) ;#define INET_ADDRSTRLEN 16 #define INET6_ADDRSTRLEN 46
socket基础API 创建socket 函数体
1 2 3 4 5 6 7 8 9 10 #include <sys/types.h> #include <sys/socket.h> int socket (int domain, int type, int protocol) ;
命名socket 函数体
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <sys/types.h> #include <sys/socket.h> int bind (int sockfd, const struct sockaddr* my_addr, socklen_t addrlen) ;bind(hServSock,(SOCKADDR*) &servAdddr, sizeof (servAdddr))
监听socket 函数体
1 2 3 4 5 6 7 8 #include <sys/socket.h> int listen (int sockfd, int backlog) ;
设置backlog之后,最大连接数一般是backlog+1.示例代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <stdbool.h> static bool stop = false ;static void handle_term (int sig) { stop = true ; } int main (int argc, char * argv[]) { signal(SIGTERM, handle_term); if ( argc <= 3 ) { printf ("usage: %s ip_address port_number backlog\n" , basename(argv[0 ])); return 1 ; } const char * ip = argv[1 ]; int port = atoi(argv[2 ]); int backlog = atoi(argv[3 ]); int sock = socket(PF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); 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)); assert(ret != -1 ); ret = listen(sock, backlog); assert(ret != -1 ); while ( !stop ) { sleep(1 ); } close(sock); return 0 ; }
结果
1 2 3 4 5 6 7 8 gao@gao-VirtualBox:~/桌面/net_code$ ./a 127.0.0.1 12345 5 gao@gao-VirtualBox:~/桌面/net_code$ telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]' .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 gao@gao-VirtualBox:~/桌面/net_code$ netstat -nt | grep 12345 tcp 0 0 127.0.0.1:48852 127.0.0.1:12345 ESTABLISHED tcp 0 0 127.0.0.1:48882 127.0.0.1:12345 ESTABLISHED tcp 0 0 127.0.0.1:12345 127.0.0.1:48852 ESTABLISHED tcp 0 0 127.0.0.1:12345 127.0.0.1:48884 ESTABLISHED tcp 0 0 127.0.0.1:12345 127.0.0.1:48882 ESTABLISHED tcp 0 1 127.0.0.1:48888 127.0.0.1:12345 SYN_SENT tcp 0 0 127.0.0.1:48878 127.0.0.1:12345 ESTABLISHED tcp 0 0 127.0.0.1:48874 127.0.0.1:12345 ESTABLISHED tcp 0 0 127.0.0.1:12345 127.0.0.1:48878 ESTABLISHED tcp 0 1 127.0.0.1:48886 127.0.0.1:12345 SYN_SENT tcp 0 0 127.0.0.1:48884 127.0.0.1:12345 ESTABLISHED tcp 0 0 127.0.0.1:12345 127.0.0.1:48874 ESTABLISHED tcp 0 0 127.0.0.1:12345 127.0.0.1:48876 ESTABLISHED tcp 0 0 127.0.0.1:48876 127.0.0.1:12345 ESTABLISHED
接受连接 函数体
1 2 3 4 5 6 7 8 9 10 11 #include <sys/types.h> #include <sys/socket.h> int accept (int sockfd, struct sockaddr* addr. socklen_t *addrlen) ;
测试功能:accept如何处理异常连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #include <errno.h> static bool stop = false ;static void handle_term (int sig) { stop = true ; } int main (int argc, char * argv[]) { signal(SIGTERM, handle_term); if ( argc <= 3 ) { printf ("usage: %s ip_address port_number backlog\n" , basename(argv[0 ])); return 1 ; } const char * ip = argv[1 ]; int port = atoi(argv[2 ]); int backlog = atoi(argv[3 ]); int sock = socket(PF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); 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)); assert(ret != -1 ); ret = listen(sock, backlog); assert(ret != -1 ); sleep(30 ); 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 remote[INET_ADDRSTRLEN]; printf ("connected with ip: %s and port: %d\n" , inet_ntop(AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client.sin_port)); close(connfd); } close(sock); return 0 ; }
执行这个服务器程序,同时启动telnet连接到服务器并很快的断开。即在listen和accept之间,强行终止telnet客户端。观察accept的处理
1 2 3 4 gao@gao-VirtualBox:~/桌面/net_code$ ./accept_pro 127.0.0.1 54321 5 connected with ip: 127.0.0.1 and port: 50722 gao@gao-VirtualBox:~/桌面/net_code$ netstat -nt | grep 54321 tcp 0 0 127.0.0.1:50722 127.0.0.1:54321 TIME_WAIT
accept并不会检测是否异常,它只是从监听队列中取出连接。
发起连接 服务器端通过listen调用来被动接受连接,客户端通过connect来主动建立连接
函数体
1 2 3 4 5 6 7 8 9 10 11 #include <sys/types.h> #include <sys/socket.h> int connect (int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen) ;
关闭连接 函数体
1 2 3 4 5 6 #include <unistd.h> int close (int fd) ;
1 2 3 4 5 6 7 8 #include <sys/socket.h> int shutdown (int sockfd, int howto) ;
数据读写API 针对文件的I/O函数,read,write也都是可以的。但是socket编程提供了几个专门用于socket数据读写的函数,可以加强对数据读取的控制
TCP数据读写 函数体
1 2 3 4 5 6 7 8 9 10 11 12 #include <sys/types.h> #include <sys/socket.h> ssize_t recv (int sockfd, void *buf, size_t len, int flags) ;ssize_t send (int sockfd, const void *buf, size_t len, int flags) ;
UDP数据读写 函数体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom (int sockfd, void * buf, size_t len, int flags, struct aockaddr* src_addr, socklen_t * addrlen) ;ssize_t sendto (int sockfd, void * buf, size_t len, int flags, struct aockaddr* dest_addr, socklen_t * addrlen) ;
地址信息函数 函数体
1 2 3 4 5 6 7 8 #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) ;
socket选项 函数体
1 2 3 4 5 6 7 8 9 10 11 #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) ;
两个相对重要的点:SO_REUSEADDR(重用本地地址) TCP_NODELAY(禁止Nagle算法)
SO_REUSEADDR
某些场合下需要尽快的解决TIME_WAIT的影响,快速启动。可更改设置
1 2 3 4 5 6 7 8 9 #define TRUE 1 #define false 0 int option; optlen = sizeof (option); option = TRUE; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)& option, optlen);
TCP套接字默认使用Nagel算法交换数据
采用该算法的好处为:减少网络负载和混乱程度,最大程度地利用缓冲
其弊端为:会牺牲一定的传输速度。
使用场景:当传输大文件数据时,不开启Nagel算法,依然可以最大程度利用缓存,且传输速度高。
默认状态下TCP_NODELAY选项为0,修改为1后,便禁用了Nagel算法。
1 2 3 4 int opt_val = 1 ; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, sizeof (opt_val));
剩余的若干更改可参考情况具体应用
网络信息API 四个重点函数
1 2 3 4 5 6 7 8 9 10 11 #include <netdb.h> struct hostent* gethostbyname (const char * name) ;struct hostent* gethostbyaddr (const void * addr, size_t len, int type) ;
结构体hostent的定义
1 2 3 4 5 6 7 8 9 #include <netdb.h> struct hostent { char * h_name; char ** h_aliases; int h_addrtype; int h_length; char ** h_addr_list; };
1 2 3 4 5 6 7 8 9 10 #include <netdb.h> struct servent* getservbyname (const char * name, const char * proto) ;struct servent* getservbyport (int port, const char * proto) ;
结构体servent的定义
1 2 3 4 5 6 7 8 #include <netdb.h> struct servent { char * s_name; char ** s_aliases; int s_port; char * s_proto; };
参考内容 游双老师《Linux高性能服务器编程》 尹圣雨老师《TCP/IP网络编程》 另:完全零基础建议先看尹圣雨老师的书,更为通俗易懂,我也是先用这本书入的门。