《网络编程》套接字编程简介

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

套接字地址结构

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

/* 套接字编程简介 */

/* 通用套接字地址结构 */
/* 定义于 <sys/socket.h> 头文件中 */

struct sockaddr{

    uint8_t     sa_len;
    sa_family_t sa_family;  /* address family: AF_xxx value */
    char        sa_data[14];/* protocol-specific address */
};
/*
 * 说明:
 * 当套接字地址作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递;
 * 因此,必须把特定协议域的套接字地址结构强制转换为通用套接字地址结构类型;
 */

/* IPv4 套接字地址结构 */
/* 定义于 <netinet/in.h> 头文件中 */

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 套接字地址结构 */
/* 定义于 <netinet/in.h> 头文件中 */

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 的使用,定义了新的通用套接字地址结构 */
/* 定义于 <netinet/in.h> 头文件中 */

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 <arpa/inet.h>
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 <strings.h>
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<arpa/inet.h>
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 */
/* 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 */
/* include readline */
#include	"unp.h"

static int	read_cnt;
static char	*read_ptr;
static char	read_buf[MAXLINE];

static ssize_t
my_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_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 = 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_t
readlinebuf(void **vptrptr)
{
	if (read_cnt)
		*vptrptr = read_ptr;
	return(read_cnt);
}
/* end readline */

ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
	ssize_t		n;

	if ( (n = readline(fd, ptr, maxlen)) < 0)
		err_sys("readline error");
	return(n);
}

参考资料:

《Unix 网络编程》

时间: 2024-08-05 22:12:55

《网络编程》套接字编程简介的相关文章

【Unix网络编程】chapter3 套接字编程简介

chapter3套接字编程简介3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数,不过这两个新函数inet_pton和inet_ntop同时适用于IPv4和IPv6. 3.2 套接字地址结构 sockaddr_ 3.2.1 IPv4套接字地址结构 IPv4套接字地址结构通常也称为"网际套接字地址结构",它以sockaddr_in命令,定义在<netinet/in.

UNIX网络编程笔记(2)—套接字编程简介

套接字编程概述 说到网络编程一定都离不开套接字,以前用起来的时候大多靠记下来它的用法,这一次希望能理解一些更底层的东西,当然这些都是网络编程的基础- (1)套接字地址结构 大多说套接字函数都需要一个指向套接字地址结构的指针作为参数,每个协议族都定义它自己的套接字地址结构,这些结构都以sockadd_开头. IPV4套接字地址结构 IPv4套接字地址结构通常称为"网际套接字地址结构",以sockaddr_in命名,并定义在 /* Internet address. */ typedef

《网络编程》基本 TCP 套接字编程

在进行套接字编程之前必须熟悉其地址结构,有关套接字的地址结构可参考文章<套接字编程简介>.基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符.客户端随后调用connect 函数,服务器端则调用 bind.listen 和accept 函数.套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown 函数关闭套接字.下面针对套接字编程实现过程中所调用的函数进程分析.以下是基于 TCP 套接字编程的流程图: socket 函数 套接

linux网络环境下socket套接字编程(UDP文件传输)

今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中,如果我们使用TCP传输,会造成传输速度较慢的情况,所以我们在进行文件传输的过程中,最好要使用UDP传输. 在其中,我们需要写两个程序,一个客户端,一个服务端,在一个终端中,先运行服务端,在运行客户端,在服务端和客户端都输入IP地址和端口号,注意服务端和客户端的端口号要相同,然后选择功能,在linux

linux网络编程-(socket套接字编程UDP传输)

今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中,如果我们使用TCP传输,会造成传输速度较慢的情况,所以我们在进行文件传输的过程中,最好要使用UDP传输. 在其中,我们需要写两个程序,一个客户端,一个服务端,在一个终端中,先运行服务端,在运行客户端,在服务端和客户端都输入IP地址和端口号,注意服务端和客户端的端口号要相同,然后选择功能,在linux

Unix网络编程之基本TCP套接字编程(上)

TCP客户/服务器实例 服务器程序 #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); //1 bzero(&servaddr, sizeof(servad

《网络编程》基于 TCP 套接字编程的分析

本节围绕着基于 TCP 套接字编程实现的客户端和服务器进行分析,首先给出一个简单的客户端和服务器模式的基于 TCP 套接字的编程实现,然后针对实现过程中所出现的问题逐步解决.有关基于 TCP 套接字的编程过程可参考文章<基本 TCP 套接字编程>.该编程实现的功能如下: (1)客户端从标准输入读取文本,并发送给服务器: (2)服务器从网络输入读取该文本,并回射给客户端: (3)客户端从网络读取由服务器回射的文本,并通过标准输出回显到终端: 简单实现流图如下:注:画图过程通信双方是单独的箭头,只

网络通讯之套接字编程

#include<stdio.h> #include<sys/socket.h> #include<netinet/in.h> static char out_ip[15] = "52.0.10.188"; static int out_port = 8888; int main() { char sSendBuf[2049], sRecvBuf[2049]; int connfd = 0, iRet = 0, iSendLen = 0; struc

【UNIX网络编程(二)】基本TCP套接字编程函数

基于TCP客户/服务器程序的套接字函数图如下: 执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型. #include <sys/socket.h> int socket(int family, int type, int protocol);/*返回值:若成功则为非负描述符,若出错则为-1*/ socket函数成功时返回一个小的非负整数值,它与文件描述符类似,把它称为套接字描述符,简称sockfd.family参数指明协议族,被称为协议域.type参数指