Linux Socekt 相关操作代码
Posted 行码阁119
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux Socekt 相关操作代码相关的知识,希望对你有一定的参考价值。
内容来自于:
https://www.nowcoder.com/courses/cover/live/504
便于复习。
1、字节序
判断时候小端字节序还是大端字节序
小端字节序:数字高位在内存高地址存放,低位在内存低地址存放
大端字节序:与小端相反
# include <stdio.h>
int main()
union
short value;
char bytes[sizeof(short)];
test;
test.value = 0x0102;
if(test.bytes[0] == 1 && test.bytes[1] == 2)
printf("大端字节序");
else if(test.bytes[0] == 2 && test.bytes[1] == 1)
printf("小端字节序");
return 0;
1.1 字节序转换函数
#include <arpa/inet.h>
// 转换端口
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 主机字节序 - 网络字节序
// 转IP
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); // 主机字节序 - 网络字节序
h - host 主机,主机字节序
to - 转换成什么
n - network 网络字节序
s - short unsigned short
l - long unsigned int
# include <stdio.h>
# include <arpa/inet.h>
//网络通信时, 需要将网络字节序转换为网络字节序
//另外一段根据实际情况将网络字节序转换为主机字节序
int main()
unsigned short a = 0x0102;
unsigned short b = htons(a);
printf("%x\\n", b);
printf("%x\\n", a);
#include <stdio.h>
#include <arpa/inet.h>
int main()
// htons 转换端口
unsigned short a = 0x0102;
printf("a : %x\\n", a);
unsigned short b = htons(a);
printf("b : %x\\n", b);
printf("=======================\\n");
// htonl 转换IP
char buf[4] = 192, 168, 1, 100;
int num = *(int *)buf;
int sum = htonl(num);
unsigned char *p = (char *)∑
printf("%d %d %d %d\\n", *p, *(p+1), *(p+2), *(p+3));
printf("=======================\\n");
// ntohl
unsigned char buf1[4] = 1, 1, 168, 192;
int num1 = *(int *)buf1;
int sum1 = ntohl(num1);
unsigned char *p1 = (unsigned char *)&sum1;
printf("%d %d %d %d\\n", *p1, *(p1+1), *(p1+2), *(p1+3));
// ntohs
return 0;
2 socket地址
// socket地址其实是一个结构体,封装端口号和IP等信息。后面的socket相关的api中需要使用到这个 socket地址。 // 客户端 -> 服务器(IP, Port)
2.1 通用 socket 地址
socket 网络编程接口中表示 socket 地址的是结构体 sockaddr,其定义如下:
#include <bits/socket.h> struct sockaddr sa_family_t sa_family; char sa_data[14]; ; typedef unsigned short int sa_family_t;
sa_family 成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见的协议 族(protocol family,也称 domain)和对应的地址族入下所示:
协议族 地址族 描述 PF_UNIX AF_UNIX UNIX本地域协议族 PF_INET AF_INET TCP/IPv4协议族 PF_INET6 AF_INET6 TCP/IPv6协议族
宏 PF_* 和 AF_* 都定义在 bits/socket.h 头文件中,且后者与前者有完全相同的值,所以二者通常混 用。
sa_data 成员用于存放 socket 地址值。但是,不同的协议族的地址值具有不同的含义和长度,如下所 示:
协议族 地址值含义和长度 PF_UNIX 文件的路径名,长度可达到108字节 PF_INET 16 bit 端口号和 32 bit IPv4 地址,共 6 字节 PF_INET6 16 bit 端口号,32 bit 流标识,128 bit IPv6 地址,32 bit 范围 ID,共 26 字节
由上表可知,14 字节的 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) ]; ; typedef unsigned short int sa_family_t;
3、专用socket地址
很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 struct sockaddr 结构体,为了向前兼容,现 在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是 sockaddr_in 还是 sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。
#include <sys/un.h>
struct sockaddr_un
sa_family_t sin_family;
char sun_path[108];
;
TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用的 socket 地址结构体,它们分别用于 IPv4 和 IPv6:
#include <netinet/in.h>
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)];
;
struct in_addr
in_addr_t s_addr;
;
struct sockaddr_in6
sa_family_t sin6_family;
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
4、. IP地址转换(字符串ip-整数 ,主机、网络 字节序的转换)
通常,人们习惯用可读性好的字符串来表示 IP 地址,比如用点分十进制字符串表示 IPv4 地址,以及用 十六进制字符串表示 IPv6 地址。但编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录 日志时则相反,我们要把整数表示的 IP 地址转化为可读的字符串。下面 3 个函数可用于用点分十进制字 符串表示的 IPv4 地址和用网络字节序整数表示的 IPv4 地址之间的转换:
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
# include<stdio.h>
# include<arpa/inet.h>
int main()
char * str = "199.5.3.2";
struct in_addr a;
a.s_addr = 199532;
char *temp = inet_ntoa(a);
printf("%s\\n", temp);
int t = inet_addr(temp);
printf("%d\\n", t);
struct in_addr *p;
int c = inet_aton(temp, p);
printf("%d\\n", c);
printf("%u\\n", p->s_addr);
return 0;
下面这对更新的函数也能完成前面 3 个函数同样的功能,并且它们同时适用 IPv4 地址和 IPv6 地址(以后用下面的函数):
include <arpa/inet.h>
// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
af:地址族: AF_INET AF_INET6
src:需要转换的点分十进制的IP字符串
dst:转换后的结果保存在这个里面
// 将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af:地址族: AF_INET AF_INET6
src: 要转换的ip的整数的地址
dst: 转换成IP地址字符串保存的地方
size:第三个参数的大小(数组的大小)
返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
# include<stdio.h>
# include<arpa/inet.h>
int main()
char buf[] = "192.168.1.4";
unsigned int num = 0;
inet_pton(AF_INET, buf, &num);
unsigned char *p = (unsigned char *)#
printf("%d %d %d %d\\n", *p, *(p + 1), *(p + 2), *(p + 3));
char str[16];
const char *t = inet_ntop(AF_INET6, &num, str, 16);
printf("%d\\n", t == str);
printf("%ld\\n", sizeof(t));
printf("%s\\n", t);
return 0;
5、TCP通信流程
// TCP 和 UDP -> 传输层的协议
UDP:用户数据报协议,面向无连接,可以单播,多播,广播, 面向数据报,不可靠
TCP:传输控制协议,面向连接的,可靠的,基于字节流,仅支持单播传输
UDP TCP
是否创建连接 无连接 面向连接
是否可靠 不可靠 可靠的
连接的对象个数 一对一、一对多、多对一、多对多 支持一对一
传输的方式 面向数据报 面向字节流
首部开销 8个字节 最少20个字节
适用场景 实时应用(视频会议,直播) 可靠性高的应用(文件传输)
// TCP 通信的流程
5.1 服务器端 (被动接受连接的角色)
1. 创建一个用于监听的套接字
- 监听:监听有客户端的连接
- 套接字:这个套接字其实就是一个文件描述符
2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)
- 客户端连接服务器的时候使用的就是这个IP和端口
3. 设置监听,监听的fd开始工作(看读缓冲区是否有数据)
4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字(fd)
5. 通信
- 接收数据
- 发送数据
6. 通信结束,断开连接
6. 套接字函数
// 客户端5.2 客服端(主动接受连接的角色)
1. 创建一个用于通信的套接字(fd) (端口是随机的)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
6. 套接字函数
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
int socket(int domain, int type, int protocol);
- 功能:创建一个套接字
- 参数:
- domain: 协议族
AF_INET : ipv4
AF_INET6 : ipv6
AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信)
- type: 通信过程中使用的协议类型
SOCK_STREAM : 流式协议
SOCK_DGRAM : 报式协议
- protocol : 具体的一个协议。一般写0
- SOCK_STREAM : 流式协议默认使用 TCP
- SOCK_DGRAM : 报式协议默认使用 UDP
- 返回值:
- 成功:返回文件描述符,操作的就是内核缓冲区。
- 失败:-1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命
名
- 功能:绑定,将fd 和本地的IP + 端口进行绑定
- 参数:
- sockfd : 通过socket函数得到的文件描述符
- addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息
- addrlen : 第二个参数结构体占的内存大小
int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn
- 功能:监听这个socket上的连接
- 参数:
- sockfd : 通过socket()函数得到的文件描述符
- backlog : 未连接的和已经连接的和的最大值, 5
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
- 参数:
- sockfd : 用于监听的文件描述符
- addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
- addrlen : 指定第二个参数的对应的内存大小
- 返回值:
- 成功 :用于通信的文件描述符
- -1 : 失败
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); - 功能: 客户端连接服务器 - 参数: - sockfd : 用于通信的文件描述符 - addr : 客户端要连接的服务器的地址信息 - addrlen : 第二个参数的内存大小 - 返回值:成功 0, 失败 -1 ssize_t write(int fd, const void *buf, size_t count); // 写数据 ssize_t read(int fd, void *buf, size_t count); // 读数据
7. TCP 三次握手
TCP 是一种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如 IP 地址、端口号等。TCP 可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在 TCP 头部。
TCP 提供了一种可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接。采用 四次挥手来关闭一个连接。16 位端口号(port number):告知主机报文段是来自哪里(源端口)以及传给哪个上层协议或
应用程序(目的端口)的。进行 TCP 通信时,客户端通常使用系统自动选择的临时端口号。
32 位序号(sequence number):一次 TCP 通信(从 TCP 连接建立到断开)过程中某一个传输
方向上的字节流的每个字节的编号。假设主机 A 和主机 B 进行 TCP 通信,A 发送给 B 的第一个
TCP 报文段中,序号值被系统初始化为某个随机值 ISN(Initial Sequence Number,初始序号
值)。那么在该传输方向上(从 A 到 B),后续的 TCP 报文段中序号值将被系统设置成 ISN 加上
该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个 TCP 报文段传送的数据是字
节流中的第 1025 ~ 2048 字节,那么该报文段的序号值就是 ISN + 1025。另外一个传输方向(从
B 到 A)的 TCP 报文段的序号值也具有相同的含义。
32 位确认号(acknowledgement number):用作对另一方发送来的 TCP 报文段的响应。其值是
收到的 TCP 报文段的序号值 + 标志位长度(SYN,FIN) + 数据长度 。假设主机 A 和主机 B 进行
TCP 通信,那么 A 发送出的 TCP 报文段不仅携带自己的序号,而且包含对 B 发送来的 TCP 报文段
的确认号。反之,B 发送出的 TCP 报文段也同样携带自己的序号和对 A 发送来的报文段的确认序
号。
4 位头部长度(head length):标识该 TCP 头部有多少个 32 bit(4 字节)。因为 4 位最大能表示
15,所以 TCP 头部最长是60 字节。
- 功能: 客户端连接服务器
- 参数:
- sockfd : 用于通信的文件描述符
- addr : 客户端要连接的服务器的地址信息
- addrlen : 第二个参数的内存大小
- 返回值:成功 0, 失败 -1
ssize_t write(int fd, const void *buf, size_t count); // 写数据
ssize_t read(int fd, void *buf, size_t count); // 读数据
6 位标志位包含如下几项:
URG 标志,表示紧急指针(urgent pointer)是否有效。
ACK 标志,表示确认号是否有效。我们称携带 ACK 标志的 TCP 报文段为确认报文段。
PSH 标志,提示接收端应用程序应该立即从 TCP 接收缓冲区中读走数据,为接收后续数据腾
出空间(如果应用程序不将接收到的数据读走,它们就会一直停留在 TCP 接收缓冲区中)。
RST 标志,表示要求对方重新建立连接。我们称携带 RST 标志的 TCP 报文段为复位报文段。
SYN 标志,表示请求建立一个连接。我们称携带 SYN 标志的 TCP 报文段为同步报文段。
FIN 标志,表示通知对方本端要关闭连接了。我们称携带 FIN 标志的 TCP 报文段为结束报文
段。16 位窗口大小(window size):是 TCP 流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。16 位校验和(TCP checksum):由发送端填充,接收端对 TCP 报文段执行 CRC 算法以校验TCP 报文段在传输过程中是否损坏。注意,这个校验不仅包括 TCP 头部,也包括数据部分。这也是 TCP 可靠传输的一个重要保障。16 位紧急指针(urgent pointer):是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP 的紧急指针是发送端向接收端发送紧急数据的方法。
以上是关于Linux Socekt 相关操作代码的主要内容,如果未能解决你的问题,请参考以下文章
linux下脚本实现从一个文件中读数,有文件多行 (每行都是:名称 + 多个空格 + 数据 ),