第五章 Linux网络编程基础API

5.1 socket地址API

socket地址 = IP地址 + 端口对

5.1.1 主机字节序和网络字节序

字节序存储方式
大端字节序整数高位字节(23~31bit)存储在内存的低地址处
小端字节序整数高位字节(23~31bit)存储在内存的高地址处

主机字节序:小端字节序
网络字节序:大端字节序

Linux提供4个函数完成主机字节序和网络字节序之间转换:
IP地址: 32bit,long int
端口号: 16bit,short int

函数含义功能
htonlhost to network longIP地址: 主机字节序数据转化为网络字节序数据
htonshost to network short端口号: 主机字节序数据转化为网络字节序数据
ntohlnetwork to host longIP地址: 网络字节序数据转化为主机字节序数据
ntohsnetwork to host short端口号: 网络字节序数据转化为主机字节序数据
#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlong); 
unsigned short int htons(unsigned short int hostshort); 
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohl(unsigned short int netshort);

5.1.2 通用socket地址

结构体sockaddr表示socket地址:

#include <bits/socket.h>
struct sockaddr
{
	sa_family_t sa_family;  //地址族类型
	char sa_data[14];  //地址值
}
协议族地址族描述地址值含义和长度
PF_UNIXAF_UNIXUNIX本地域协议族文件的路径名,长度可达到108字节
PF_INETAF_UNIXTCP/IPv4协议族16 bit 端口号和32 bit IPv4,共6字节
PF_INET6AF_UNIXTCP/IPv6协议族16 bit 端口号,32 bit 流标识,128 bit IPv6 地址,32 bit 范围ID,共26字节

sa_data无法容纳协议族的地址值,Linux中定义新的通用socket地址结构体:

#include <bits/socket.h>
struct sockaddr_storage
{
	sa_family_t sa_family;  //地址族类型
	unsigned long int __ss_align; //内存对齐
	char __ss_padding[128-sizeof(__ss_align)];  //地址值
}

5.1.3 专用socket地址

通用socket地址结构体设置和获取IP地址和端口号需要执行烦琐的位操作。
UNIX本地域协议族:

#include <sys/un.h>
struct sockaddr_un
{
	sa_family_t sin_family;  //地址族:AF_UNIX
	char sun_path[108];  //文件路径名
};

IPv4:

struct sockaddr_in
{
	sa_family_t sin_family;  //地址族:AF_INET
	u_int16_t sin_port;  //端口号,要用网络字节序表示
	struct in_addr sin_addr;  //IPv4地址结构体
};
struct in_addr
{
	u_int32_t s_addr;  //IPv4地址,要用网络字节序表示
};

IPv6:

struct sockaddr_in6
{
	sa_family_t sin6 family;    /*地址族:AF INET6 */ 
	u_int16_t sin6_port;        /*端口号,要用网络字节序表示 */ 
	u_int32_t sin6_flowinfo;    /*流信息,应设置为0*/ 
	struct in6_addr sin6_addr;  /*IPv6地址结构体,见下面 */
    u_int32_t sin6_scope_id;    /*scopeID,尚处于实验阶段*/ 
);
struct in6_addr
{
	unsigned char sa addr[16];  /*IPv6地址,要用网络字节序表示*/
};

5.1.4 IP地址转换函数

点分十进制字符串表示IPv4地址,十六进制字符串表示IPv6地址,可读性好,需要转换为整数(二进制数)方能使用。
点分十进制字符串表示的IPv4地址和用网络字节序表示的IPv4地址之间转换:

#include <arpa/inet.h>
in_addr_t inet_addr(const char* strptr);
//点分十进制字符串表示的IPv4地址转化为用网络字节序表示的IPv4地址
//失败返回INADDR_NONE

int inet_aton(const char* cp, struct in_addr* inp);
//点分十进制字符串表示的IPv4地址转化为用网络字节序表示的IPv4地址
//转化结果存储与参数inp指向的地址结构中
//成功返回1,失败返回0

char* inet_ntoa(struct in_addr in);
//用网络字节序表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址
//返回值是静态内存,不可重入

同时适用于IPv4地址,IPv6地址的函数:

#include <arpa/inet.h>
int inet_pton(int af, const char* src, void* dst);
//字符串表示的IP地址src -> 用网络字节序整数表示的IP地址
//结果存储在dst指向的内存中
//af指定地址族:AF_INET / AF_INET6
//成功时返回1,失败则返回0并设置errno

const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);
//网络字节序整数表示的IP地址 -> 字符串表示的IP地址
//cnt:目标存储单元大小
//成功时返回目标存储单元的地址,失败则返回NULL并设置errno

两个宏帮助我们指定大小(分别用于IPv4,IPv6):

#include <netinet/in.h>
#define INET ADDRSTRLEN 16
#define INET6 ADDRSTRLEN 46

5.2 创建socket

UNIX/Linux:所有东西都是文件
socket:可读、可写、可控制、可关闭的文件描述符
创建一个socket:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//成功时返回socket文件描述符,失败返回-1,并设置errno
参数说明选择
domain指定哪个底层协议族PF_INET: IPv4 PF_INET6: IPv6 PF_UNIX: UNIX本地域协议族
type指定服务类型SOCK_STREAM: 流服务(TCP协议) SOCK_DGRAM: 数据报服务(UDP协议)
protocol具体协议0:默认协议

5.3 命名socket

命名socket: 将一个socket与socket地址绑定
服务器程序需要命名socket,客户端才知道如何连接它

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
//sockfd: 文件描述符  my_addr: socket地址  addrlen:socket地址长度
//将my_addr所指的socket地址分配给未命名的sockfd文件描述符
//成功返回0,失败返回-1并设置errno

5.4 监听socket

创建监听队列存放待处理的客户连接:

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//sockfd: 被监听的socket
//backlog: 内核监听队列的最大长度(backlog+1)
//成功返回0,失败返回-1,并设置errno

5.5 接受连接

从listen监听队列中接受一个连接:

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
//sockfd: 执行过listen系统调用的监听socket
//addr: 用来获取被接受连接的远端socket地址
//addrlen: socket地址的长度
//成功返回新的连接socket,标识被接受的这个连接,
//通过读写该socket来与被接受连接对应的客户端通信。
//失败返回-1,并设置errno

accept只是从监听队列中取出连接,不论连接处于何种状态,更不关心任何网络状况的变化。

5.6 发起连接

服务器通过listen调用来被动接受连接
客户端通过connect来主动与服务器建立连接

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
//sockfd: socket返回一个socket
//serv_addr: 服务器监听的socket地址
//addrlen: 这个地址的长度
//成功建立连接,sockfd唯一标识这个连接,客户端通过读写sockfd与服务器通信
//成功返回0,失败返回-1,并设置errno

5.7 关闭连接

关闭对应的socket:

#include <unistd.h>
int close(int fd);
//fd: 待关闭的socket
//并非立即关闭一个连接,将fd的引用计数减1,fd引用计数为0时,真正关闭连接
//fork将父进程中打开的socket的引用计数加1
//父进程、子进程都执行close
//成功返回0,失败返回-1,并设置errno

立即终止连接:

#include <sys/socket.h>
int shutdown(int sockfd, int howto);
//sockfd: 待关闭的socket

howto参数的可选值:
howto参数的可选值

5.8 数据读写

TCP数据读写

TCP流数据读写:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//sockfd: 读取sockfd上的数据
//buf、len: 指定读缓冲区的位置和大小
//flag通常设置为0
//成功: 返回实际读取到的数据的长度(可能小于len,需多次调用)
//返回0: 通信对方已经关闭连接
//返回-1:出错,设置errno
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//sockfd: 往sockfd上写入数据
//buf、len: 指定写缓冲区的位置和大小
//成功返回实际写入的数据的长度
//失败返回-1并设置errno

flags参数的可选值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值