博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《网络编程》套接字编程简介
阅读量:2192 次
发布时间:2019-05-02

本文共 7468 字,大约阅读时间需要 24 分钟。

       本节介绍的套接字是可以实现不同计算机之间的远程进程间通信。套接口是网络进程的 ID,在网络中每一个节点都有一个网络地址,也就是 IP 地址,两个进程间通信时,首先要确定各自所在网络节点的网络地址。但是,网络地址只要确定进程所在的计算机,由于一台计算机上同时可能有多个网络进程,所以仅凭网络地址还不能确定是网络中的哪一个进程,因此套接口中还需要其他信息,也就是端口。在一台计算机中,一个端口号只能分配给一个进程,所以,进程和端口之间是一一对应的关系。因此,使用端口号和网络地址的组合就能唯一地确定整个网络中的一个网络进程。

套接字地址结构

        把网络应用程序中所用到的网络地址和端口号信息放在一个结构体中,也就是套接口地址结构。大多数的套接口函数都需要一个指向套接口地址结构的指针作为参数,并以此来传递地址信息。每个协议族都定义它自己的套接口地址结构,套接口地址结构都是以 sockaddr_ 开头,并以每个协议中名中的两个字母作为结尾。

/* 套接字编程简介 *//* 通用套接字地址结构 *//* 定义于 
头文件中 */struct sockaddr{ uint8_t sa_len; sa_family_t sa_family; /* address family: AF_xxx value */ char sa_data[14];/* protocol-specific address */};/* * 说明: * 当套接字地址作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递; * 因此,必须把特定协议域的套接字地址结构强制转换为通用套接字地址结构类型; *//* IPv4 套接字地址结构 *//* 定义于
头文件中 */struct in_addr{ in_addr_t s_addr; /* 32-bit IPv4 address */ /* network byte ordered */};struct sockaddr_in{ uint8_t sin_len; /* length of struct (16) */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP or UDP port number */ /* network byte ordered */ struct in_addr sin_addr; /* 32-bit IPv4 address */ /* network byte ordered */ char sin_zero[8];/* unused */};/* 说明: * sin_len 是简化了长度可变套接字地址结构的处理,POSIX 规范并不要求有此成员; * sin_bzero 使所以套接字地址结构大小至少为 16 字节; * s_addr、sin_family、sin_port 是 POSIX 数据类型。in_addr_t 数据类型必须是一个至少 32 位的无符号整型,in_port_t 必须是一个至少 16 位的无符号整型, * 而 sa_family_t 通常是一个 8 位无符号整数,但在不支持长度字段的实现中,它则是 16 位无符号整数; * IPv4 地址和 TCP 或 UDP 端口号在套接字地址结构中是以 网络字节序来 存储的; *//* IPv6 套接字地址结构 *//* 定义于
头文件中 */struct in6_addr{ uint8_t s6_addr[16]; /* 128-bit IPv6 address */ /* network byte ordered */};#define SIN6_LEN /* required for compile-time tests */struct sockaddr_in6{ uint8_t sin6_len; /* length of this struct (28) */ sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* transport layer port# */ /* network byte ordered */ uint32_t sin6_flowinfo; /* flow information, undefined */ struct in6_addr sin6_addr; /* IPv6 address */ /* network byte ordered */ uint32_t sin6_scope_id; /* set of interfaces for a scope */};/* 说明: * 若系统支持套接字地址结构中的长度字段,则 SIN6_LEN 常值必须定义; * sin6_flowinfo 字段分成两个字段:低序 20 位是流标,高序 12 位保留; * 对于具备范围的地址,sin6_scope_id 字段标识其范围; *//* 为了使用 IPv6 的使用,定义了新的通用套接字地址结构 *//* 定义于
头文件中 */struct sockaddr_storage{ uint8_t ss_len; /* length of this struct (implementation dependent) */ sa_family_t ss_family; /* address family: AF_xxx value */ /* implementation-dependent elements to provide: * 1) alignment sufficient to fulfill the alignment requirments of all socket address types that the system supports. * 2) enough storage to hold any type of socket address that the system suports. */};/* 说明: * 若系统支持任何套接字地址结构有对齐需要,则该新通用地址结构能够满足最苛刻的对齐要求; * 新地址结构能够容纳系统支持的任何套接字地址结构; */

字节排序

        计算机在内存中的数据存储有两种方式:一种是小端字节序,即内存低地址存储数据低字节,内存高地址存储数据高字节;另一种是大端字节序,即内存低地址存储数据高字节,内存高地址存储数据低字节;具体如下图所示:

        网络字节序采用的是大端字节序。某个系统所采用的字节序称为主机字节序(也称处理器字节序),主机字节序可能是小端字节序,也有可能是大端字节序。在网络协议中处理多字节数据时采用的都是网络字节序,即大端字节序,而不是主机字节序。要把主机字节序和网络字节序相对应,则必须采用字节序转换函数,以下是主机字节序和网络字节序之间的转换函数:

/* * 函数功能:主机字节序和网络字节序之间的转换; * 返回值:返回对应类型表示的字节序; * 函数原型: */#include 
uint32_t htonl(uint32_t hostint32);//返回值:以网络字节序表示的32位整型数;uint16_t htons(uint16_t hostint16);//返回值:以网络字节序表示的16位整型数;uint32_t ntohl(uint32_t netint32);//返回值:以主机字节序表示的32位整型数;uint16_t ntohs(uint16_t netint16);//返回值:以主机字节序表示的16位整型数;/* * 说明: * 从以上函数我们可以发现: * h表示“主机(host)”字节序,n表示“网络(network)”字节序; * l表示“长(long)”整型,s表示“短(short)”整型; * */

字节操作函数

/* * 函数功能:字节操作; * 函数原型: */#include 
void bzero(void *dest, size_t nbytes);//将dest所存储的数据前nbytes字节初始化为0;void bcopy(const void *str, void *dest, size_t nbytes);//将str所存储的数据前nbytes字节复制到dest中;int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);//比较两个字符的前nbytes字节的大小;void *memset(void *dest, int c, size_t len);//将dest所存储的数据前len字节初始化为c;void *memcopy(void *dest,const void *src, size_t nbytes);//将src所存储的数据前nbytes字节复制到dest中;int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);//比较两个字符的前nbytes字节的大小;

地址转换函数

       若要把网络字节序的地址打印成我们能够理解的格式,则需要转换函数把网络字节序转换成我们可读的地址格式,inet_ntop 和 inet_pton 这两个函数把对 IP 地址格式进行转换,其定义如下:

#include
const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size);//若成功则返回地址字符串指针,出错则返回NULL;int inet_pton(int domain, const char *restrict str, void *restrict addr);//若成功则返回1,格式无效则返回0,出错则返回-1;/* * 说明: * inet_ntop 是将网络字节序的二进制地址转换成文本字符串格式; * inet_pton 是将文本字符串格式转换成网络字节序的二进制地址; * 参数domain只能取值:AF_INET 或 AF_INET6; */

 readn 、 writen 和 readline 函数

        字节流套接字上的 read 和 write 函数不同于普通的文件 I/O。当我们要求多次输入或输出时,必须多次调用 read 和 write 函数,这样比较麻烦。所以我们经常使用 readn 或 writen 函数。

#include "unp.h"/* 函数功能:读/写 多个字节流;* 返回值:读或写的字节数,若出错则返回-1;* 函数原型:*/ssize_t readn(int filedes, void *buff, size_t nbytes);ssize_t writen(int filedes, const void *buff, size_t nbytes);ssize_t readline(int filedes, void *buff, size_t maxlen);

其具体实现如下:

/* include readn */#include	"unp.h"ssize_t						/* Read "n" bytes from a descriptor. */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;		/* and call read() again */			else				return(-1);		} else if (nread == 0)			break;				/* EOF */		nleft -= nread;		ptr   += nread;	}	return(n - nleft);		/* return >= 0 */}/* end readn */ssize_tReadn(int fd, void *ptr, size_t nbytes){	ssize_t		n;	if ( (n = readn(fd, ptr, nbytes)) < 0)		err_sys("readn error");	return(n);}

/* include writen */#include	"unp.h"ssize_t						/* Write "n" bytes to a descriptor. */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;		/* and call write() again */			else				return(-1);			/* error */		}		nleft -= nwritten;		ptr   += nwritten;	}	return(n);}/* end writen */voidWriten(int fd, void *ptr, size_t nbytes){	if (writen(fd, ptr, nbytes) != nbytes)		err_sys("writen error");}

/* include readline */#include	"unp.h"static int	read_cnt;static char	*read_ptr;static char	read_buf[MAXLINE];static ssize_tmy_read(int fd, char *ptr){	if (read_cnt <= 0) {again:		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {			if (errno == EINTR)				goto again;			return(-1);		} else if (read_cnt == 0)			return(0);		read_ptr = read_buf;	}	read_cnt--;	*ptr = *read_ptr++;	return(1);}ssize_treadline(int fd, void *vptr, size_t maxlen){	ssize_t	n, rc;	char	c, *ptr;	ptr = vptr;	for (n = 1; n < maxlen; n++) {		if ( (rc = my_read(fd, &c)) == 1) {			*ptr++ = c;			if (c == '\n')				break;	/* newline is stored, like fgets() */		} else if (rc == 0) {			*ptr = 0;			return(n - 1);	/* EOF, n - 1 bytes were read */		} else			return(-1);		/* error, errno set by read() */	}	*ptr = 0;	/* null terminate like fgets() */	return(n);}ssize_treadlinebuf(void **vptrptr){	if (read_cnt)		*vptrptr = read_ptr;	return(read_cnt);}/* end readline */ssize_tReadline(int fd, void *ptr, size_t maxlen){	ssize_t		n;	if ( (n = readline(fd, ptr, maxlen)) < 0)		err_sys("readline error");	return(n);}
参考资料:

《Unix 网络编程》

你可能感兴趣的文章
Leetcode C++《热题 Hot 100-22》2.两数相加
查看>>
Leetcode C++《热题 Hot 100-23》3.无重复字符的最长子串
查看>>
Leetcode C++《热题 Hot 100-24》5.最长回文子串
查看>>
Leetcode C++《热题 Hot 100-28》19.删除链表的倒数第N个节点
查看>>
Leetcode C++《热题 Hot 100-29》22.括号生成
查看>>
Leetcode C++《热题 Hot 100-47》236.二叉树的最近公共祖先
查看>>
Leetcode C++《热题 Hot 100-48》406.根据身高重建队列
查看>>
《kubernetes权威指南·第四版》第二章:kubernetes安装配置指南
查看>>
Leetcode C++《热题 Hot 100-49》399.除法求值
查看>>
Leetcode C++《热题 Hot 100-51》152. 乘积最大子序列
查看>>
Leetcode C++ 《第181场周赛-1》 5364. 按既定顺序创建目标数组
查看>>
Leetcode C++ 《第181场周赛-2》 1390. 四因数
查看>>
阿里云《云原生》公开课笔记 第一章 云原生启蒙
查看>>
阿里云《云原生》公开课笔记 第二章 容器基本概念
查看>>
阿里云《云原生》公开课笔记 第三章 kubernetes核心概念
查看>>
阿里云《云原生》公开课笔记 第四章 理解Pod和容器设计模式
查看>>
阿里云《云原生》公开课笔记 第五章 应用编排与管理
查看>>
阿里云《云原生》公开课笔记 第六章 应用编排与管理:Deployment
查看>>
阿里云《云原生》公开课笔记 第七章 应用编排与管理:Job和DaemonSet
查看>>
阿里云《云原生》公开课笔记 第八章 应用配置管理
查看>>