unp第二章主要将了TCP和UDP的简介,这些在《TCP/IP详解》和《计算机网络》等书中有很多细致的讲解,可以参考本人的这篇博客【计算机网络 第五版】阅读笔记之五:运输层,这篇博客就不再赘述。
本篇博客主要记录套接字编程API,从一些基本的API来一步一步了解套接字网络编程。
1.套接字地址结构
大多数的套接字函数都以一个指向套接字地址结构的指针作为参数。每个协议簇都定义了自己的套接字地址结构。
套接字地址结构均以sockaddr_开头,并以对应每个协议簇的唯一后缀结尾。
1.1 ipv4套接字地址结构
typedef uint32_t in_addr_t;
struct 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;
unsigned char sin_zero[8];
};
通常我们在使用套接字的时候,只会用到三个字段:sin_family,sin_addr和sin_port,如下:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13);
1.2 通用套接字结构
当作为一个参数传递进任何套接字函数时,套接字地址总是以引用形式来传递。
类似于void*代表通用指针类型,通用套接字地址结构可以便于参数传递,使得套接字函数能够处理来自所支持的任何协议簇的套接字地址结构。
struct sockaddr
{
__SOCKADDR_COMMON (sa_);
char sa_data[14];
};
以bind函数为例,说明它的用法:
struct sockaddr_in servaddr;
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
int bind(int , struct sockaddr* , socklen_t);
1.3 IPv6的套接字地址结构
struct in6_addr
{
uint8_t __u6_addr8[16];
}
struct sockaddr_in6
{
uint8_t sin_len
sa_family_t sin_family
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
1.4 新的通用套接字地址结构
为IPV6套接字API定义的新的通用套接字地址结构,其足以容纳所支持的任何套接字地址结构
书中说到在in.h文件中,可是我怎么也找不到。后来在usr/include/X86_64-linux-gnu/bits的socket.h中找到
struct sockaddr_storage
{
uint8_t ss_len;
sa_family_t ss_family;
__ss_aligntype __ss_align;
char __ss_padding[_SS_PADSIZE];
};
2. 值-结果参数
当一个套接字函数传递一个套接字地址结构时,往往是采用引用的方式传递。为了读取正确的字节数和写入时不会越界,该套接字的长度也需要作为参数传递给套接字函数。
不过,其传递方式取决于该结构的传递方向:是从进程到内核,或者内核到进程
2.1 从进程到内核
这类函数有三个:bind,connect和sendto,其传递的时该结构的整数大小,这样内核就知道从进程中复制多少数据,例如:
struct sockaddr_in serv;
connect( sockfd, ( SA * )&serv, sizeof( serv ) );
2.2 从内核到进程
这类函数有4个,accept,recvfrom,getsockname和getpeername,其传递的是指向某个套接字结构的指针和指向表示该结构大小的整数变量和指针:
struct sockaddr_un cli;
socklen_t len;
len = sizeof(cli);
getpeername(unixfd,(SA*)&cli,&len);
3. 字节排序函数
3.1 大端和小端字节序
字节序分为大端字节序和小端字节序,以下面的代码来判断系统到底时何种字节序:
#include "unp.h"
int main(int argc, char const *argv[])
{
union {
short s;
char c[sizeof(short)];
}un;
un.s = 0x0102;
printf("%s: ", CPU_VENDOR_OS);
if (sizeof(short) == 2)
{
if (un.c[0]==0x01 && un.c[1]==0x02)
{
printf("big-endian\n");
}
else if(un.c[0]==0x02 && un.c[1]==0x01)
{
printf("little-endian\n");
}
else
printf("unknown\n");
}
else
printf("sizeof(short)=%d\n", (int)sizeof(short));
exit(0);
return 0;
}
运行此代码后输出:
$ gcc byteorder.c -o byteorder -lunp
$ ./byteorder
x86_64-unknown-linux-gnu: little-endian
代表本机时小端字节序
3.2 转换函数
网络字节序使用大端字节序来传送,那么本机和网络之间要正确传递数据,就需要一个转换函数。
这两种字节序之间的转换使用以下4种函数:
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代表net网络,s代表short,l代表long
测试用例:将0x1234(4660)从主机字节序转换成网络字节序,转换后应该为0x3412(13330)
#include "unp.h"
int main(int argc, char const *argv[])
{
uint16_t portAddr;
portAddr = htons(0x1234);
printf("the port 0x1234 netword port is:%d\n", portAddr);
printf("the port is:%d\n", ntohs(portAddr));
return 0;
}
the port 0x1234 netword port is:13330
the port is:4660
4. 字节操纵函数
操作多字节字段的函数有两组,他们既不对数据作解释,也不假设数据是以空字符结束的C字符串。
#include <string.h>
void bzero(void* dest,size_t nbytes);
void bcopy(const void* src,void *dest , size_t nbytes);
int bcmp(const void *ptr1, const void* ptr2, size_t nbytes);
void *memset(void *dest , int c , size_t len);
void *memcpy(void *dest , const void* src , size_t nbytes);
int memcmp(const void *ptr1 , const void *ptr2, size_t nbytes);
5.地址转换函数
5.1 inet_aton,inet_addr和inet_ntoa函数
在点分十进制数串和与它长度为32位的网络字节序二进制值间转换ipv4地址
int inet_aton (const char *__cp, struct in_addr *__inp) ;
in_addr_t inet_addr (const char *__cp);
char *inet_ntoa (struct in_addr __in);
测试用例:输入一个点分十进制网络地址,测试输出
#include "unp.h"
int main(int argc, char const *argv[])
{
struct in_addr addr;
char *pAddr;
inet_aton(argv[1],&addr);
printf("%d\n",addr.s_addr);
pAddr = inet_ntoa(addr);
printf("%s\n",pAddr);
return 0;
}
$ ./Test_inet_aton 127.0.0.1
16777343
127.0.0.1
5.2 inet_pton和inet_ntop函数
随着IPv6出现的新函数,p代表表达式,即ASCII字符串;n代表数值,即存放在套接字地址结构中的二进制值。
这两个函数对于ipv4和ipv6都适用。
int inet_pton (int __af, const char *__cp, void * __buf)
const char *inet_ntop (int __af, const void *__cp, char *__buf, socklen_t __len)
5.3 sock_ntop和相关函数(作者自定义的函数)
inet_ntop函数的基本问题时要求调用者传递一个指向某个二进制地址的指针,而该地址通常包含在一个套接字地址中
因此调用者事先需要知道这个结构的格式。
struct sockaddr_in addr;
inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));
为了解决这个问题,作者自己写了一个函数:
#include "unp.h"
char *sock_ntop(const struct sockaddr *sa, socklen_t salen);
char *
sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
char portstr[8];
static char str[128];
switch (sa->sa_family) {
case AF_INET: {
struct sockaddr_in *sin = (struct sockaddr_in *) sa;
if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
return(NULL);
if (ntohs(sin->sin_port) != 0) {
snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
strcat(str, portstr);
}
return(str);
}
#ifdef IPV6
case AF_INET6: {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
str[0] = '[';
if (inet_ntop(AF_INET6, &sin6->sin6_addr, str + 1, sizeof(str) - 1) == NULL)
return(NULL);
if (ntohs(sin6->sin6_port) != 0) {
snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port));
strcat(str, portstr);
return(str);
}
return (str + 1);
}
#endif
}
6. readn,writen和readline
字节流套接字上调用read和write输入和输出的字节数可能比请求的数量要少,所以作者自己写了readn,writen和readline三个函数。
readn的实现如下:
#include "unp.h"
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);
}
writen的实现如下:
#include "unp.h"
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);
}
readline的实现如下:
#include "unp.h"
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = read(fd, &c, 1)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
if (n == 1)
return(0);
else
break;
} else
return(-1);
}
*ptr = 0;
return(n);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)