1.网络编程基础API
1.1 主机字节序和网络字节序
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);
2.socket
2.1 socket结构体sockaddr
#include <bits/socket.h> struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
TCP/IP协议族有sockaddr_in,它用于IPv4
struct sockaddr_in { sa_family_t sin_family; /*地址族:AF_INET */ u_int16_t sin_port; /*端口号,要用网络字节序表示*/ struct in_addr sin_addr;/* IPv4地址结构体,见下面* / }; struct in_addr { u_int32_t s_addr; /*IPv4地址,要用网络字节序表示*/ };
接下来用代码来说明详细过程
#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; /* STGTERM 信号的处理函数, 触发时结束主程序中的循环 */ static void handle_term(int sig) { stop = true; } int main(int argc, char * argv[]) { signal( SIGTERM, handle_term ); //siganl我们后面再说 if(argc <= 2) { printf ( "usage : %s ip_address port_number backlog\n" , basename( argv[0] ) ); return 1; } const char * ip = argv[1]; //const char * ip 为底层const 值不能变 int post = atoi(argv[2]); //atoi 为 把字符串转换成整型数 // //int backlog = atoi(argv[3]); int sock = socket(PF_INET, SOCK_STREAM, 0); //PF_INET 为 底层协议族 //记住这个就够了 //type 参数 //SOCK_STREAM TCP //SOCK_DGRAM UDP //0 所有情况下设置为0 assert(sock != -1); //创建一个socket地址 struct sockaddr_in address; memset(&address, 0x00, sizeof(address)); address.sin_family = AF_INET;//协议族 address.sin_port = htons(post); inet_pton(AF_INET, ip, &address.sin_addr); //表示用字符串表示的IP地址转换为用网络字节序表示的IP地址 //另一个方法 //address.sin_addr.s_addr = htonl(ip); 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) { } else { char remote[INET_ADDRSTRLEN]; printf ( "connected with ip: %s and port: %din", inet_ntop( AF_INET, &client.sin_addr,remote, INET_ADDRSTRLEN ),ntohs( client.sin_port ) ); close( connfd) ; } close(sock); //关闭套接字 return 0; }
2.2客户端连接函数
#Include <sys/type.h> #include <sys/socket.h> int connect (int sockfd, const struct sockaddr * serv_addr, socklen_t addrlen);
2.3关闭连接
#include <unistd.h> int close(int fd); /* fd参数是待关闭的socket。不过,close系统调用并非总是立即关闭一个连接,而是将fl的引用计数减1。只有当fd的引用计数为0时,才真正关闭连接*/ #include <sys/socket.h> int shutdown(int sockfd, int howt); /* 此为半关闭 howto参数 SHUT_RD 关闭读 SHUT_WR 关闭写 SHUT_RDWR 关闭读写 */ //同时shutdown并没有计数 是立即终止连接
2.4读写
#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 ); ssize_t read(intad, void *buf, size_t count); ssize_t write(int fd, const void *bufsize_t count);
flags参数可选值有
其中MSG_OOB 可能为用得最多的 为发送或接收紧急数据(实际上只有一个为紧急数据)
2.4.1 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 );
2.5 通用读写
可以用于TCP和UDP
#include <sys/socket.h> ssize_t recvmsg ( int sockfd,struct msghdr* msg, int flags ); ssize_t sendmsg ( int sockfd,struct msghdr* msg, int flags );
msghbr的结构体如下
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参数,并在调用过程中更新*/ }; //msg_iov成员是iovec 结构体类型的指针,iovec结构体的定义如下: struct iovec { void *iov_base; /*内存起始地址*/ size_t iov_len; /*这块内存的长度*/ };
2.6杂项
2.6.1带外标记
之前演示了TCP带外数据的接收方法。但在实际应用中,我们通常无法预期带外数据何时到来。好在Linux内核检测到TCP紧急标志时,将通知应用程序有带外数据需要接收。
内核通知应用程序带外数据到达的两种常见方式是:
I/O复用产生的异常事件
SIGURG信号。
但是,即使应用程序得到了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置,才能准确接收带外数据。这一点可通过如下系统调用实现:
#include <sys/ socket.h> int sockatmark (int sockfd ) ;
sockatmark判断sockfd是否处于带外标记,即下一个被读取到的数据是否是带外数据。如果是,sockatmark返回1,此时我们就可以利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则sockatmark返回0。
2.6.2地址信息函数
在某些情况下,我们想知道一个连接socket的本端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获取sockfd对应的本端socket地址,并将其存储于address参数指定的内存中,该socket地址的长度则存储于address_len参数指向的变量中。如果实际socket地址的长度大于address所指内存区的大小,那么该socket地址将被截断。getsockname成功时返回0,失败返回-1并设置errno。 getpeername获取sockfd对应的远端socket地址,其参数及返回值的含义与getsockname的参数及返回值相同。
2.6.3socket的getsockopt和setsockopt
读取和设置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 ) ; /* level为设置协议的选项 如IPv4 option_name指定选项的名字 */
2.7(重要)端口复用问题
多线程会导致端口复用问题
解决端口复用的问题: bind error: Address already in use,发生这种情况是在服务端主动关闭连接以后,接着立刻启动就会报这种错误.
setsockopt函数
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen _toptlen); setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); //(用这个)
//setsockopt(Ifd,SoL_SOCKET, SO_REUSEPORT,&opt, sizeof(int));
2.6.3.1部分重要选项
2.6.3.1.1 SO_REUSEADDR
设置socket选项SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接占用的socket地址
2.6.3.2 SO_RCVBUF和SO_SNDBUF
设置TCP接收缓冲区和发送缓冲区的大小
接收缓冲区最小值为256
发送缓冲区最小值为2048(不同系统可能不同)
2.6.3.3 SO_RCVLOWAT和SO_SNDLOWAT
表示TCP接收缓冲区和发送缓冲区的低水位标记(默认为1字节)
一般被I/o系统调用用来判断socket是否可读或可写
2.6.3.4 SO_LINGER
控制close在关闭TCP连接时的行为
2.7 gethostbyname和gethostbyaddr
gethostbyname根据主机名称获取主机的完整信息
gethostbyaddr根据ip地址获取主机的完整信息
#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 /*按网络字节序列出的主机工Р地址列表*/ };