诸暨企业网站建设,自动点击器下载,提供手机自适应网站建设维护,提高网站打开速度第5章 Linux网络编程基础API
5.1 socket地址API
主机字节序#xff08;小端字节序#xff09;#xff1a;整数的高位字节存储在内存的高地址处#xff0c;而低位字节则存储在内存的低地址处。 网络字节序#xff08;大端字节序#xff09;#xff1a;相反。
void byt…第5章 Linux网络编程基础API
5.1 socket地址API
主机字节序小端字节序整数的高位字节存储在内存的高地址处而低位字节则存储在内存的低地址处。 网络字节序大端字节序相反。
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);}
}主机字节序和网络字节序之间的转换发送端总是把要发送的数据转化成大端字节序数据后发送。函数htonl、htons、ntohl、ntohshost to network long(short)
#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地址值
}#include sys/un.h
struct sockaddr_un
{sa_family_t sin_family; //地址族AF_UNIXchar sun_path[108]; //文件路径名
};struct sockaddr_in
{sa_family_t sin_family; //地址族AF_INETu_int16_t sin_port; //端口号网络字节序struct in_addr sin_addr; //IPv4地址结构体
};
struct in_addr
{u_int32_t s_addr; //IPv4地址网路字节序
};struct sockaddr_in6
{sa_family_t sin6_family; //地址族AF_INET6u_int16_t sin6_port; //端口号网络字节序u_int32_t sin6_flowinfo; //流信息设置为0struct in6_addr sin6_addr; //IPv6地址结构体u_int32_t sin6_scope_id; //scope ID
};
struct in6_addr
{unsigned char sa_addr[16]; //IPv6地址网路字节序
};函数inet_addr点分十进制v4地址转化为网络字节序整数表示的v4地址、inet_aton与上一个一样但结果转储在参数inp指向的地址中、inet_ntoa网络字节序表示的v4转化为点分十进制不可重入、inet_pton适用v4和v6网络字节序转储在参数dst指向的内存中、inet_ntop适用v4和v6相反
5.2 创建socket
socket就是可读、可写、可控制、可关闭的文件描述符int。
#include sys/types.h
#include sys/socket.h
int socket(int domain, int type, int protocol);
/*
** domain:底层协议族
** type:服务类型SOCK_STREAMTCP流服务、SOCK_UGRAMUDP数据报服务
** protocol:0表示默认协议
*/5.3 命名socket
命名将一个socket与socket地址绑定。bind函数。 服务器中需要客户端不需要而是采用匿名即使用OS自动分配的socket地址。
5.4 监听socket
listen函数创建一个监听队列以存放待处理的客户连接。客户端被动连接。
#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.hstatic bool stop false;/* SIGTERM信号的处理函数触发时结束主程序中的循环 */
static void handle_term(int sig)
{stop true;
}int main(int argc, char **argv)
{signal(SIGTERM, handle_term);if (argc 4) {printf(usage: %s ip_address port_number backlog\n,basename(argv[0]));return 1;}const char *ip argv[1]; //IP地址int port atoi(argv[2]); //端口号int backlog atoi(argv[3]); //backlog值内核监听队列的最大长度int sock socket(PF_INET, SOCK_STREAM, 0);assert(sock 0);/* 创建一个IPv4 socket地址 */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);/* 循环等待连接 查到有SIGTERM信号将它中断 */while(!stop){sleep(1);}/* 关闭socket */close(sock);return 0;
}监听队列中完整连接的上限通常比 backlog 值略大。
5.5 接受连接
accept函数。
#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.hint 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]);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, 5);assert(ret ! -1);/* 暂停20秒以等待客户端连接和相关操作(掉线或者退出)完成 */sleep(20);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 {/* 接受连接成功则打印客户端的IP地址和端口号 */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;
}
accept只是从监听队列中取出连接而不论连接处于何种状态 (如上面的ESTABLISHED 状态和 CLOSE_WAIT 状态更不关心任何网络状况的变化。
5.6 发起连接
客户端通过connect函数主动与服务器建立连接。
5.7 关闭连接
关闭该连接对应的socket。 close函数将参数fd的引用计数减1为0才是真正关闭。多进程程序中一次fork默认使父进程中打开的socket的引用计数加1。读写同时关闭。 shutdown函数分别关闭socket上的读或写或者都关闭。
5.8 数据读写
TCP数据读写recv可能要多次调用才能读取完整数据、send写入数据
#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.hint 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 {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); //flagsMSG_OOB 带外数据send(sockfd, normal_data, strlen(normal_data), 0);}close(sockfd);return 0;
}#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 1024int 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);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); //flagsMSG_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);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数据读写recvfromUDP通信没有连接的概念每次读取数据都要获取发送端的socket地址、sendto写入数据。 通用数据读写函数recvmsg数据将被读取并存放在msg_iovlen块分散的内存称为分散读、sendmsgmsg_iovlen块分散内存中的数据将被一并发送称为集中写。
5.9 带外标记
内核检测到TCP紧急标志然后通知应用程序带外数据到达的两种常见方式是I/O复用产生的异常事件和SIGURG信号。应用程序通过sockatmark函数得到带外数据在数据流中的具体位置。
5.10 地址信息函数
getsockname获取sockfd对应的本端socket地址存储与参数指定的内存中。 getpeername获取远端socket地址。
5.11 socket选项
读取和设置socket文件描述符属性getsockopt、setsockopt。 对服务器而言部分socket选项在TCP同步报文段中设置只能在调用listenlisten监听队列中的连接至少是SYN_RCVD状态三次握手前两步至少已经完成同步报文段已发出之前设置才有效。 对客户端而言部分socket选项应该在connect之前设置。
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include stdio.h
#include assert.h
#include unistd.h
#include stdlib.h
#include errno.h
#include string.hint 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]);int sock socket(PF_INET, SOCK_STREAM, 0);assert(sock);int reuse 1;//即使 sock 处于 TIME_WAIT 状态与之绑定的 socket 地址也可以立即被重用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));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 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;
}#include sys/socket.h
#include arpa/inet.h
#include assert.h
#include stdio.h
#include unistd.h
#include string.h
#include stdlib.h#define BUFFER_SIZE 512int main(int argc, char *argv[])
{if (argc 2) {printf(usage: %s ip_address port_number send_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;inet_pton(AF_INET, ip, server_address.sin_addr);server_address.sin_port htons(port);int sock socket(PF_INET, SOCK_STREAM, 0);assert(sock 0);int sendbuf atoi(argv[3]);int len sizeof(sendbuf);/* 先设置TCP发送缓冲区的大小然后立即读取之 */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;
}#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 BUFFER_SIZE 1024int main(int argc, char *argv[])
{if (argc 2) {printf(usage: %s ip_address port_number recv_buffer_size\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 recvbuf atoi(argv[3]);int len sizeof(recvbuf);/* 先设置TCP接受缓冲区的大小然后立即读取之 */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 *)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[BUFFER_SIZE];memset(buffer, \0, BUFFER_SIZE);while(recv(connfd, buffer, BUFFER_SIZE - 1, 0) 0) {}close(connfd);}close(sock);return 0;
}setsockopt设置缓冲区大小时系统会将其值加倍且TCP接收缓冲区最小值为256字节发送缓冲区最小值为2048字节。 SO_RCVLOWAT 和 SO_SNDLOWAT 选项分别表示TCP接收缓冲区和发送缓冲区的低水位标记。它们一般被I/O复用系统调用来判断socket是否可读或可写。默认1字节。 SO_LINGER 选项用于控制 close 系统调用在关闭TCP连接时的行为。默认情况下当我们使用 close 来关闭一个socket时close 将立即返回TCP模块负责把该 socket对应的TCP发送缓冲区中残留的数据发送给对方。
5.12 网络信息API
socket地址由IP地址和端口号组成不便于扩展可以用主机名代替IP地址用服务名称代替端口号。 gethostbyname根据主机名称获取主机完整信息。 gethostbyaddr根据IP地址获取主机完整信息。 getservbyname根据名称获取某个服务的完整信息。 getservbyport根据端口号获取某个服务的完整信息。 四个函数均不可重入即非线程安全。尾部加_r是可重入版本。
#include sys/socket.h
#include netinet/in.h
#include netdb.h
#include stdio.h
#include unistd.h
#include assert.hint main(int argc, char *argv[])
{assert(argc 2);char *host argv[1];/* 获取目标主机地址信息 */struct hostent *hostinfo gethostbyname(host);assert(hostinfo);/* 获取daytime服务信息 */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;/* 注意下面的代码因为h_addr_list本身使用网络字节序的地址列表所以使用其中的IP* 地址时无需对目标IP地址转换字节序 */address.sin_addr *(struct in_addr *)*hostinfo-h_addr_list;int sockfd socket(PF_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 time is : %s\n, buffer);close(sockfd);return 0;
}getaddrinfo通过主机名获得IP地址内部使用的是 gethostbyname 函数也能通过服务名获得端口号内部使用的是 getservbyname 函数)。它是否可重人取决于其内部调用的函数是否是它们的可重入版本。将隐式地分配堆内存因为参数result指针struct addrinfo*类型指向链表存储结果原本是没有指向一块合法内存的调用结束后必须使用 freeaddrinfo 函数来释放这块内存。 getnameinfo通过socket地址同时获得以字符串表示的主机名内部使用的是gethostbyaddr 函数和服务名内部使用的是 getservbyport 函数它是否可重入取决于其内部函数是否是它们的可重入版本。