UNP卷1:第三章(套接字编程简介)
最后更新于:2022-04-01 14:48:53
### 1. 套接字结构
### 1) IPv4套接字地址结构
~~~
truct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in{
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
~~~
我们通常只使用三个字段:sin_family,sin_addr和sin_port.如下图所示:
~~~
bzero( &servaddr, sizeof( servaddr ) );
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(7777);
~~~
而一个实例如下:
~~~
#include <stdio.h>
#include <sys/stat.h>
#include <netinet/in.h>
int main( void )
{
printf("%d\n", htonl(INADDR_ANY));
printf("%d\n", htonl(16));
printf("%d\n", htons(13));
return 0;
}
~~~
程序输出:
~~~
leichaojian@ThinkPad-T430i:~$ ./a.out
0
268435456
3328
~~~
### 2)通用套接字结构
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式来传递。然而以这样的指针作为参数之一的任何套接字函数必须处理来自所支持的任何协议族的套接字地址结构。
~~~
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family; /*address family:AF_xxx value*/
char sa_data[14]; /*protocol-specific address*/
};
~~~
于是套接字函数被定义为以指向某个通用套接字地址结构的一个指针作为其参数之一,类似bind函数:
~~~
bind(int, struct sockaddr *, socklen_t);
~~~
我们调用bind的时候,必须进行强制类型转换:
~~~
struct sockaddr_in serv;
.......
bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));
~~~
### 3)新的通用地址结构
~~~
struct sockaddr_storage{
uint8_t sa_len;
sa_family_t sa_family;
};
~~~
sockaddr_storage类型提供的通用套接字地址结构相比sockaddr存在以下两点差别:
(1)如果系统支持的任何套接字地址结构有对齐需求,那么sockaddr_storage能够满足最苛刻的对齐要求。
(2)sockaddr_storage足够大,能够容纳系统支持的任何套接字地址结构。
备注:sockaddr_storage的其他字段对用户来说是透明的。
### 2) 值-结果参数
当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递,也就是说传递的是指向该结构的一个指针。该结构的长度也作为一个参数来传递,不过其传递方式取决于该结构的传递方向:是从进程到内核,还是从内核到进程。
(1)从进程到内核传递套接字地址结构的函数有3个:bind,connect和sendto。这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小,例如:
~~~
struct sockaddr_in serv;
connect( sockfd, ( SA * )&serv, sizeof( serv ) );
~~~
既然指针和指针所指内容的大小都传递给了内核,于是内核知道到底需从进程复制多少数据进来。
(2)从内核到进程传递套接字地址结构的函数有4个:accept,recvfrom,getsockname和getpeername。
~~~
struct sockaddr_un cli;
socklen_t len;
len = sizeof( cli );
getpeername( unixfd, ( SA * )&cli, &len );
/*len may have changed*/
~~~
这里len既作为值传递进去,又作为结果返回回来。
### 3) 字节排序函数
### (1)大端模式---小端模式
~~~
#include <stdio.h>
int main( int argc, char **argv )
{
union{
short s;
char c[ sizeof( short ) ];
} un;
un.s = 0x0102;
if ( sizeof( short ) == 2 ){
if ( un.c[ 0 ] == 1 && un.c[ 1 ] == 2 )
printf("big-endian\n");
if ( un.c[ 0 ] == 2 && un.c[ 1 ] == 1 )
printf("little-endian\n");
else
printf("unknown\n");
}
else
printf("sizeof(short)=%d\n", sizeof( short ) );
exit( 0 );
}
~~~
程序输出:
~~~
leichaojian@ThinkPad-T430i:~$ ./a.out
little-endian
~~~
### (2) 主机字节序和网络字节序之间相互转换的四个函数
~~~
#include <netinet/in.h>
uint16_t htons( uint16_t host16bitvalue );
uint32_t htonl( uint32_t host32bitvalue );
------------均返回:网络字节序的值
uint16_t ntohs( uint16_t net16bitvalue );
uint32_t ntohl( uint32_t net32bitvalue );
------------均返回:主机字节序的值
~~~
在这些名字中,h代表host,n代表network,s代表short,l代表long。如今我们应该把s视为16位的值(如TCP或UDP端口号),把l视为一个32位的值(例如IPv4地址)
测试用例如下:
~~~
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char **argv)
{
uint32_t ipAddr;
uint16_t portAddr;
struct sockaddr_in servaddr;
char buff[1024];
bzero(&servaddr, sizeof(servaddr));
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
ipAddr = ntohl(servaddr.sin_addr.s_addr);
printf("ipAddr is:%d\n", ipAddr);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_addr.s_addr = htonl(ipAddr);
inet_ntop(AF_INET, &servaddr.sin_addr, buff, sizeof(buff));
printf("the ip addr is:%s\n", buff);
printf("------------\n");
portAddr = htons(9877);
printf("the port 9877 netword port is:%d\n", portAddr);
printf("the port is:%d\n", ntohs(portAddr));
return 0;
}
~~~
程序输出:
~~~
leichaojian@ThinkPad-T430i:~$ ./test 127.0.0.1
ipAddr is:2130706433
the ip addr is:127.0.0.1
------------
the port 9877 netword port is:38182
the port is:9877
~~~
### 4) 相关的重要函数
### (1)inet_aton和inet_ntoa函数
~~~
#include <arpa/inet.h>
int inet_aton( const char *strptr, struct in_addr *addrptr );
返回:若字符串有效则为1,否则为0
char *inet_ntoa( struct in_addr inaddr );
返回:指向一个点分十进制数串的指针
~~~
测试程序如下:
~~~
1 #include <stdio.h>
2 #include <arpa/inet.h>
3
4 int main( int argc, char **argv )
5 {
6 struct in_addr addr;
7 char *pAddr;
8 inet_aton( argv[ 1 ], &addr );
9 printf( "%d\n", addr );
10 pAddr = inet_ntoa( addr );
11 printf("%s\n", pAddr );
12
13 return 0;
14 }
~~~
程序输出:
~~~
leichaojian@ThinkPad-T430i:~$ ./a.out 127.0.0.1
16777343
127.0.0.1
~~~
### (2)inet_pton和inet_ntop函数
~~~
#include<arpa/inet.h>
int inet_pton( int family, const char *strptr, void *addrptr );
const char *inet_ntop( int family, const void *addrptr, char *strptr, size_t len );
~~~
这里p和n分别代表“表达”(presentation)和“数值”(numeric).即将点分十进制IP地址转换为套接字结构中的二进制值,或者逆向转换:
~~~
if ( inet_pton( AF_INET, argv[ 1 ], &servaddr.sin_addr ) <= 0 ){
printf("inet_pton error for %s", argv[1]);
exit(1);
}
~~~
### (3)readn,writen(作者自己编写的)
字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,然而这不是出错的状态。这个现象的原因在于内核中用于套接字的缓冲区可能已达到极限,此时所需的是调用者再次调用read或write函数,以输入或输出剩余的字节。
~~~
ssize_t readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0){
if ((nread = read(fd, ptr, nleft)) < 0){
if (errno == EINTR)
nread = 0;
else
return (-1);
} else if (nread == 0)
break;
nleft --= nread;
ptr += nread;
}
return (n - nleft);
}
ssize_t writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0){
if ((nwritten = write(fd, ptr, nleft)) <= 0){
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return (-1);
}
nleft -= nwritten;
ptr += nwritten;
}
return (n);
}
~~~