Linux下的网络编程(1)
1 基本概念
1.1 通信模式:线路交换和包交换
1.2 OSI(Open System Interconnection)七层通信协议。
1.3 TCP/IP协议
1.4 客户/服务器模式
1.5 套接口:网络进程ID,端口号和网络地址组合唯一地确定一个网络进程
2 Linux 中套接口的数据结构
套接口数据结构总是通过指针向一个套接口函数传递信息。为了做到协议无关,在套几口函数中的套接口地址指针必须支持所有协议族的套接口地址指针。
2.1 通用套接口地址数据结构:
#include <sys/socket.h>
struct sockaddr
{
unit8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
}
参数: unit8_t 无符号的8位整数 <sys/types.h>
sa_family_t 套接口地址机构的地址族 <sys/socket.h>
socklen_t 套接口地址结构的长度 <sys/socket.h>
in_port_t Ipv4地址 <netinet/in.h>
in_addr_t TCP或UDP端口 <netinet/in.h>
因此,套接口函数被定义为采用指向通用套接口地址结构的指针,如connect函数:
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t, addrlen);
对该函数的任何调用,都应该将指向特定协议的套接口地址结构的指针类型,转换成指向通用套接口地址结构的指针。类型转换"struct sockaddr"不能省略。
2.2 IPv4套接口地址数据结构 (sockaddr_in)
sockaddr_in机构中的成员均以sin_开头
include <netinet/in.h>
struct in_addr
{
in_addr_t s_add; //32位IP地址, 网络字节序
};
struct sockaddr_in
{
uint8 sin_len;
sa_family_t sin_family; //协议族名 IPv4为AF_INET
in_port_t sin_port; //16位端口号,网络字节序
struct in_addr sin_addr;
char sin_zero[8]; //备用的域
};
3 基本函数
3.1 字节排序函数
网络字节序使用的是大端字节序,给定系统所采用的主机字节序可能为小端字节序,两者之间互相转换的函数
#include <netinet/in.h>
uint16_t htons[uint16_t hostvalue];
uint32_t htonl[uint32_t hostvalue];
返回的是网络字节序
uint16_t ntohs[uint16_t netvalue];
uint32_t ntohl[uint32_t netvalue];
返回的是主机字节序
h: host, n: network, s: short, l: long
一般使用htos和ntohs转换端口号,使用htonl和ntohl转换IP地址
3.2 字节操纵函数
读取结构体中的某个字节。
b打头的函数由任何支持套接口函数的系统提供,mem由任何支持ANSI C库的系统所提供
#include <string.h>
void bzero(void *dest, size_t nbytes); //将指定的起始地址设置为0
void bcocy(const void *str, 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 *memecpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
3.3 地址转换函数
in_addr_t inet_addr(const char *straddr);
功能:将ASCII地址转换为网络字节序的32位二进制值
结果:返回32位二进制的网络字节序地址,如果出错返回INADDR_NONE——一个Linux定义的常量,一个不存在的IP地址,一般将这个常量定义为255.255.255.255,用二进制表示再转换成有符号数就是-1。
int inet_aton(const char *straddr, struct in_addr *addrp);
功能:将ASCII地址转换为网络字节序的32位二进制值。
结果:返回1表示转换成功, 返回0则是转换不成功。
参数:输入ASCII数放在 straddr中, 返回结果的整数放在参数addrp中。
char *inet_ntoa(struct in_addr inaddr);
功能:将网络字节序转换为ASCII地址
结果:返回ASCII地址, 如果转换不成功, 返回NULL
eg. struct_sockaddr_in类型的变量sin,想将IP地址"162.105.12.145"存储到其中,可以使用函数inet_addr()。
sin.sin_addr.s_addr = inet_addr("162.105.12.145");
如果有个数据结构struct in_ddr, 按照ASCII格式打印, 可以使用inet_ntoa()。
printf("%s", inet_ntoa(ina.sin_addr));
3.4 字节流读写函数
通常文件的I/O实现是使用read()和write(),字节流套接口上的read()和write()的表现行为不同于上,原因是套接口的缓冲区,它们所读写的字节数可能比要求的要少。
3.5 连接函数
首先,socket();
#include <sys/socket.h>
int socket(int family, int type, int protocol);
功能:生成一个套接口描述字。
结果:返回非负描述字,表成功;负值表失败
参数:family:协议族;type:字节流类型; protocol:一般为0.
family = AF_INET; //IPv4协议
type = SOCK_STREAM; //TCP套接口
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
功能:为套接口分配一个本地协议地址。
结果:返回0表成功,-1 表失败,并设置全局变量errno。
接着,bind();
bind()函数不是非必须的,一般用socket()得到套接口后直接调用connect()或者listen(), 内核会自动给套接口分配一个地址和端口号,只有希望进程使用某个特定的网络地址和端口号时,才会使用bind()。客户端很少使用。
my_addr中的IP地址必须是所有主机的IP地址之一。端口号除系统保留的外可以任意指定,常见错误EADDRINUSE. IP地址可以同时被几个进程使用,端口在一个时刻只能为一个进程服务。
设置端口为0(表由内核指定端口号)
struct sockaddr_in serveraddr;
serveraddr.port = 0;
设置IP地址(INADDR_ANY,表由内核指定IP地址)
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
然后,connect() OR listen();accept();
#include <sys/types.h>
#include <sys/socket.h>
int connet(int sockfd, const struct sockaddr *serv_addr, int addrlen);
结果:成功0,失败-1并设置errno为相应的错误号。
参数:sockfd:系统调用socket()返回的套接字;serv_addr:目的端口和IP地址的套接口;addrlen是目的套接口的长度
服务器端,先用listen()监听,然后用accept()逐一处理各个连接。
int listen(int sockfd, int backlog);
结果:0表成功;-1 表失败。
参数:backlog:规定内核为此套接口排队的最大选择个数。
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
结果:返回非负描述字表示成功,出错将返回-1,如果成功,则返回值用来标识新建立的连接。
参数:cliaddr: 客户进程的协议地址;addrlen: 客户进程的协议长度;sockf: 监听套接口描述字
accept的函数返回值称为已连接套接字。区别:监听套接字只有一个,而且一直存在,每个连接都有一个已连接套接字,当连接断开时就关闭该描述字。如果不需要客户的协议地址,可以将第二第三个参数设置为空
最后,关闭套接口。
#include <unistd.h>
int close(int sockfd);
结果:成功返回0,否则-1。
int shutdown(int sockfd, int how);
功能:允许某一方向的通信或者双方通信关闭
参数:sockfd:想要关闭的套接口文件描述字;how: 0不允许再接受;
1不允许再发送;
2不允许再接受和发送。
注意:shutdown()严格来讲,并没有关闭套接口,而是关闭了该套接口的通信功能,该套接口仍是打开的。
服务器为每一个连接生成一个新进程来 处理具体的连接。
#include<unistd.h>
pid_t fork(void);
结果:父进程中,返回子进程的ID号;子进程中,返回0;出错返回-1。所以可以通过返回值来判断当前进程是父进程还是子进程。
注意:父进程在调用fork之前打开的所有描述字在函数fork返回之后都是共享的。可以在父进程总调用accept标识新的连接然后调用fork,随后子进程协议连接套接口,而父进程则关闭已连接的套接口。
函数exec有一下六种形式
#include <unitstd.h>
int execl(const char *pathname, const char *arg0,...);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ...char *const envp[]);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0,...);
int execl(const char filename, char *const argv[]);
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
功能:返回本地协议地址
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
功能:返回远程协议地址
4 IP地址转换
4.1 域名服务系统DNS(Domain Name Service)
域名服务系统是提供主机名IP 地址之间的转换服务的一种系统
4.2 名字地址转换为数字地址
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname);
功能:实现名字地址到数字地址的转换
结果:返回一个指向结构hostent的指针,失败返回一个NULL,并设置h_error为相应值
Hostent 数据结构:
struct hostent
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr_list[0]
参数:h_name:主机的规范名称,该字符数组以'\0'结束,可以作为字符串直接打印;
h_aliases: 主机别名列表,指向一个二维数组,数组元素如h_name形式
h_addrtype: 返回主机地址类型,IPv4是AF_INET, IPv6是AF_INET6
h_length: 返回地址长度(字节)IPv4是4, IPv6是16
h_addr_list: 主机的一组网络地址列表,使用网络字节顺序
h_addr h_addr_list[0]: 主机的第一个网络地址
和host有关的系统调用,错误存放在h_error中,取值如下:
HOST_NOT_FOUND; TRY_AGAIN; NO_RECOVERY; NO_DATA;
4.3 数字地址到名字地址的转换
#include <netdb.h>
struct hostent *gethostbyaddr(const char *addr, size_t len, int family);
功能:将一个数字地址转换成一个名字地址或者主机名
结果:返回一个指向结构 hostent 的指针,如果失败,则返回空指针进行出错处理。
参数:addr不是char *类型,而是一个指向含有地址结构(in_addr或in6_addr)的指针
len是此结构的大小
family为协议地址族
4.4 得到当前主机的名字的函数
#include <sys/utsname.h>
int uname(struct utsname *name);
功能:返回当前主机的名字,常与gethostbyname一起使用确定本机的IP地址
结果:返回一个非负值则表示成功,-1 表示失败
结构utsname的定义形式如下:
#define UTS_NAMESIZE 16
#define UTS_NODESIZE 256
struct utsname
{
char sysname[UTS_NAMESIZE];
char nodename[UTS_NODESIZE];
char release[UTS_NAMESIZE];
char version[UTS_NAMESIZE];
char machine[UTS_NAMESIZE];
};
其中的字符串数字都是以'\0'结尾的c字符串。
4.5 服务器名到其端口号的转换函数
服务器名和主机名一样,用名字来表示,和端口之间有一映射关系。端口发生改变,无需重新编译,修改一下映射文件即可。
#inclued <netdb.h>
struct servent *getserbyname(const char *servname,const char *protoname);
功能:实现服务器名到端口号的转换。
结果:返回非空指针表示成功,否则是出错
参数:servname; protoname:协议名(可空缺)
servent 数据结构:
struct servent
{
char *s_name;
char *aliases;
int s_port;
char *s_porto;
}
参数:s_name: 规范服务器名; s_aliases:别名成员列表;s_port: 端口号;s_porto:协议名
struct servent *getservbyport(int port, const char *protoname);
功能:端口号到服务器名的转换
结果:返回结果为非空指针表示成功,否则为出错
参数:port: 网络字节序;protoname: 协议名
典型调用:struct servent *sptr;
sptr = getservbyname("domain", NULL);
或者
struct servent *sptr;
sptr = getservbyport(host(87), "udp");

浙公网安备 33010602011771号