UNIX网络编程---套接字编程简介
从这里开始正式开始网络编程之旅,所有的函数都是基本的库函数。这些都是网络编程的基础。Come on!!!!
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议簇都定义它自己的套接字地址结构。这些机构的名字均以sockaddr_开头,并以对应每个协议簇的唯一后缀结尾。
1) Ipv4套接字地址结构
这是一个很常用的套接字。定义在
struct in_addr{
in_addr_t s_addr; //32位 IPV4 address
};
struct sockaddr_in{
uint8_t sin_len; //无符号的8位整数
sa_family_t sin_family; //应该也为8位整数
In_port_t sin_port; //16位无符号
struct in_addr sin_addr; //32位
char sin_zero[8];
};
从进程到内核传递套接字地址结构的
4
个套接字函数(
bind
、
connect
、
sendto
、
sendmsg
)都要调用
sockargs
函数,该函数从进程复制套接字地址结构,并显示地把它的
sin_len
字段设置成早先作为参数传递给这
4
个函数的该地址结构的长度。从内核到进程传递套接字地址结构的
5
个套接字函数分别是
accept
、
recvfrom
、
recvmsg
、
getpeername
和
getsockname
,均在返回到进程之前设置
sin_len
字段。
Ipv4地址和TCP或UDP端口号在套接字地址结构中总是以网络字节来存储。
套接字地址结构仅在给定主机上使用:虽然结构中的某些字段用在不同主机之间的通信,但是结构本身并不在主机之间传递。
2) 通用套接字地址结构
在头文件 #include
Struct sockaddr{
Uint8_tsa_len;
Sa_family_t sa_family;
Char sa_data[14];
};
其实通用套接字struct sockaddr结构体的大小(16字节)和常用的套接字地址struct sockaddr_in大小一样。
3) Ipv6套接字地址结构
在头文件
Struct in6_addr{
Unit8_ts6_addr[16]; //128-bit Ipv6 address
};
#define SIN6_LEN
Struct sockaddr_in6{
Uint8_t sin_len;
Sa_family_t sin6_family;
In_port_t sin6-port;
Uint32_t sin6_flowinfo;
Struct in6_addr sin6_addr;
Uint32_t sin6_scope_id;
}
还有关于新的通用套接字的介绍,这里只要知道几个IP地址不一样即可,同时还有各种套接字结构的对比,IPv4的套接字结构最小为16字节,IPv6的套接字结构最小为28个字节。域套接字的长度可变。
当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递,该结构的长度也作为一个参数来传递,不过期传递方式取决于该结构的传递方向,是从进程到内核,还是从内核到进程。
从内核到进程的函数,其中两个参数是指向某个套接字地址结构的指针和指向表示该结构大小的整数变量的指针,比如accept,recvfrom。
把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,原因在于:当函数被调用时,结构大小是一个值,它告诉内核结构的大小,这样内核在写该结果时不至于越界;当函数返回时,结构大小又是一个结构,它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数称为值-结构参数。
在网络编程中,值-结果参数最常见的例子是所返回套接字地址结构的长度。还有一些值-结果参数
Select函数中间的3个参数
Getsockopt函数的长度参数
使用recvmsg函数时,msghdr结构中的msg_namelen和msg_controllen字段
Ifconf机构中的ifc_len字段
Sysctl函数两个长度参数中的第一个。
一句话概括:
从进程传递给内核的参数是需要让内核知道内核需要读取多少字节
从内核传递给内核的参数是需要让进程知道内核给进程写了多少字节
关注如何在主机字节序和网络字节序之间相互转换
#include
Uint16_t htons(uint16_t host16bitvalue);
Uint32_t htonl(uint32_t host32bitvalue); // 均返回网络字节序的值
Uint16_t ntohs(uint16_t host16bitvalue);
Uint32_t htons(uint32_t host32bitvalue);//均返回主机字节序的值
在这些函数的名字中,h代表host,n代表network,s代表short,l代表long.short和使用这些函数时,我们并不关心主机字节序和网络字节序的真实值。
字节处理函数
#include
Void bzero(void *dest,size_t nbytes);
Void bcopy(const void *src,void *dest,size_t nbytes);
Int bcmp(const void *ptrl,const void *ptr2,size_t nbytes); //若相等则为0,否则为非0
在c语言中也有和这些功能一样的函数
#include
Void *memset(void *dest,int c,size_t len);
Void *memcpy(void *dest,const void *src,size_t nbytes);
Int memcmp(const void *ptrl,const void *ptr2,sieze_t nbytes);// 若相等则为0,否则为<0或者>0
Memset把目标字节串指定数据的字节置为值c。memcpy类似bcopy,不过两个指针参数的顺序是相反的。
记忆memcpy两个指针参数顺序的方法之一是记着他们是按照与C中的赋值语句相同的顺序从左到右写的 dest=src
Memcpy比较两个任意的字节串,若相同则返回0,否则返回一个非0值,是大于0还是小于0则取决于第一个不等的字节。
六、Inet_aton、inet_addr和inet_ntoa函数#include
int inet_aton(const char *strptr,struct in_addr *addrptr);//若字符串有效则为1,否则为0
in_addr_t inet_addr(const char *strptr); //若字符串有效则为32位二进制网络字节序的Ipv4地址,否则为INADDR_NONE
char *inet_ntoa(struct in_addr inaddr); //指向一个点分十进制数串的指针
前两个函数的功能一样
这两个函数是比较新的,对Ipv4和Ipv6都适用。P代表表示(presentation)n代表数值(numeric)
#include
int inet_pton(int family,const chat *strptr,void *addrptr); //若成功为1,
const char *inet_ntop(int family,const void *addrptr,char*strptr,size_t len);
//若成功则为指向结果的指针,出错为NULL
第一个函数尝试转换由strptr指针所指的字符串,并通过addrptr指针存放二进制结果。若成功返回值为1,否则返回0.
Inet_ntop进行相反的转换,从数值格式(addrptr)转换到表达式(strptr)。len参数是目标存储单元的大小,以免该函数溢出调用者的缓冲区。
由点分十进制转换为数值格式的函数(Ipv4):
inet_aton(const char *strptr,struct in_addr *addrptr);
inet_addr(const chat *strptr);
inet_pton(int family,const char *strptr,void *addrptr);
上图是地址转换函数小结,可以参考,关于地址转换的函数也就那么几个
由数值格式转换为点分十进制(Ipv4):
char *Inet_ntoa(struct in_addr *addrptr);
inet_ntop(int family,const void *addrptr,char*strptr,size_t len);
其中inet_pton()函数的实现:
Intinet_pton(int family,const char *strptr,viod *addrptr)
{
If(family==AF_INET){
Struct in_addr temp;
Memset(&temp,0,sizeof(temp));
If(Inet_aton(strptr,&temp)){
Memcpy(addrptr,&temp,sizeof(temp));
Return (1);
}
Return 0;
}
Errno=EAFNOSUPPORT;
Return (-1);
}
Const char *inet_ntop(int family,const void *addrptr,char*strptr,size_t len)
{
Const u_char*p=(const u_char *)addrptr;
If(family==AF_INET)
{
Char temp[128];
Snprintf(temp,sizeof(temp),”%d.%d.%d.%d”,p[0],p[1],p[2],p[3]);
If(strlen(temp)>=len){
Errno=ENOSPC;
Return (NULL);
}
Strcpy(strptr,temp);
Return strptr;
}
Errno=EAFNOSUPPORT;
ReturnNULL;
}
八、Sock_ntop和相关函数
这里的函数都是作者为了更好的利用而自己编写的,不是标准的库函数
九、Readn、writen和readline函数
Size_t readn(int fileds,void *buff,size_t nbytes);
Size_t writen(int fileds,const void *buff,size_t nbytes);
Size_t readline(int fileds,void *buff,size_t maxlen);
其中readn函数的实现如下:
Int readn(int fileds,char *str,size_t n)
{
Char *ptr;
Int nleft,nread;
Nleft=n;
Ptr=str;
While(nleft)
{
If((nread=read(filed,ptr,n))<0)
{
If(errno==EINTR)
Nread=0;
Else
Return -1;
}
Else if(nread==0)
Break;
Nlefr-=nread;
Ptr+=nread;
}
Return (n-left);
}
这里只给出了一个函数的具体实现,这些函数只要知道怎么使用,对于基础的学习来说更加重要