Linux网络编程 1.socket套接字

ぐ巨炮叔叔 提交于 2019-12-17 00:58:00

Linux网络编程 1.socket套接字

1.什么是socket套接字

​ Socket套接字由远景研究规划局(Advanced Research Projects Agency, ARPA)资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将TCP/IP协议相关软件移植到UNIX类系统中。设计者开发了一个接口,以便应用程序能简单地调用该接口通信。这个接口不断完善,最终形成了Socket套接字。Linux系统采用了Socket套接字,因此,Socket接口就被广泛使用,到现在已经成为事实上的标准。与套接字相关的函数被包含在头文件sys/socket.h中。

简单来说socket套接字就是一套TCP/IP通信协议的API。

套接字怎么使用呢?

套接字通信一般用于连接客户端和服务器。在服务器端:

​ 由于服务器时被动提供服务,客户端主动连接,所以服务器应该先于客户端启动,启动服务器之后服务器的IP和端口不能变化。服务器的ip和端口要在启动之前绑定。

在客户端:

​ 因为客户端是主动连接服务器,所以需要知道服务器的ip地址,和服务器主机上的服务器进程:端口。

到这我们先不着急看代码,还有一个重要问题需要解决:

首先我们想象一个通信的场景:

客户端给服务器发送了一个复杂的数据-结构体,在客户端数据的低字节存储在内存的低地址位,高字节存储在高地址位。而服务端恰好相反。这时候服务器接收的数据就会混乱。

2.字节序

在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编/译码从而导致通信失败。

字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)

目前在各种体系的计算机中通常采用的字节存储机制主要有两种:Big-Endian 和 Little-Endian,下面先从字节序说起。

概念:

小端存储->主机字节序

存储方式:数据低字节存放在内存低地址位,高字节存放在高地址位(低低,高高)。我们的电脑都是按照小端存储在内存中存数据的。

大端存储->网络字节序

存储方式:数据的低字节存放在内存的高地址位,高字节存放在低地址位(低高,高低)。通信过程中使用的数据全部使用网络字节序。

举例:
// 有一个16位的数: 0x12345678   ->   这个数是 4 个字节	
// 这个数在内存中按照大端, 小端如何存储
// 内存低地址位 --------------> 内存的高地址位
小端:  0x78   0x56   0x34   0x12
大端:  0x12   0x34   0x56   0x78

相关函数:

BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数:htons、htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。

函数看这里:
#include <arpa/inet.h>
// h -> host -> 主机字节序
// to -> 转换
// n -> net -> 网络字节序
// s -> short -> 16位整形数
// l -> long -> 32位整形数
// 参数: 要转换的数据
// 返回值: 转换完成之后得到的结果

//一般端口的转换:
//主机字节序->网络字节序
uint16_t htons(uint16_t hostshort);
//网络字节序转主机字节序
uint16_t htons(uint16_t netshort);

//一般对整形IP进行转换
//主机字节序->网络字节序
uint32_t htonl(uint32_t hostlong);
//网络字节序->主机字节序
uint32_t ntohl(uint32_t netlong);



点分十进制IP地址转换

#include <arpa/inet.h>
// p -> 主机字节序的点分十进制IP地址(字符串): 192.168.1.100
// n -> 网络字节序的整形IP地址
// 将本地IP字符串 -> 整形大端IP地址
int inet_pton(int af,const char* src,void *dst);
参数:
-af:地址族协议 ipv4:AF_INET, ipv6:AF_INET6
-src: 要被转换的点分十进制的IP地址: 192.168.1.100这种格式
- dst: 转换得到的大端IP存储到这个指针指向的内存中
	返回值:
		转换成功: 0
           失败: -1

// 整形大端IP地址  -> 将本地IP字符串         
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	参数: 
		- af: 地址族协议, 使用的ipv4还是ipv6的ip地址
			- ipv4: AF_INET
			- ipv6: AF_INET6
		- src: 这个指针指向的内存中存储了大端的整形IP地址
		- dst: 存储得到的IP地址字符串
		- size: dst参数指向的内存的总大小(容量)
    返回值:
		成功: 返回第三个参数dst的地址
		失败: NULL               
               

3.sockaddr数据结构

说完了字节序,我们来讨论一下用于存放地址信息的结构体: sockaddr

如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5Mv2QP7-1576504082585)(assets/sockaddr.png)]

//结构不算难理解,后面主要会用到sockaddr_in和sockaddr
struct sockaddr {
	sa_family_t sa_family;	// 地址族协议, ipv4, ipv6
	char        sa_data[14];
}

struct in_addr
{
    in_addr_t s_addr;
};  
struct sockaddr_in
{
    sa_family_t sin_family;		/* __SOCKADDR_COMMON(sin_) */
    in_port_t sin_port;         /* Port number.  */
    struct in_addr sin_addr;    /* Internet address.  */
    /* Pad to size of `struct sockaddr'. */
    unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE -
               sizeof (in_port_t) - sizeof (struct in_addr)];
};  

4.套接字函数

#include <arpa/inet.h>
// 创建套接字
int socket(int domain, int type, int protocol);
	参数:
		- domain: 指定地址族协议
			- AF_UNIX, AF_LOCAL: 用于本地进程间通信(本地套接字)
            - AF_INET: 使用IPv4
            - AF_INET6: 使用IPv6
        - type: 使用什么传输协议
        	- SOCK_STREAM: 流式传输协议
        	- SOCK_DGRAM: 报式传输协议
        - protocol: 指定具体的协议, 这个参数一般指定为0
        	- 使用流式协议中的tcp协议
        	- 使用报式协议中的udp协议
	返回值:
		成功: 返回用于socket的文件描述符
		失败: -1

// 将监听的套接字和本地的IP和端口绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	参数:
		- sockfd: 用于监听的套接字
		- addr: 在这个变量中初始化要绑定的IP和端口信息
		- addrlen: 第二个参数addr对应的内存大小
	返回值:
		成功: 0
        失败: -1

// 设置监听->给监听的套接字
int listen(int sockfd, int backlog);
	参数:
		- sockfd: 绑定成功的套接字
		- backlog: 可以最大同时监听多少个客户端连接
			- 这个值最大是128, 如果参数值超过128, 还是按128处理
	返回值:
		成功: 0
        失败: -1
            
// 等待并接受客户端连接, 阻塞函数
// 开始监听之后, 没有客户端连接服务器, 这时候程序就阻塞在了accept函数上
// 当检测到了客户端连接, 这个函数解除阻塞, 和客户端建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	参数:
		- sockfd: 用户监听的套接字
		- addr: 传出参数, 得到连接的客户端的IP和端口信息
		- addrlen: 传入传出参数, 记录了参数addr指针对应的内存大小
	返回值:
		成功: 通信的文件描述符
		失败: -1

// 接收数据
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
	参数:
		- 前三个参数和read一样
		- flags: 套接字是一些属性, 默认使用0
// 发送数据
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
	参数:
		- 前三个参数和write一样
		- flags: 套接字是一些属性, 默认使用0
		
// 连接服务器, 这个函数也是阻塞函数
// 连接完成之后, 解除阻塞
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	参数:
		- sockfd: 客户端用于通信的套接字
		- addr: 初始化服务器的IP和端口, 通过这个地址连接服务器
		- addrlen: 参数 addr 指针指向的内存大小, sizeof(addr)
   返回值:
		成功: 0
        失败: -1

onnect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
- sockfd: 客户端用于通信的套接字
- addr: 初始化服务器的IP和端口, 通过这个地址连接服务器
- addrlen: 参数 addr 指针指向的内存大小, sizeof(addr)
返回值:
成功: 0
失败: -1


后面我还会继续整理套接字通信步骤
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!