TCP/IP协议族 TCP/IP协议族体系结构以及主要协议 四层协议模型:
Linux网络编程基础API socket地址API 主机字节序和网络字节序
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 #include <stdio.h> void byteorder () { union { short value; char union_bytes[sizeof (short )]; } test; test.value = 0x0102 ; if ((test.union_bytes[0 ] == 1 ) && (test.union_bytes[1 ] == 2 )) { printf ("big endian\n" ); } else if ((test.union_bytes[0 ] == 2 ) && (test.union_bytes[1 ] == 1 )) { printf ("little endian\n" ); } else { printf ("unknown....\n" ); } } int main (int argc, const char **argv) { byteorder(); return 0 ; }
通用socket地址 socket网络编程接口中表示socket地址的是结构体sockaddr,定义如下:
1 2 3 4 5 6 7 #include <bits/socket.h> struct sockaddr { sa_family_t sa_family; char sa_data[14 ]; }
sa_family成员是地址族类型变量。
协议族
地址族
描述
PF_UNIX
AF_UNIX
UNIX本地域协议族
PF_INET
AF_INET
TCP/IPv4协议族
PF_INET6
AF_INET6
TCP/IPv6协议族
宏PF_ 和 AF_都定义在bis/socket.h文件中,两者值完全相同,所以常常混用
sa_data成员用于存放socket地址值。但是不同的协议族地址值有不同含义和长度
协议族
地址值含义和长度
PF_UNIX
文件的路径名,长度可达108字节
PF_INET
16bit端口号和32bitIPv4地址,共6字节
PF_INET6
16bit端口号、32bit流标识、128bitIPv6地址,32bit范围ID,共26字节
下边也是通用的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地址不好用,用Linux提供的专用socket地址结构体
sockaddr_un 为 本地套接字
sockaddr_in 为 ipv4
sockaddr_in6 为 ipv6
重点为ipv4:
1 2 3 4 5 6 7 8 9 10 struct sockaddr_in { sa_family_t sa_family; u_int16_t sin_port; struct in_addr sin_addr ; } struct in_addr { u_int32_t s_addr; }
所有专用socket地址类型变量在使用的时候都必须强转为sockaddr类型
IP地址转换函数 编程中需要把点分十进制(192.168.1.117)转换成整数(二进制数)
ipv4使用以下函数转换:
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 #include <arpa/inet.h> in_addr_t inet_addr (const char *strptr) ;int inet_aton (const * cp,struct in_addr* inp) ;char *inet_ntoa (struct in_addr in) ;int inet_pton (int af,const char *src,void *dst) ;const char *inet_ntop (int af,const void *src,char *dst,socklen_t cnt) ;
创建socket(socket函数) 1 2 3 4 5 6 7 8 9 #include <sys/types.h> #include <sys/socket.h> int socket (int domain, int type, int protocol) ;
命名socket(bind函数) 1 2 3 4 5 6 7 8 9 #include <sys/types.h> #include <sys/socket.h> int bind (int sockfd, const struct sockaddr* my_addr, socklen_t addrlen) ;
监听socket(listen函数) 1 2 3 4 5 #include <sys/socket.h> int listen (int sockfd, int backlog) ;
一下代码是backlog参数对listen的影响
处于ESTABLISHED(数据传输)状态的为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 <arpa/inet.h> #include <stdio.h> #include <string.h> #include <signal.h> #include <netinet/in.h> #include <stdlib.h> #include <assert.h> #include <unistd.h> static bool stop = false ;static void handler_term (int sig) { stop = true ; } int main (int argc, char *argv[]) { signal(SIGTERM, handler_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 = PF_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 ; }
接受连接(accpet函数) 1 2 3 4 5 6 7 8 9 10 11 12 #include <sys/types.h> #include <sys/socket.h> int accept (int sockfd, struct sockaddr* addr,socklen_t * addrlen) ;
如果监听队列中处于ESTABLISTEN状态的客户端断开连接,accept()函数依然可以成功返回。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 #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <signal.h> #include <netinet/in.h> #include <stdlib.h> #include <assert.h> #include <unistd.h> #include <errno.h> int main (int argc, const char **argv) { int server_sock; struct sockaddr_in server_addr ; server_sock = socket(PF_INET, SOCK_STREAM, 0 ); assert(server_sock >= 0 ); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080 ); server_addr.sin_addr.s_addr = INADDR_ANY; int ret = bind(server_sock, (struct sockaddr *)&server_addr, sizeof (server_addr)); assert(ret != -1 ); ret = listen(server_sock, 5 ); assert(ret != -1 ); sleep(20 ); struct sockaddr_in client_addr ; socklen_t client_addrlen = sizeof (client_addr); int connfd = accept(server_sock, (struct sockaddr *)&client_addr, &client_addrlen); if (connfd < 0 ) { printf ("errno is %d\n" , errno); } else { char remote[INET_ADDRSTRLEN]; printf ("connect with ip : %s and port is : %d\n" , inet_ntop(AF_INET, &client_addr.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client_addr.sin_port)); close(connfd); } close(server_sock); return 0 ; }
发起连接 客户端主动与服务端建立连接
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) ;
常见errno
ECONNREFUSED: 目标端口不存在,连接被拒绝
ETIMEDOUT: 连接超时
关闭连接 不是立即关闭一个连接,而是将fd的引用数-1,fd的引用为0时才是真正关闭
1 2 #include <unistd.h> int close (int fd) ;
立即关闭连接
1 2 #include <sys/socket.h> int shutdown (int sockfd, int howto) ;
数据读写 TCP数据读写 对比read、write函数增加了对数据读写的控制。
1 2 3 4 #include <sys/types.h> #include <sys/socket.h> ssize_t recv (int sockfd,void *buf,size_t len,int flag) ;ssize_t send (int sockfd,const void *buf)
flag参数为数据读写提供了额外的控制
选项名
含义
send
recv
MSG_CONFIRM
指示数据链路层协议持续监听对方的回应,直到得到答复,它仅能用于SOCK_DGRAM和SOCK_RAW类型的socket
Y
N
MSG_DONTROUTE
不查看路由表,直接将数据发送给本地局域网内的主机。这表示发送者确切的知道目标主机就在本地网络上
Y
N
MSG_DONTWAIT
对socket的此次操作将是非阻塞的
Y
Y
MSG_MORE
告诉内核应用程序还有更多数据要发送,内核将超时等待新数据写入TCP发送缓冲区后一并发送。这样可以防止TCP发送过多小的报文段,从而提高传输效率
Y
N
MSG_WAITALL
读操作仅在读取到指定数量的字节后才返回
N
Y
MSG_PEEK
窥探读缓存中的数据,此次读操作不会导致这些数据被清除
N
Y
MSG_OOB
发送或接收紧急数据
Y
Y
MSG_NOSIGNAL
往读端关闭的管道或者socket连接中写数据时不引发SIGPIPE信号
Y
N
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 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <unistd.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; server_address.sin_port = htons(port); inet_pton(AF_INET, ip, &server_address.sin_addr); int sockfd = socket(PF_INET, SOCK_STREAM, 0 ); assert(sockfd >= 0 ); if (connect(sockfd, (struct sockaddr *)&server_address, sizeof (server_address)) < 0 ) { printf ("connect failed\n" ); } else { 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); return 0 ; }
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 <assert.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <errno.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 ; }
UDP数据读写 1 2 3 4 5 6 #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也可用于面向连接的socket,只需要把最后两个参数设置为NULL以忽略发送端/接收端的socket地址
通用数据读写 以下两个函数不仅能用于TCP流数据,也能用于UDP数据报
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <sys/socket.h> ssize_t recvmsg (int sockfd, struct msghdr* msg, int flags) ;ssize_t sendmsg (int sockfd, struct msghdr* msg, int flags) ;struct msghdr { void *msg_name; socklen_t msg_namelen; struct iovec *msg_iov ; int msg_iovlen; void *msg_control; socklen_t msg_controllen; int msg_flags; }
msg_name参数对于TCP来说不用设置。
带外标记 实际应用中无法预期带外数据何时到来。
1 2 3 4 #include <sys/socket.h> int sockatmark (int sockfd) ;
地址信息函数 想知道连接socket的本端socket地址,以及远端socket地址,用以下函数
1 2 3 4 5 #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选项 以下函数为socket文件描述符属性读取和设置专用
1 2 3 4 5 6 7 8 9 #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 * restrict option_len) ;
部分socket选项设置应该在listen/connect函数之前完成
SO_REUSEADDR选项 主动关闭连接端会经历TIME_WAIT状态,这个时候可以用SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接占用的socket地址。
1 2 3 4 5 6 7 8 9 10 11 int sock = socket(PF_INET, SOCK_STREAM, 0 );assert(sock >= 0 ); int reuse = 1 ;setsockopt(sock, SOL_SOCKET, SO_REUSERADDR,&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_in*)&address,sizeof (address));
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 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> int main (int argc, char **argv) { if (argc < 2 ) { printf ("Usage: %s ip_address, port_number\n" , basename(argv[0 ])); return 1 ; } char *ip = argv[1 ]; int port = atoi(argv[2 ]); int sockfd = socket(AF_INET, SOCK_STREAM, 0 ); assert(sockfd >= 0 ); int reuse = 1 ; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)); struct sockaddr_in server_address ; bzero(&server_address, sizeof (server_address)); server_address.sin_family = AF_INET; server_address.sin_port = htons(port); inet_pton(AF_INET, ip, &server_address.sin_addr); int ret; ret = bind(sockfd, (struct sockaddr *)&server_address, sizeof (server_address)); assert(ret != -1 ); ret = listen(sockfd, 5 ); assert(ret != -1 ); struct sockaddr_in client_address ; socklen_t client_address_len = sizeof (client_address); int connfd = accept(sockfd, (struct sockaddr *)&client_address, &client_address_len); 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_address.sin_addr, remote, INET_ADDRSTRLEN), htons(client_address.sin_port)); close(connfd); } close(sockfd); return 0 ; }
SO_RCVBUF和SO_SNDBUF选项 设置TCP接收、发送缓冲区大小。使用setsockopt设置缓冲区大小时,系统都会将其值翻倍,并且不得小于某个值。
TCP接收缓冲区最小值为256字节
TCP发送缓冲区最小值为2048字节
缓冲区最小值取决于操作系统。
为了保证一个TCP连接拥有足够的空闲缓冲区来处理拥塞
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 67 68 #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #define BUFFER_SIZE 1024 int main (int argc, char **argv) { if (argc <= 2 ) { printf ("Usage: %s ip port buffer_size\n" , basename(argv[0 ])); return 1 ; } const char *ip = argv[1 ]; int port = atoi(argv[2 ]); struct sockaddr_in addr ; bzero(&addr, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET, ip, &addr.sin_addr); int sock = socket(AF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); int recvbuf = atoi(argv[3 ]); int len = sizeof (recvbuf); setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof (recvbuf)); getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, (socklen_t *)&len); printf ("the tcp receive buffer size after setting is %d\n" , recvbuf); int ret = bind(sock, (struct sockaddr *)&addr, sizeof (addr)); assert(ret != -1 ); ret = listen(sock, 5 ); assert(ret != -1 ); struct sockaddr_in client_addr ; socklen_t client_addr_len = sizeof (client_addr); int connfd = accept(sock, (struct sockaddr *)&client_addr, &client_addr_len); if (connfd < 0 ) printf ("errno is %d\n" , errno); else { char buffer[BUFFER_SIZE]; memset (buffer, '\0' , BUFFER_SIZE); while (recv(connfd, buffer, BUFFER_SIZE - 1 , 0 ) > 0 ) { } close(connfd); } close(sock); return 0 ; }
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 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #define BUFFER_SIZE 512 int main (int argc, char **argv) { if (argc <= 2 ) { printf ("Usage:%s ip_address port_number buffer_size\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; server_address.sin_port = htons(port); inet_pton(AF_INET, ip, &server_address.sin_addr); int sock = socket(AF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); int sendbuf = atoi(argv[3 ]); int len = sizeof (sendbuf); setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof (sendbuf)); getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, (socklen_t *)&len); printf ("the tcp send buffer size after setting is %d\n" , sendbuf); if (connect(sock, (struct sockaddr *)&server_address, sizeof (server_address)) != -1 ) { char buffer[BUFFER_SIZE]; memset (buffer, 'a' , BUFFER_SIZE); send(sock, buffer, BUFFER_SIZE, 0 ); } close(sock); return 0 ; }
SO_RCVLOWAT和SO_SNDLOWAT选项 表示TCP接收缓冲区和发送缓冲区的低水位标记,一般被I/O复用系统调用用来判断socket是否可读可写。当TCP接收缓冲区中可读数据的总数大于低水位标记,I/O复用系统调用将通知应用程序可以冲对应的socket上读取数据;当TCP发送缓冲区中的空间大于低水位标记时,I/O复用系统调用将通知应用程序可以往对应的socket上写入数据。
默认情况,低水位标记为1字节。
SO_LINGER选项 用于控制close系统调用在关闭TCP连接时的行为。默认情况下,当我们使用close系统调用来关闭一个socket是,close将立即返回,TCP模块扶着将该socket对用的TCP发送缓冲区残留的数据发送给对方。
设置SO_LINGER选项的值时,需要给setsockopt传递一个linger类型的结构体。
1 2 3 4 5 6 #include <sys/socket.h> struct linger { int l_onoff; int l_linger; }
根据linger结构体中两个成员变量的不同值,close可能会产生3种行为:
l_onoff 等于0。此时SO_LINGER不起作用,close用默认行为关闭socket
l_onoff不为0,l_linger等于0。此时close立即返回,TCP将丢弃被关闭socket对应的TCP发送缓冲区中残留的数据,同时给对方发送一个复位报文段。异常终止连接
l_onoff不为0,l_linger大于0。close行为取决于两个条件:
被关闭socket是否有残留数据
该socket是阻塞还是非阻塞的
对于阻塞:close将等待l_linger时间,直到TCP模块发送完所有残留数据并得到对方确认
对于非阻塞:close立即返回
网络信息API socket的IP地址和端口号不便于记忆与扩展,所以使用主机名和服务名称来代替IP地址和端口号
gethostbyname、gethostbyaddr 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <netdb.h> struct hostent *gethostbyname (const char *name) ;struct hostent *gethostbyaddr (const void *addr, size_t len, int type) ;#include <netdb.h> struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; }
getservbyname、getservbyport getservbyname函数根据名称获取某个服务的完整信息,getservbyport根据端口号获取服务的完整信息。都是通过读取/exc/services文件夹 来获取服务信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <netdb.h> struct servent *getservbyname (const char *name, const char *proto) ;struct servent *getservbyport (int port, const char *proto) ;#include <netdb.h> struct servent { char *s_name; char **s_aliases; char *s_proto; };
访问daytime服务
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 #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <assert.h> int main ( int argc, char *argv[] ) { assert( argc == 2 ); char *host = argv[1 ]; struct hostent * hostinfo = gethostbyname( host ); assert( hostinfo ); struct servent * servinfo = getservbyname( "daytime" , "tcp" ); assert( servinfo ); printf ( "daytime port is %d\n" , ntohs( servinfo->s_port ) ); struct sockaddr_in address ; address.sin_family = AF_INET; address.sin_port = servinfo->s_port; address.sin_addr = *( struct in_addr* )*hostinfo->h_addr_list; int sockfd = socket( AF_INET, SOCK_STREAM, 0 ); int result = connect( sockfd, (struct sockaddr* )&address, sizeof ( address ) ); assert( result != -1 ); char buffer[128 ]; result = read( sockfd, buffer, sizeof ( buffer ) ); assert( result > 0 ); buffer[ result ] = '\0' ; printf ( "the day tiem is: %s" , buffer ); close( sockfd ); return 0 ; }
上述四个函数属于不可重入函数(非线程安全),可重入版本在函数名称后边加上_r即可
1 2 3 4 struct hostent *gethostbyname_r (const char *name) ;struct hostent *gethostbyaddr_r (const void *addr, size_t len, int type) ;struct servent *getservbyname_r (const char *name, const char *proto) ;struct servent *getservbyport_r (int port, const char *proto) ;
getaddrinfo 该函数既能通过主机名获取IP地址,也能通过服务名获得端口号。它是否可重入取决于内部调用gethostbyname、getservbyname函数是否是可重入版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <netdb.h> int getaddrinfo (const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result) ;struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr *ai_addr ; char *ai_canonname; struct addrinfo *ai_next ; };
ai_flags值可以按下表的标志的按位或
我们使用hints参数的时候,可以设置其ai_flags、ai_family、ai_socktype和ai_protocol四个字段,其他字段必须设置为NULL。
1 2 3 4 5 6 7 8 9 10 struct addrinfo hints ;struct addrinfo *res ;bzero(&hints,sizeof (hints)); hints.ai_socktype = SOCK_STREAM; getaddrinfo("ernest-laptop" ,"daytime" ,&hints,&res); #include <netdb.h> void freeaddrinfo (struct addrinfo* res) ;
getnameinfo 该函数能通过socket地址通知获得以字符串表示的主机名(内部使用的gethostbyaddr函数)和服务名(内部使用getservbyport函数)。是否可重入同上
1 2 3 #include <netdb.h> int getnameinfo (const struct sockaddr *sockaddr,socklen_t addrlen, char *host, socklen_t hostlen, char *serv,socklen_t servlen, int flags) ;
genameinfo将返回的主机名存储在host参数指向的缓存中,将服务名存储在serv参数指向的缓存中,hostlen和servlen参数分别指定这两块缓存的长度。
flag参数控制getnameinfo的行为。flag取值如下:
getaddrinfo和getnameinfo函数成功返回0,失败返回错误码:
Linux下strerror函数能将数值错误码errno转换成易读的字符串形式。下面函数可将上表的错误码转换成其字符串形式
1 2 #include <netdb.h> const char *gai_strerror (int error) ;
高级I/O函数
pipe函数 pipe函数可用于创建一个管道,以实现进程间通信。
1 2 3 #include <unistd.h> int pipe (int fd[2 ]) ;
fd[0]和fd[1]分别构成管道两端,往fd[1]写入的数据可以从fd[0]读出,并且fd[0]只能从管道读出数据,fd[1]只能用于往管道写入数据。如果要实现双向的数据传输,应该使用两个管道。默认情况下,这一对文件描述符是阻塞的,如果我们使用read来读取一个空的管道,则将被read阻塞,直到管道有数据可读;使用write写一个满的管道也是一样。如果程序将这对fd设置为非阻塞,则read和write会有不同行为
管道内部传输的是字节流,管道容量的默认大小:65536字节
socketpair函数能够方便的创建双向管道。创建的一对文件描述符都是可读可写的。
1 2 3 4 #include <sys/type.h> #include <sys/socket.h> int socketpair (int domain, int type, int protocol, int fd[2 ]) ;
dup函数和dup2函数 这两可以把标准输入重定向到一个文件或者把标准输入重定向到一个网络连接(CGI编程)
1 2 3 4 #include <unistd.h> int dup (int file_descriptor) ;int dup2 (int file_descriptor_one, int file_descriptor_two) ;
dup:创建一个新的文件描述符,改文件描述符和原油文件描述符file_descriptor指向相同文件、管道或者网络连接,并且返回的文件描述符总是去取系统当前可用的最小整数值。
dup2:与dup类似,返回第一个不小于file_descriptor_two的整数值。
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 <assert.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <stdio.h> int main (int argc, char *argv[]) { if (argc <= 2 ) { printf ("Usage: %s ip port\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; server_address.sin_port = htons(port); inet_pton(AF_INET, ip, &server_address.sin_addr); int sock = socket(AF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); int ret = bind(sock, (struct sockaddr *)&server_address, sizeof (server_address)); assert(ret != -1 ); ret = listen(sock, 5 ); assert(ret != -1 ); struct sockaddr_in client_address ; socklen_t client_address_len; int connfd = accept(sock, (struct sockaddr *)&client_address, &client_address_len); if (connfd < 0 ) { printf ("errno is: %d\n" , errno); } else { close(STDOUT_FILENO); dup(connfd); printf ("abcdefg\n" ); close(connfd); } close(sock); return 0 ; }
readv函数和writev函数 readv函数
sendfile mmap函数munmap函数 splice函数 tee函数 fcntl函数 Linux服务器程序规范
Linux服务器程序一般以后台进程(守护进程)形式运行
通常有一套日志系统
以某个专门的非root身份运行
通常可配置
启动的时候生成一个PID文件并存入/var/run目录中
需要考虑系统资源和限制