TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法

TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法

  • 1.套接字类型与协议设置-socket函数
  • 2.地址族与数据序列-bind函数
    • (1)IP地址与端口号
    • (2)地址信息的表示
    • (3)网络字节序与地址转换
    • (4)网络地址的初始化与分配
    • (5)bind函数
  • 3.进入等待连接请求状态-listen函数
  • 4.受理客户端请求-accept函数
  • 5.请求连接-connect函数

在上一篇我们已经大概理解网络编程与套接字是做什么用的,本篇我们需要学会使用网络编程各基础函数的使用方法

1.套接字类型与协议设置-socket函数

#include
int socket(int domin,int type,int protocol);
  • 成功返回文件描述符,失败返回-1
  • domain是套接字使用的协议族信息,我们将重点讲解PF_INET对应的IPv4协议族
    TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法_第1张图片
  • type 是传输的类型信息
    ①面向连接的套接字SOCK_STREAM特点:套接字必须一一对应;可靠的、按序传递的、基于字节的面向连接的套接字
    ②面向消息的套接字SOCK_DGRAM特点:不可靠的、不按序传递的、以高速传输为目的套接字
面向连接的套接字SOCK_STREAM 面向消息的套接字SOCK_DGRAM
传输过程中数据不会消失 传输的数据可能丢失可能损毁
按序传输数据 强调快速传输而非传输顺序
传输的数据不存在边界 传输的数据存在边界
限制每次传输的数据大小
  • protocol 是计算机通信使用的协议信息
    大部分情况下传递0,除非同一协议族中存在多个数据传输方式相同的协议,才进行指定

eg
IPv4协议族中面向连接的套接字

int tcp_socket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);

IPv4协议族中面向消息的套接字

int udp_socket = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);

2.地址族与数据序列-bind函数

(1)IP地址与端口号

①IP地址
IP是网络协议的简写,是为了收发网络数据而分配给计算机的值,IP地址分为两类IPv4和IPv6,IPv4是四字节地址族、而IPv6是16字节地址族。
②端口号
端口号是为了区分程序中创建的套接字而分配给套接字的序号,也就是说操作系统此端口号把数据传输给相应端口的套接字。
TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法_第2张图片
端口号由16位构成,可分配的范围0-65535(0 - 2^16 -1)但是0-1023分配给特定程序,所以应当分配此范围外的值。注:TCP套接字和UDP套接字不会共用端口号,允许重复。

(2)地址信息的表示

本节以IPv4为核心,讨论目标地址的表示方法,假如我们需要表示IPv4地址族,IP地址是211.204.214.76,端口号2048,我们需要用一个结构体作为地址传递给bind函数

struct sockaddr_in
{
     
	sa_family_t		sin_family;//地址族
	unint16_t		sin_port;//16位端口号unsigned short类型
	struct in_addr	sin_addr;//32位IP地址
	char 			sin_zero[8];//不使用
}

其中内含的另一个结构体in_addr用来存放32位IP地址如下

struct in_addr
{
     
	in_addr_t	s_addr;//32位IPv4,unsigned long类型
}

下面说明sockaddr_in各个成员

  • sin_family:每种协议族使用的地址族都不同,IPv4是4字节地址族、而IPv6是16字节地址族。
    TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法_第3张图片
  • sin_port:以网络字节序保存的16位端口号(网络字节序后面说明)
  • sin_addr:以网络字节序保存的32位IP地址信息
  • sin_zero[8]:填充0
    此外,int bind(int sockfd,struct sockaddr,socklen_t addrlen);由于bind函数需要struct sockaddr的参数,如下
struct sockaddr
{
     
	sa_family_t		sin_family;
	char			sa_data[14];//地址信息
}

而我们使用的是struct sockaddr_in所以我们需要进行转换**(struct sockaddr*)&serv_addr**
eg

struct sockaddr_in serv_addr;
...
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
	//do something
...

可能有些朋友看到这会有些迷惑,这么多结构体是干什么的,其实我们只要明白,bind函数第二参数需要的struct sockaddr,而这个结构体不好用,因为他的IP地址、端口号、填充的0都在一个变量内,所以我们使用好用的struct sockaddr_in分别记录上述的东西,最后通过转换变成bind函数的第二参数。

(3)网络字节序与地址转换

在(2)中sockaddr_in的成员sin_port、sin_addr需要以网络字节序保存端口号与IP地址信息,那么为什么强调网络字节序呢?
其实CPU有两种保存数据的方式,即大端序与小端序,保存顺序的不同意味着接受数据的解析顺序不同,所以需要统一
大端序:高位存放到地位地址
TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法_第4张图片
小端序:高位存放到高位地址
TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法_第5张图片
而网络字节序使用大端序格式进行网络传输,所以在填充sockaddr_in之前小端序统统转化为大端序排列。下面介绍四个函数把主机字节序转化为网络字节序

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

htons中h代表主机字节序(host),n代表网络字节序(net),而末尾的s、l代表short和long,这样就好理解了。把short类型数据从主机字节序转化为网络字节序。
short用于转换端口号、long用于转换IP地址

(4)网络地址的初始化与分配

现在我们可以进行网络地址的初始化也就是对上面的结构体进行赋初值

struct sockaddr_in addr;
char* serv_port = "9190";	 //声明端口号字符串
memset(&addr,0,sizeof(addr));//addr所有成员初始化为同一值(sin_zero)
addr.sin_family = AF_INET;	 //指定地址族
addr.sin_addr.s_addr = htonl(INADDR_ANY);//自动获取服务端ip地址
addr.sin_port = htons(atoi(serv_port));	 //端口号初始化,atoi是把字符串转换成整型数的一个函数

此外还可以输入ip地址对addr.sin_addr.s_addr初始化,这里就需要检测ip地址是否合法
①inet_addr函数

#include
in_addr_t inet_addr(const char* string);
  • 将点分十进制ip地址转化为32位大端序返回
  • 失败返回INADDR_NONE

eg

struct sockaddr_in addr;
char* serv_ip = "211.217.168.13";
char* serv_port = "9190";
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(serv_ip);
addr.sin_port = htons(atoi(serv_port));

②inet_aton函数
与inet_addr函数作用相同,只不过利用了struct in_addr结构体

#include
in_addr_t inet_aton(const char* string,struct in_addr *addr);
  • 成功返回1,失败返回0

(5)bind函数

前面花了大篇幅说明了sockaddr_in初始化的方法,现在我们可以向套接字分配网络地址

#include
int bind(int sockfd,struct sockaddr* myaddr,socklen_t addrlen);
  • 成功返回1,失败返回0
  • sockfd要分配套接字的文件描述符
  • myaddr存有地址值结构体变量地址值
  • addrlen第二个结构体变量的长度

整个初始化过程:

int serv_sock;
struct sockaddr_in serv_addr;
char* serv_port = "9190";	 //声明端口号字符串
//创建套接字
serv_sock = sock(PF_INET,SOCK_STREAM,0);
//地址信息初始化
memset(&addr,0,sizeof(addr));//addr所有成员初始化为同一值(sin_zero)
addr.sin_family = AF_INET;	 //指定地址族
addr.sin_addr.s_addr = htonl(INADDR_ANY);//自动获取服务端ip地址
addr.sin_port = htons(atoi(serv_port));	 //端口号初始化
//分配地址信息
bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr);
...

3.进入等待连接请求状态-listen函数

在已经用bind函数分配地址后,需要调用listen函数进入等待连接请求状态。只有调用了listen函数后,客户端才能调用connect函数

#include
int listen(int sock,int backlog);
  • 成功返回0,失败返回-1
  • sock是希望进入等待连接状态的文件描述符
  • backlog是连接请求等待队列的长度,长度为5表示最多有5个连接请求进入队列
    TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法_第6张图片
    可以把服务端套接字视为一个门卫,将请求的连接放在等候室

eg

int serv_sock;
if(listen(serv_sock,5) == -1)
	//do something

4.受理客户端请求-accept函数

由于服务端套接字在listen函数中充当门卫,那么需要新建套接字连接到发起请求的客户端
TCP/IP网络编程笔记Chapter I -3网络编程各基础函数的使用方法_第7张图片

#include
int accept(int sock,struct sockaddr *addr,socklen_t *addrlen);
  • 成功返回创建套接字的文件描述符,失败返回-1
  • sock服务器套接字的文件描述符
  • addr发起请求的客户端地址信息变量
  • addrlen是addr结构体的长度

eg

int clnt_sock;
socklen_t clnt_addr_size;
clnt_addr_size = sizeof(clnt_sock);
clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size));

5.请求连接-connect函数

上面分析了服务端的函数,剩余一个客户端的connect函数用于请求连接

#include
int connect(int sock,struct sockaddr *servaddr,socklen_t addlen);
  • 成功返回0,失败返回-1
  • sock是客户端套接字文件描述符
  • servaddr目标服务端的地址信息变量
  • addlen是servaddr结构体长度

eg

if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
	//do something

客户端调用connect函数后两种情况返回:

  • 服务端接收连接请求(服务端把连接请求放到等待队列,不意味着调用accept函数)
  • 发生断网等异常中断

客户端在调用connect函数时使用本机IP,随机出端口进行自动分配,无需调用bind函数

好了,终于搞明白各函数的用法了,下一篇我们来实现一个简单的服务端与客户端

你可能感兴趣的:(TCP/IP网络编程,linux,socket,网络,c++)